Scaling a Node.js API from a handful of endpoints to hundreds requires deliberate architectural choices from day one. TypeScript makes this tractable — but only when paired with the right project structure and runtime validation.
Feature-First Folder Layout
We organise every API using a feature-first structure. Each feature (users, orders, payments) gets its own directory containing its router, controller, service, and Zod schema. Related code stays co-located, onboarding is fast, and there is no cross-feature coupling.
src/
features/
users/
users.router.ts
users.controller.ts
users.service.ts
users.schema.ts
middleware/
lib/
app.tsType-Safe Request Validation with Zod
Every request body and query parameter is parsed through a Zod schema at the controller boundary before any business logic runs. This eliminates an entire class of runtime errors and gives accurate TypeScript types throughout the request lifecycle — no more casting unknown to anything.
Centralised Error Handling
A single Express error middleware handles every thrown error. We define a lightweight AppError class that carries an HTTP status code. Controllers simply throw — they never construct raw HTTP responses. Consistent error shapes across every endpoint come for free.
Auto-Generated OpenAPI Docs
We use zod-to-openapi to generate an OpenAPI 3.1 spec directly from our Zod schemas. The spec is served at /docs as a live Swagger UI — always in sync with the code, never written by hand.
Every API we ship is self-documented, fully type-safe, and structured so any engineer on the team can navigate it within minutes of cloning the repo.
