Blog
Mert Özen's blog posts on software development, backend, frontend, and DevOps.
Getting Started with Express 5: Routing, the Middleware Chain and What Changed from Express 4
Express 5 forwards errors from async handlers to the error middleware by itself, moves path syntax to path-to-regexp 8 and leaves req.body undefined when the body was not parsed. With real output: Express 4 crashing on the same error, old paths that now fail at startup, the three-parameter error handler trap, moving the Bookstore API to Express, a listen that silently swallows EADDRINUSE, and NODE_ENV=production not changing speed for a JSON API.
Building the Bookstore REST API with Express: Routers, Validation with zod, Async Error Handling and Problem Details
A solid REST API with Express separates its layers, validates every request with a zod schema and derives the type from it, turns errors into RFC 9457 Problem Details in one middleware, and keeps money as whole numbers of kuruş instead of floats. With real output: an order total of 1339.1999999999998, a float sum drifting by 5 kuruş over 20 million lines, validation returning six errors in one response, the same Problem Details contract as ASP.NET Core, paging, and z.toJSONSchema.
Getting an Express API Production-Ready: Configuration, Structured Logging with pino, Health Checks, Graceful Shutdown and Cluster
A production-ready Express API validates its configuration against a schema at startup, writes JSON logs with request ids and redacted secrets using pino, separates liveness from readiness, and on SIGTERM stops new connections and finishes open requests before closing the pool. With real output: an export cut at 22 MB instead of 98 MB, the full file after the fix and a controlled cut on timeout, 503 while draining, PID 1's 10-second wait and a 4-worker cluster.
Security in Express: helmet, CORS, Rate Limiting, JWT Validation and Input Limits
Securing an Express API means adding security headers with helmet, restricting CORS to an origin allow-list, applying a rate limit with trust proxy set correctly, validating JWTs with jose against a pinned algorithm, issuer, audience and lifetime, and checking ownership on every order access. With real output: a limit bypassed through X-Forwarded-For, a CORS setting that reflects every origin, 401 for alg none and algorithm confusion, 404 against BOLA, and prototype pollution.