Handlers
Handlers are the HTTP implementations of OpenAPI operations in {service-name}-http packages. Types come from the adjacent {service-name}-spec package.
Use the express/add-handler workflow to add new handlers.
Route (or operation) means the OpenAPI specification. Handler means the Express implementation.
Group folders are the plural collection name (handlers/recipes/create.ts). Use that same folder under the spec routes/ and the SDK requests/. See API design.
Package structure and integration
See base for file layout, router factories, and how handlers are mounted. Use the express/add-handler workflow to add new handlers.
Each handler is registered on an Express router with createOperationScopedMiddleware and the operation's operationJsonSpec from @…-spec/operations/<operationId>.
Wrap every handler with createHandler so async errors reach Express error middleware.
Typing
Spec packages export request and response types keyed by operationId and status code. Handlers should use these types so mismatches with the spec fail at compile time. See @saflib/openapi.
OpenAPI validation runs in scoped middleware (express-openapi-validator, OpenAPI 3.1). For nullable fields, see API design — nullable fields.
Layering
A typical request passes through layers in this order:
- Global middleware — metrics, security headers, body parsing, CORS, etc. (Middleware)
- Service context — request-scoped
AsyncLocalStorage(database keys, clients, config) - Early auth gate — optional monolith-wide session check before product routers
- Scoped middleware — OpenAPI validation, CSRF, context, structured logging, per-operation auth tags
- Handler — maps domain results to HTTP responses
- Error middleware — 404 and centralized error formatting after all routes
The handler is the only layer that should construct successful HTTP responses for an operation. Middleware handles cross-cutting concerns (auth, validation, logging); handlers handle HTTP semantics for that operation.
Error handling
Handlers must account for every response status declared on the operation, except 401 (and other auth failures), which scoped auth middleware handles.
Do not wrap handlers in try/catch. Unsafe work should return errors per best practices; map those to the appropriate status and body. Uncaught exceptions are handled by error middleware as 5xx.
Handlers may signal errors in three ways:
throw createError(...)fromhttp-errorsnext(err)with anhttp-errorserror- Respond directly:
res.status(code).json({ ... } satisfies ResponseBodyType)
Most error responses should use the standard error object. Options 1 and 2 only work when the operation's error responses reference that schema. For a custom error shape, use option 3.
message is for debugging and logging — not for end-user copy. Frontends and SDKs should use HTTP status and the code field. Pass code in the third argument to createError:
createError(400, "This file type is not supported", {
code: "FILE_TYPE_NOT_SUPPORTED",
});