Introduction
Code-first API docs, OpenAPI 3.1, integration tests, and runtime validation — one Zod schema per route.
DocTreen is a code-first API documentation library for Node.js. Define your route shape once with Zod (or DocTreen's own schema builder), and it generates an interactive docs UI, runnable integration flows, and Postman exports for Express, Fastify, Hono, Koa, and NestJS — no router rewrite, no separate spec file, no decorator boilerplate on every DTO field.
Why DocTreen
Most API doc tools force you to pick one of three trade-offs:
- Spec-first (OpenAPI YAML): write the spec, then keep it in sync with code. Spec drifts.
- Annotation-heavy (
@nestjs/swagger,swagger-jsdoc): decorate every property of every DTO. Boilerplate compounds. - Framework-locked (
ts-rest, Hono RPC): rewrite your router. Migration pain.
DocTreen sits next to your existing router. Pass a Zod schema to defineRoute
(or @DocRoute on NestJS) once, and you get:
- An interactive docs UI at
/docs— zero-dependency HTML, served by the same Node process - OpenAPI 3.1 export at
/docs/openapi.jsonand a one-click download button — drop the file into Scalar, Redoc, Swagger UI, or any spec-driven tool components.schemaswith$refdedup v1.11 —defineSchemalifts shared shapes intocomponents.schemas; repeated anonymous objects auto-promote too- Per-route + top-level
tagsv1.11 — group operations the way you want, with descriptions andexternalDocs - Callbacks + webhooks v1.11 — OpenAPI 3.1
callbacks(per-operation) andwebhooks(document-level) wired in - Multi-example bodies and responses v1.11 — single value or named map, per-status examples for error responses
securitySchemes+ per-routesecurityv1.8 — declare auth schemes once, attach them automatically; the spec passes Redocly'ssecurity-definedrule cleanlyhidden: trueper route v1.8 — keep internal endpoints serving traffic but invisible to docs / OpenAPI consumersdoctreen lint openapiCLI v1.11 — Spectral-lite linter for the exported (or any) OpenAPI 3.x doc; CI-ready exit codesdoctreen mockCLI v1.12 — serves a spec-driven fake API with CRUD short-circuits, latency / error injection, and optional Faker- Runnable integration flows with a CLI runner suitable for CI
- Postman Collection v2.1 export
- Runtime validation — opt in with
validate: trueand invalid requests are rejected with a structured 422 before they reach your handler. Same schema as the docs. - Schema Drift Detection — declared schemas are compared against real traffic on every adapter; sampled mismatches feed a UI tab,
/drift.json, hourly + 7-day buckets, an opinionated CLI (npx doctreen drift report --fail-on-mismatch), an optional reset endpoint, and a Redis-backedDriftStorereference for multi-replica deployments.
How DocTreen compares
| Feature | DocTreen | @nestjs/swagger | Scalar / Redoc | ts-rest |
|---|---|---|---|---|
| Spec file required | ||||
| Frameworks supported | 5 adapters | NestJS only | Any (spec-only) | Custom router |
| Zod schemas accepted directly | All adapters | Manual | Manual | |
| Runtime request validation | One schema | class-validator | ||
| Integration test runner | Built-in flows | |||
| Postman export | ||||
| OpenAPI 3.1 export | Built-in | Required | Plugin | |
| securitySchemes + per-route security | v1.8 | Manual | Spec input | Manual |
| Hide a route from docs | Per route + patterns | Spec edit | ||
| Schema drift detection | v1.10, CI-ready | |||
| OpenAPI linter (CLI) | v1.11 | Via Spectral | ||
| Spec-driven mock server | v1.12 | Separate Prism | ||
| Setup time | ~5 min | ~30 min | ~1 hour | Refactor router |
Quick taste
import express from 'express';
import { z } from 'zod';
import { defineRoute, expressAdapter } from 'doctreen/express';
const app = express();
app.use(express.json());
app.post('/users', defineRoute(
(req, res) => res.status(201).json({ id: 1, ...req.body }),
{
description: 'Create a user',
request: { body: z.object({ name: z.string(), email: z.string().email() }) },
response: z.object({ id: z.number(), name: z.string(), email: z.string() }),
errors: { 409: 'Email already in use' },
}
));
app.use(expressAdapter(app, {
meta: { title: 'My API', version: '1.0.0' },
validate: true,
}));
app.listen(3000);That single declaration drives:
GET /docs— interactive UIGET /docs/openapi.json— OpenAPI 3.1 specPOST /userswith{ "email": "nope" }—422with structured issuesnpx doctreen drift report— shape mismatches vs live traffic- Postman collection download + LLM-friendly markdown for the route
Next up: Installation and Quick start.