Building a Production REST API with Node.js and Express in 2026
Node.jsBackendAPI

Building a Production REST API with Node.js and Express in 2026

Layered architecture, validation, error handling, auth, rate limiting, observability — the patterns I use to ship Node.js + Express APIs that don't fall over in production.

HJ
Hassan Javed
April 2026
12 min read

Why Express, in 2026

I keep getting asked: "Express? Still? Why not Hono / Fastify / Bun?"

Express is still the most common Node.js HTTP framework I see in production codebases, by a wide margin. It's not the fastest, it's not the most modern, but it's stable, mature, and every Node engineer knows it. For a senior who needs to onboard a junior in two days, Express wins.

That said, I'll mention where Fastify or Hono make sense at the end. The patterns in this post apply to all three; Express is just the example.

The architecture I keep coming back to

Five layers, each with one responsibility:

▸routes/ — HTTP layer (Express routers, validation)
▸controllers/ — Glue (HTTP → service)
▸services/ — Business logic (no HTTP, no DB-specific code)
▸repositories/ — Data layer (DB queries only)
▸lib/ — Shared utilities (logger, config, errors)

A request flows: route → controller → service → repository → DB → service → controller → response.

The reason for this separation is testability. Services don't know about HTTP, so you can unit-test business logic without booting Express. Repositories don't know about services, so you can swap MongoDB for PostgreSQL without touching business logic.

Validation: at the boundary, not throughout

Bad code I see all the time has validation scattered across the service. Better: validate at the route boundary with Zod, so your service receives already-validated data and stays clean.

I use Zod everywhere now. Joi was the standard for years; Zod is faster, has better TypeScript inference, and the schema syntax is more readable.

Error handling that doesn't suck

The Express default — a single next(err) and a global handler — is fine but easy to misuse. The pattern:

1.Custom error classes for known cases (HttpError, NotFoundError, ValidationError)
2.Throw them anywhere — if (!user) throw new NotFoundError("User")
3.Single error middleware at the bottom that translates known errors to HTTP responses and logs unknown errors

The benefit: known errors have clear codes. Unknown errors get logged with context and a generic message goes back to the client. No leaking stack traces in production.

Authentication: JWT with refresh, not session cookies

For a stateless REST API, JWT with a short-lived access token (15 min) and a long-lived refresh token (7 days) stored in an HttpOnly cookie is the pattern I default to.

▸Access token: signed JWT, 15-minute expiry, sent in Authorization: Bearer header
▸Refresh token: random opaque string, stored in DB, sent in HttpOnly cookie
▸Refresh endpoint: validates the refresh token against DB, issues a new access token, optionally rotates the refresh token

When NOT to use this: if you only need browser clients on a single domain, a simple HttpOnly session cookie is simpler and more secure (no JWT footguns). JWT shines when you have multiple clients (web + mobile + third-party API consumers).

Rate limiting: at multiple layers

A single layer of rate limiting is brittle. I use three:

1.At the edge (Cloudflare or your CDN): per-IP basic protection against floods
2.At the app (express-rate-limit or rate-limit-redis): per-user, per-route limits
3.At sensitive operations (login, password reset, payment): aggressive limits with progressive delays

Don't roll your own — express-rate-limit with a Redis store handles 99% of cases.

Observability: logs, metrics, traces

The minimum viable observability stack for a Node API:

1.Structured logs with Pino, going to stdout. Don't log to files in containerized deployments.
2.Metrics with prom-client exposing /metrics. Counters for requests by route + status, histograms for latency.
3.Traces with OpenTelemetry. The auto-instrumentation for Express + your DB driver gets you 80% of the value with one initialization line.

Where to ship the data: Grafana Cloud (free tier is enough for most projects), Datadog (expensive but excellent), or Honeycomb (best for tracing).

The DB layer

For new projects in 2026 I'm using:

▸PostgreSQL + Drizzle ORM for SaaS apps where I want SQL and types
▸MongoDB + native driver for projects with flexible schemas (a lot of Web3 indexing)
▸Redis for caching, rate limiting, sessions

Mongoose is fine but Drizzle's type inference is better than Mongoose's schemas, and for SQL-relational data, Postgres is the right answer.

When to reach for Fastify or Hono instead

▸Fastify: if you're benchmarking and Express's overhead matters (rare — most APIs are I/O-bound by DB calls). Fastify is 2-3x faster than Express for tight benchmarks.
▸Hono: if you're deploying to edge runtimes (Cloudflare Workers, Vercel Edge, Deno Deploy). Hono is built for this and Express isn't.
▸Next.js Route Handlers: if you don't need a separate API service. App Router's route handlers cover what most SaaS APIs need.

What I ship for clients

A typical Node.js + Express API engagement looks like:

▸Express + TypeScript
▸Zod for validation
▸Pino for logging
▸prom-client + OpenTelemetry for observability
▸PostgreSQL + Drizzle (or MongoDB native, depending on schema needs)
▸JWT auth with refresh tokens
▸Rate limiting at app + sensitive-route levels
▸Dockerfile + GitHub Actions CI for deployment
▸README with setup, env vars, runbook

If you're starting a new Node.js + Express project and want a senior to architect it, reach out — backend engagements are part of my core service offering.

TL;DR

▸Layered architecture: route → controller → service → repository
▸Validate at the boundary with Zod, throw custom HttpError classes, single global error middleware
▸JWT + refresh tokens for multi-client APIs; HttpOnly session cookie for single-client
▸Rate limit at edge + app + sensitive operations
▸Pino + prom-client + OpenTelemetry for observability
▸Postgres + Drizzle or MongoDB native for the DB
Related Reads

You might also like