TypeScript with Node.js
Type Node.js services with correct module settings, environment variables, and framework patterns.
Node ESM and TypeScript Configuration
Node ESM projects use `"module": "NodeNext"` and `"moduleResolution": "NodeNext"` with `"type": "module"` in package.json. Import paths may require `.js` extensions in source TS files mapping to emitted files — follow Node ESM rules.
Dual CJS/ESM packages are complex — new services should pick ESM unless integrating with CJS-only dependencies. Use `tsx` or `ts-node` with ESM loader for development.
- NodeNext module settings for ESM
- Extension requirements in imports
- ESM-first for new services
import { createServer } from './server.js'; // .js in TS source for Node ESMTyping Environment Variables
Validate env vars at startup with Zod or envalid — infer TypeScript types from schemas rather than casting `process.env`. Centralize env access in one module that throws on missing required vars.
Augment Node types for known env keys via declaration merging on `ProcessEnv` only when validation module is guaranteed to run first — schema validation is safer than blind augmentation.
- Validate env at startup with schema
- Centralize process.env access
- Infer types from validation schema
const envSchema = z.object({ DATABASE_URL: z.string().url() });
export const env = envSchema.parse(process.env);Express, Fastify, and Route Handlers
Express handlers type Request, Response, and NextFunction — use generics on Request for typed params and body: `Request<{ id: string }, {}, CreateBody>`. Fastify schema declarations generate types automatically.
Next.js Route Handlers export typed functions `(request: NextRequest) => NextResponse`. Share Zod schemas between client forms and server validation for end-to-end type alignment.
- Generic Request types in Express
- Fastify schema-driven types
- Shared Zod schemas client/server
Database and ORM Typing
Prisma generates types from schema — regenerate after migrations. Drizzle infers types from table definitions. Raw SQL queries return unknown rows unless typed with explicit interfaces or query builders.
Avoid `any` on database rows — define DTO types at repository boundaries and map ORM entities to domain types when layers diverge.
- Regenerate ORM types after schema changes
- DTO types at repository boundaries
- Type raw SQL results explicitly
Testing and Mocking in Node
Use `vitest` or `jest` with `ts-jest`/`esbuild-jest`. Mock modules with typed `vi.mocked()` or `jest.mocked()`. For integration tests, type supertest responses or use contract tests against OpenAPI specs.
`@types/node` versions should match runtime Node major version. Enable `types: ["node"]` explicitly in tsconfig when DOM types pollute server-only projects.
- Typed mocks with mocked helpers
- Match @types/node to runtime version
- Exclude DOM types in server tsconfig