Node.js
Deploy a NextRush app to Node.js using @nextrush/adapter-node.
@nextrush/adapter-node connects a NextRush Application to Node's built-in http.createServer.
It's the default target — the nextrush meta package re-exports it directly, so
import { listen } from 'nextrush' already runs on Node.js with no extra install.
Minimal deployment
import { createApp, createRouter, listen } from 'nextrush';
const app = createApp();
const router = createRouter();
router.get('/health', (ctx) => ctx.json({ status: 'ok' }));
app.route('/', router);
await listen(app, Number(process.env.PORT ?? 8080));node dist/index.jslisten() is a thin wrapper: it calls serve() and logs a startup line through
app.logger. serve() itself calls app.ready() (boots any registered extensions), creates the
Node.js server, and resolves once it's bound.
No log without a logger
createApp()'s default logger is a no-op — listen()'s startup message and any
ctx/app.logger calls produce no output until you attach a real logger (e.g.
@nextrush/logger) via createApp({ logger }). Don't assume you'll see console output in
production without configuring one.
serve() options that matter in production
@nextrush/adapter-node's serve(app, options) accepts:
ServeOptions (@nextrush/adapter-node)
| Property | Type | Description |
|---|---|---|
port | number= 8080 | Port to listen on. |
host | string= '0.0.0.0' | Host/interface to bind to. |
timeout | number= 30000 | Per-request socket timeout in ms (`server.timeout`). |
keepAliveTimeout | number= 5000 | Keep-alive timeout in ms (`server.keepAliveTimeout`). |
shutdownTimeout | number= 30000 | Grace period to drain in-flight requests during `close()` before force-closing. |
onListen? | (info: { port: number; host: string }) => void | Called once the server is bound. |
onError? | (error: Error) => void | Custom handler for uncaught server-level errors. |
import { createApp, createRouter } from 'nextrush';
import { serve } from '@nextrush/adapter-node';
const app = createApp();
const router = createRouter();
router.get('/health', (ctx) => ctx.json({ status: 'ok' }));
app.route('/', router);
const server = await serve(app, {
port: Number(process.env.PORT ?? 8080),
host: '0.0.0.0',
onListen: ({ port, host }) => {
console.log(`listening on ${host}:${port}`);
},
});
process.on('SIGTERM', async () => {
await server.close(); // drains in-flight requests, then tears down extensions
process.exit(0);
});server.close() stops accepting new connections, waits up to shutdownTimeout for in-flight
requests to drain, then calls app.close() for extension teardown. See
Reliability for the full graceful-shutdown pattern, including
which signals to handle and how health checks should react during drain.
Behind a reverse proxy
If NextRush sits behind nginx, an ALB, or a platform load balancer, enable proxy: true so
ctx.ip reads X-Forwarded-For from a trusted upstream instead of the raw socket address:
const app = createApp({ proxy: true });Only trust proxies you control
proxy: true makes NextRush trust client-supplied X-Forwarded-For headers. Enabling it
behind an untrusted or misconfigured proxy lets clients spoof their own IP — only set this
when a proxy you control is guaranteed to overwrite that header on every request.
Running under a process manager
A raw node dist/index.js process won't restart itself after a crash and won't survive a
reboot. Two common approaches:
[Unit]
Description=NextRush application
After=network.target
[Service]
Type=simple
User=nextrush
WorkingDirectory=/opt/nextrush-app
Environment=NODE_ENV=production
Environment=PORT=8080
ExecStart=/usr/bin/node dist/index.js
Restart=on-failure
RestartSec=2
# systemd sends SIGTERM by default — matches the shutdown handler above.
KillSignal=SIGTERM
TimeoutStopSec=35
[Install]
WantedBy=multi-user.targetsudo systemctl enable --now nextrush-app
sudo systemctl status nextrush-apppm2 start dist/index.js --name nextrush-app
pm2 save
pm2 startup # generates a boot script for your init systemSee Scaling — process managers for when to use PM2's cluster mode versus letting a container orchestrator manage replicas instead — don't run both at once.
On a container platform, skip the process manager
If you're deploying to Kubernetes, ECS, or Cloud Run, the orchestrator already restarts crashed containers and manages replica count. Run one process per container (see Docker) instead of adding PM2/systemd inside it.
Environment setup
# .env (never commit this file)
NODE_ENV=production
PORT=8080const PORT = Number(process.env.PORT ?? 8080);NODE_ENV=production is read by createApp() to decide default error-response verbosity —
see Configuration for the full list of environment-driven
defaults and how to externalize secrets.
Requirements
- Node.js >= 22.0.0 (declared in
@nextrush/adapter-node'spackage.jsonenginesfield)
Related
- Docker — containerize this same app
- Reliability — graceful shutdown, health checks, timeouts
- Configuration — environment variables and secrets