Features
Runtime validation
Reject invalid requests with a structured 422 — same Zod schema as the docs.
The same Zod schema you declared for documentation can validate every incoming
request. Enable it once at the adapter level and DocTreen runs safeParseAsync
against request.body and request.query before your handler executes.
Invalid requests are rejected with a structured 422 response.
const express = require('express');
const { z } = require('zod');
const { expressAdapter, defineRoute } = require('doctreen/express');
const app = express();
app.use(express.json());
app.post('/users', defineRoute(
(req, res) => res.status(201).json({ id: 1, ...req.body }),
{
request: { body: z.object({ name: z.string().min(2), email: z.string().email() }) },
}
));
// Turn validation on for every Zod-declared route on this app
app.use(expressAdapter(app, { validate: true }));Sending { "email": "nope" } to POST /users now returns:
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"error": "validation_failed",
"issues": [
{ "path": "body.name", "message": "Required", "code": "invalid_type" },
{ "path": "body.email", "message": "Invalid email", "code": "invalid_string" }
]
}Properties
- Opt-in. Default is off; you set
validate: trueonce per adapter. Upgrading from v1.5 cannot suddenly start rejecting requests. - Per-route override. Pass
validate: falsetodefineRoute(or@DocRoute) to skip validation on a specific route while keeping the docs entry. Passvalidate: trueto enable validation on one route when the adapter default is off. - Async refinements work — internally uses
safeParseAsync, so.refine(async ...)and pipelines are honoured. - Zod only. Schemas built with the
s.*helper are descriptive shapes, not parsers, so they cannot be used to validate. Mixed routes (some Zod, somes.*) work fine — only the Zod ones validate, others pass through. - All five adapters. Express, Fastify, Hono, Koa, and NestJS all support
validate: true. Hono and Koa require the adapter to be called before routes (their middleware does not retro-apply); Express, Fastify, and NestJS work regardless of order.
If you previously hand-rolled a NestJS pipe or an Express middleware that
ran zodSchema.parse(req.body) for every endpoint, this replaces it.