TypeScript Node.js Backend
Production Node.js in TypeScript: build, run, and deploy patterns.
# 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
Node.js + Express + TypeScript
Express in TypeScript: middleware, error handling, validation, structure.
Node.js + Fastify
Fastify with schemas, plugins, and high-performance JSON handling.
Node.js + Prisma + Postgres
Prisma ORM patterns: schema design, migrations, raw queries, transactions.
Node.js + NestJS
NestJS modules, providers, and the dependency-injection conventions.
Node.js Microservice Patterns
Service boundaries, event-driven communication, retries, idempotency.