All Node.js templates

TypeScript Node.js Backend

Production Node.js in TypeScript: build, run, and deploy patterns.

DevZone Tools1,640 copiesUpdated Mar 6, 2026TypeScriptNode.js
# CLAUDE.md — TypeScript Node.js Backend

## Stack defaults

- Node 20+ (LTS). Pin major version in `engines`.
- Type-stripping: **tsx** for dev, **tsc** for production builds (`outDir: dist`).
- ES modules only (`"type": "module"` in `package.json`). No CommonJS in new code.
- Package manager: pnpm or npm. Lock file is committed.

## Project layout

```
src/
  app.ts          # express/fastify/hono app factory
  server.ts       # bootstrap (reads env, starts listening)
  routes/
  services/
  repositories/
  lib/
  types/
test/
dist/             # gitignored
tsconfig.json
```

- Separate `app.ts` (factory) from `server.ts` (entry point) so tests can import the app without binding ports.

## tsconfig

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "verbatimModuleSyntax": true,
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src/**/*"]
}
```

- ES module imports use the **full filename** including `.js` (compiled) or rely on `tsx` which handles `.ts` extensions.

## Configuration

- One `env.ts` with Zod parsing at boot. Crash early on invalid env.
- Read `NODE_ENV` once. Branch in `env.ts`, not throughout the app.
- Don't use dotenv at the source — let the orchestrator inject env. `dotenv` only in dev.

## Logging

- **pino** — fast, structured, the right answer for production.
- Log JSON to stdout. Let the orchestrator collect.
- One logger per module: `const log = pino({ name: "users" })`.

## Errors

- Custom errors extend `Error` with a `name` and a `code`:
  ```ts
  export class NotFoundError extends Error {
    name = "NotFoundError"
    constructor(public resource: string, public id: string) { super(`${resource} ${id} not found`) }
  }
  ```
- Map domain errors to HTTP statuses at the framework layer. Don't `throw new HttpError(404)` from a service.

## Process lifecycle

- Handle `SIGTERM`. Drain in-flight requests, close DB pools, exit clean.
- Don't `process.exit(0)` from a request handler.
- Uncaught exceptions / unhandled rejections: log + exit. Let the orchestrator restart.

## Testing

- **Vitest** or **Node's built-in test runner**. Fast and minimal.
- Unit tests for services. Integration tests for routes against a real DB.
- Snapshot tests for response shapes — they're cheap insurance.

## Don't

- Don't use `require()` in TypeScript. Use `import`.
- Don't ship `console.log` in production code. Use the structured logger.
- Don't put SQL in route handlers. Repository → service → route.
- Don't depend on Node-specific globals (`process`, `Buffer`) in code shared with the browser.

Other Node.js templates