Why validate at all?
The classic argument against runtime validation is performance — "just don't, it's slow." This is outdated. Modern JSON Schema validators (ajv, zod, typebox) run in microseconds per request. The cost is negligible compared to a database query or an external API call. The benefit — catching contract violations at the boundary before they propagate — is enormous for debugging and reliability.
The approach: OpenAPI spec as the single source of truth
Instead of writing validation code by hand, I define the API contract once in an OpenAPI 3.1 spec and let tooling derive validators from it. The spec is used for: - Runtime request/response validation (ajv) - TypeScript type generation (openapi-typescript) - Documentation (Swagger UI) - Client SDK generation (openapi-generator) One spec, four consumers. Change the spec and everything stays in sync.
Wiring it into Express
The key insight is to precompile the validators at startup, not per-request. Load the spec once, walk the paths object, compile an ajv validator for each request body schema and response schema, and store them in a lookup map keyed by METHOD /path. The middleware then just does a lookup in O(1) and runs a precompiled validator. There's no schema parsing at request time — that work is already done.
Handling validation errors gracefully
When validation fails, return a structured 400 response with the ajv errors array. I wrap ajv errors into a consistent format:
json
{
"error": "ValidationError",
"issues": [
{ "path": "/body/userId", "message": "must be string" }
]
}
This tells clients exactly what's wrong without leaking internal stack traces.
Response validation in staging
Validating responses (not just requests) catches bugs in your own code — when your handler returns a shape that doesn't match the spec. I run response validation in staging and log violations without rejecting the response, so I can fix latent bugs without impacting production. Once the spec drift is cleaned up, I flip to strict mode that returns 500 on spec violation in all environments.