NextRush

@nextrush/adapter-node

Node.js HTTP adapter for NextRush

Connect NextRush to Node.js's built-in HTTP server.

Included in nextrush

This adapter is included when you install the nextrush package. Install separately only when using other runtimes alongside Node.js.

$ pnpm add @nextrush/adapter-node

Node.js 22+ Required

NextRush requires Node.js 22.0.0 or later. Earlier versions are not supported.

What It Provides

The adapter exports server lifecycle functions and context utilities:

// Server functions
import { serve, listen, createHandler } from '@nextrush/adapter-node';
import type { ServeOptions, ServerInstance } from '@nextrush/adapter-node';

// Context (advanced)
import { NodeContext, createNodeContext } from '@nextrush/adapter-node';

// Body source (advanced)
import {
  NodeBodySource,
  createNodeBodySource,
  createEmptyBodySource,
} from '@nextrush/adapter-node';

Quick Start

import { createApp } from '@nextrush/core';
import { createRouter } from '@nextrush/router';
import { serve } from '@nextrush/adapter-node';

const app = createApp();
const router = createRouter();

router.get('/', (ctx) => {
  ctx.json({ message: 'Hello from Node.js!' });
});

app.route('/', router);

await serve(app, {
  port: 3000,
  onListen: ({ port }) => console.log(`Listening on port ${port}`),
});

Using the Meta Package

The nextrush package includes this adapter:

import { createApp, createRouter, listen } from 'nextrush';

const app = createApp();
const router = createRouter();

router.get('/', (ctx) => ctx.json({ hello: 'world' }));

app.route('/', router);
await listen(app, 3000);

serve()

Start an HTTP server with full configuration:

import { serve } from '@nextrush/adapter-node';

const server = await serve(app, {
  port: 3000,
  host: '0.0.0.0',
  timeout: 30000,
  keepAliveTimeout: 5000,
  onListen: ({ port, host }) => {
    console.log(`Server running at http://${host}:${port}`);
  },
  onError: (error) => {
    console.error('Server error:', error);
  },
});

// Later: graceful shutdown
await server.close();

Options

ServeOptions

PropertyTypeDescription
portnumber= 3000Port to listen on
hoststring= '0.0.0.0'Host to bind to
timeoutnumber= 30000Request timeout in milliseconds
keepAliveTimeoutnumber= 5000Keep-alive timeout in milliseconds
shutdownTimeoutnumber= 30000Graceful shutdown timeout in milliseconds. Forces closure if connections do not drain within this time.
onListen?(info: { port: number; host: string }) => voidCallback when server starts listening
onError?(error: Error) => voidCustom error handler for uncaught server errors
logger?LoggerLogger for adapter diagnostics. Defaults to app.logger.

Return Value

interface ServerInstance {
  server: Server; // Node.js http.Server
  port: number; // Listening port
  host: string; // Bound host
  address(): { port: number; host: string }; // Get address info
  close(): Promise<void>; // Graceful shutdown
}

listen()

Shorthand for common case with default logging:

import { listen } from '@nextrush/adapter-node';

await listen(app, 3000);
// Output: 🚀 NextRush listening on http://localhost:3000

createHandler()

Create a request handler for custom server setup.

Signature:

function createHandler(
  app: Application,
  options?: { logger?: Logger }
): (req: IncomingMessage, res: ServerResponse) => void;
import { createHandler } from '@nextrush/adapter-node';
import { createServer } from 'node:http';
import { createServer as createHttpsServer } from 'node:https';

const handler = createHandler(app);

// Use with http
const httpServer = createServer(handler);

// Use with https
const httpsServer = createHttpsServer(
  {
    key: privateKey,
    cert: certificate,
  },
  handler
);

HTTPS Support

Use createHandler() with Node.js https:

import { createApp } from '@nextrush/core';
import { createHandler } from '@nextrush/adapter-node';
import { createServer } from 'node:https';
import { readFileSync } from 'node:fs';

const app = createApp();
// ... configure app

const handler = createHandler(app);

const server = createServer(
  {
    key: readFileSync('private-key.pem'),
    cert: readFileSync('certificate.pem'),
  },
  handler
);

server.listen(443);

Graceful Shutdown

import { serve } from '@nextrush/adapter-node';

const server = await serve(app, { port: 3000 });

// Handle shutdown signals
async function shutdown() {
  console.log('Shutting down...');
  await server.close();
  console.log('Server closed');
  process.exit(0);
}

process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);

Accessing Raw Objects

Access Node.js IncomingMessage and ServerResponse when needed:

router.get('/raw', (ctx) => {
  const { req, res } = ctx.raw;

  // Access Node.js-specific properties
  const remoteAddress = req.socket.remoteAddress;
  const httpVersion = req.httpVersion;

  // Direct response manipulation (escape hatch)
  res.writeHead(200, { 'X-Custom': 'value' });
  res.end('Raw response');
});

Use Raw Objects Carefully

When using ctx.raw, you bypass NextRush's response handling. Make sure you don't mix raw and NextRush response methods.

Performance Tuning

Timeouts

await serve(app, {
  port: 3000,
  timeout: 60000, // 60s request timeout
  keepAliveTimeout: 10000, // 10s keep-alive
});

Cluster Mode

Scale across CPU cores:

import cluster from 'node:cluster';
import { cpus } from 'node:os';
import { createApp } from '@nextrush/core';
import { listen } from '@nextrush/adapter-node';

if (cluster.isPrimary) {
  const numCPUs = cpus().length;
  console.log(`Primary ${process.pid} starting ${numCPUs} workers`);

  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code) => {
    console.log(`Worker ${worker.process.pid} died (${code})`);
    cluster.fork(); // Replace dead worker
  });
} else {
  const app = createApp();
  // ... configure app

  await listen(app, 3000);
  console.log(`Worker ${process.pid} started`);
}

Complete Example

import { createApp } from '@nextrush/core';
import { createRouter } from '@nextrush/router';
import { serve } from '@nextrush/adapter-node';

const app = createApp({
  env: process.env.NODE_ENV as 'production' | 'development',
});

// Middleware
app.use(async (ctx) => {
  const start = Date.now();
  await ctx.next();
  console.log(`${ctx.method} ${ctx.path} - ${Date.now() - start}ms`);
});

// Error handler
app.setErrorHandler((error, ctx) => {
  console.error('Request error:', error);
  ctx.status = 500;
  ctx.json({ error: 'Internal Server Error' });
});

// Routes
const router = createRouter();

router.get('/health', (ctx) => {
  ctx.json({ status: 'healthy', runtime: 'node' });
});

router.get('/users/:id', (ctx) => {
  ctx.json({ id: ctx.params.id });
});

app.route('/', router);

// Start server
const server = await serve(app, {
  port: Number(process.env.PORT) || 3000,
  onListen: ({ port }) => {
    console.log(`🚀 Server running on http://localhost:${port}`);
  },
});

// Graceful shutdown
process.on('SIGTERM', async () => {
  await server.close();
  process.exit(0);
});

TypeScript Types

import type { ServeOptions, ServerInstance } from '@nextrush/adapter-node';

import type { Application } from '@nextrush/core';

import type { IncomingMessage, ServerResponse } from 'node:http';

See Also

On this page