Community content. Review instructions before giving them to an AI agent — treat modules like open-source code.
Node Express Conventions
Express API conventions: router/controller/service layering, centralized error middleware, async handler wrapping, edge validation, env-based config, graceful shutdown, and keeping routes free of business logic.
Mby @markdownersPublished August 21, 2026 · ~3 min read
0 downloads · Used by 0 stacks
Layer every Express app the same way — router defines the URL, controller translates HTTP to a call, service holds the logic — and let a single centralized error handler be the only place that turns a thrown error into an HTTP response.
Router / controller / service layering
- Keep route files (
router.get(...)) responsible only for wiring a path and HTTP method to a controller function — no business logic, no direct database calls in a route file. - Keep controllers responsible for translating the HTTP request into a call to a service function and the service's return value into an HTTP response (status code, JSON shape) — a controller should not contain domain logic or direct query building.
- Keep services responsible for the actual business logic and data access, with no knowledge of
req/res— a service function should be callable from a controller, a queue worker, or a test with the same signature. - Never let a route handler directly query the database or call a third-party API inline "just this once" — that logic becomes unreusable and untestable the moment it's needed elsewhere.
Centralized error handling
- Register exactly one error-handling middleware (four-argument signature) at the end of the middleware chain, and have every other layer
throwornext(err)rather than manually formatting an error response inline. - Distinguish operational errors (expected: not-found, validation failure, unauthorized) from programmer errors (bugs) with a typed/known error class, and map known error types to their correct status codes in the central handler — don't let every error fall through to a generic 500.
- Never leak stack traces or internal error details to the client in production; log the full error server-side and return a minimal, safe message to the caller.
Async handler wrapping
- Wrap every async route handler (or use a framework version that does this natively) so a rejected promise is forwarded to
next(err)automatically — an unwrapped async handler that throws will hang the request or crash the process instead of hitting the error middleware. - Never mix callback-style and promise-style error handling in the same handler; pick async/await consistently and let the wrapper handle rejections.
Validation at the edge
- Validate every request's body, query, and params against a schema at the edge (middleware or the top of the controller) before any of it reaches a service — services should be able to trust the shape of their inputs.
- Return a 400 with a clear, structured validation error on failure, and stop processing immediately — never let a partially-invalid payload continue deeper into the request lifecycle.
- Re-validate on the server even when the client also validates — client-side validation is UX only, never a security or correctness boundary.
Configuration from environment
- Load all configuration (ports, database URLs, secrets, feature flags) from environment variables through a single validated config module read once at startup, not scattered
process.env.Xreads throughout the codebase. - Fail fast at startup if a required environment variable is missing, rather than discovering it when the first request that needs it arrives.
- Never commit real secrets to a
.envfile in version control; commit a.env.examplewith variable names and placeholder values instead.
Graceful shutdown
- Listen for termination signals (
SIGTERM,SIGINT) and stop accepting new connections, finish in-flight requests, close database/queue connections, then exit — an abrupt process kill drops in-flight requests and can leave connections in a bad state. - Set a hard timeout on graceful shutdown (e.g., a few seconds) so a hung connection can't block deployment/restart indefinitely; force-exit after the timeout.
Keep routes free of business logic
- Treat "can I unit-test this without spinning up an HTTP server" as the litmus test for whether logic has leaked into the router/controller layer — if a piece of logic requires mocking
req/resto test, it belongs in a service instead. - Resist adding "just a quick if-check" inline in a route for a one-off business rule; add it to the relevant service function so the rule lives with the rest of that domain's logic.
Badge
Link back to this module from your own README.
[](https://markdowners.com/m/markdowners/node-express-conventions)Discussions about this module
No discussions about this module yet.
Start a discussion
Comments (0)
Sign in to comment. Sign in
No comments yet. Be the first to add one.