Introduction
Code-first API docs, OpenAPI 3.1, integration tests, and runtime validation — one schema per route, in Node.js, Python, or PHP.
DocTreen is a code-first API documentation library for Node.js, Python, and PHP. Define your route shape once — with Zod, Pydantic, or DocTreen's own schema builder — and it generates an interactive docs UI, OpenAPI 3.1 export, runnable integration flows, and runtime validation for Express, Fastify, Hono, Koa, NestJS, Flask, and Laravel — 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,drf-spectacular): decorate every property of every DTO. Boilerplate compounds. - Framework-locked (
ts-rest, Hono RPC, flask-smorest): rewrite your router. Migration pain.
DocTreen sits next to your existing router. Pass a schema to defineRoute
(@DocRoute on NestJS, @define_route on Flask, ->doc() on Laravel) once,
and you get:
- An interactive docs UI at
/docs— zero-dependency HTML, served by the same process, identical in every language (the Python and PHP packages ship the exact UI assets the npm package builds) - 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. The Python and PHP exporters are verified against the Node exporter by a shared conformance suite. 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 Fakerdoctreen codegenCLI v1.13 — emits strict typed declarations and a zero-dependency typed client from your OpenAPI doc (TypeScript on Node, readonly DTOs + client on PHP)- 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; sampled mismatches feed a UI tab,
/drift.json, hourly + 7-day buckets, and a CI-ready CLI.
One library, three languages
| Node.js | Python | PHP | |
|---|---|---|---|
| Package | doctreen (npm) | doctreen (PyPI) | doctreen/doctreen (Packagist) |
| Status | Stable | Alpha — Flask end to end; FastAPI + Django/DRF; flows as pytest tests | Early — Laravel, phases 1–4 |
| Frameworks | Express · Fastify · Hono · Koa · NestJS | Flask · FastAPI · Django/DRF | Laravel (Symfony, PSR-15 planned) |
| Validating schemas | Zod | Pydantic v2 | — (S:: builder is descriptive) |
| Descriptive schemas | s builder | s builder | S:: builder |
All three share one schema model, one config shape, one UI, and one OpenAPI output — the Python and PHP exporters are held byte-identical to Node's by a shared conformance suite that runs on every push. If your stack spans languages, you get one contract surface across all of it: same drift format, same flow JSON, same spec.
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);from flask import Flask
from pydantic import BaseModel
from doctreen.adapters.flask import define_route, flask_adapter
app = Flask(__name__)
class User(BaseModel):
id: int
name: str
email: str
@app.post("/users")
@define_route(
description="Create a user",
request={"body": User},
response=User,
errors={409: "Email already in use"},
)
def create_user():
return {"id": 1, "name": "Ada", "email": "ada@example.com"}, 201
app.register_blueprint(flask_adapter(app, {
"meta": {"title": "My API", "version": "1.0.0"},
"validate": True,
}))use Doctreen\Schema\S;
Route::post('/users', [UserController::class, 'store'])->doc([
'description' => 'Create a user',
'request' => ['body' => S::object(['name' => S::string(), 'email' => S::string()])],
'response' => S::object(['id' => S::number(), 'name' => S::string(), 'email' => S::string()]),
'errors' => [409 => 'Email already in use'],
]);
// Docs UI + OpenAPI + validation mount automatically — the Laravel service
// provider is auto-discovered; configure via config/doctreen.php.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.