Search docs

Jump between documentation pages.

Node.js

The Node adapter runs your REST API on the built-in node:httpserver. It's the default target for containers, VMs, and any Node-based PaaS (Heroku, Railway, Render, Fly.io). Use it when you control the process — long-lived, observable, and easy to debug.

When to choose Node

  • You deploy to a container, VM, or Node PaaS (no per-request billing).
  • You need node:* modules (filesystem, child processes, native addons).
  • You want the broadest npm package compatibility.

Scaffold

The fastest way to start is the node-basic template. It ships with TypeScript, pnpm workspaces, a /healthzroute, graceful shutdown, and Hey API codegen wired up.

bash
pnpm create daloy@latest my-api --template node-basic
cd my-api
pnpm dev    # hot-reload via daloy dev

Install

Requires Node.js 24 LTS or newer. The adapter ships with @daloyjs/core; no extra dependency.

bash
pnpm add @daloyjs/core

Minimal server

ts
// src/server.ts
import { serve } from "@daloyjs/core/node";
import { app } from "./app.js";

const { port, close } = serve(app, {
  port: Number(process.env.PORT ?? 3000),
  hostname: "0.0.0.0",
  connectionTimeoutMs: 30_000,
  shutdownTimeoutMs: 10_000,
  handleSignals: true,       // SIGTERM / SIGINT trigger graceful shutdown
  maxHeaderBytes: 16 * 1024, // 16 KiB cap (default)
  trustProxy: false,         // set true only behind a trusted reverse proxy
});

console.log(`listening on :${port}`);

// later — drain in-flight requests, then close
await close();

What the adapter wires for you

  • requestTimeout, headersTimeout, and keepAliveTimeout set to safe production values.
  • SIGTERM / SIGINT handlers that call server.close() followed by server.closeAllConnections() after shutdownTimeoutMs — the pattern that became stable in Node 18.2 and is recommended on Node 24+.
  • When trustProxy: true, the adapter reads x-forwarded-proto and x-forwarded-host when constructing the request URL. Leave it off unless TLS is terminated at a known proxy you control.

Behind a load balancer

Two rules to avoid the classic 502/504 race:

  • Make your load balancer's idle timeout greater than DaloyJS's requestTimeoutMs.
  • Make DaloyJS's keepAliveTimeout greaterthan the load balancer's — the Node adapter does this for you.

Dockerfile

docker
FROM node:24-slim AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile --prod

FROM node:24-slim AS build
WORKDIR /app
COPY . .
RUN corepack enable && pnpm install --frozen-lockfile && pnpm build

FROM gcr.io/distroless/nodejs24-debian12
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
USER nonroot
EXPOSE 3000
CMD ["dist/server.js"]

Gotchas

  • Don't put process.exit() in a SIGTERM handler — let close() drain. The adapter handles the hard kill after the timeout.
  • Set hostname: "0.0.0.0" in containers; Node binds to localhostby default and that's invisible from outside the container.

See also