DocTreen

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.json and 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.schemas with $ref dedup v1.11defineSchema lifts shared shapes into components.schemas; repeated anonymous objects auto-promote too
  • Per-route + top-level tags v1.11 — group operations the way you want, with descriptions and externalDocs
  • Callbacks + webhooks v1.11 — OpenAPI 3.1 callbacks (per-operation) and webhooks (document-level) wired in
  • Multi-example bodies and responses v1.11 — single value or named map, per-status examples for error responses
  • securitySchemes + per-route security v1.8 — declare auth schemes once, attach them automatically; the spec passes Redocly's security-defined rule cleanly
  • hidden: true per route v1.8 — keep internal endpoints serving traffic but invisible to docs / OpenAPI consumers
  • doctreen lint openapi CLI v1.11 — Spectral-lite linter for the exported (or any) OpenAPI 3.x doc; CI-ready exit codes
  • doctreen mock CLI v1.12 — serves a spec-driven fake API with CRUD short-circuits, latency / error injection, and optional Faker
  • doctreen codegen CLI 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: true and 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.jsPythonPHP
Packagedoctreen (npm)doctreen (PyPI)doctreen/doctreen (Packagist)
StatusStableAlpha — Flask end to end; FastAPI + Django/DRF; flows as pytest testsEarly — Laravel, phases 1–4
FrameworksExpress · Fastify · Hono · Koa · NestJSFlask · FastAPI · Django/DRFLaravel (Symfony, PSR-15 planned)
Validating schemasZodPydantic v2— (S:: builder is descriptive)
Descriptive schemass builders builderS:: 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

FeatureDocTreen@nestjs/swaggerScalar / Redocts-rest
Spec file required
Frameworks supported5 adaptersNestJS onlyAny (spec-only)Custom router
Zod schemas accepted directlyAll adaptersManualManual
Runtime request validationOne schemaclass-validator
Integration test runnerBuilt-in flows
Postman export
OpenAPI 3.1 exportBuilt-inRequiredPlugin
securitySchemes + per-route securityv1.8ManualSpec inputManual
Hide a route from docsPer route + patternsSpec edit
Schema drift detectionv1.10, CI-ready
OpenAPI linter (CLI)v1.11Via Spectral
Spec-driven mock serverv1.12Separate Prism
Setup time~5 min~30 min~1 hourRefactor 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 UI
  • GET /docs/openapi.json — OpenAPI 3.1 spec
  • POST /users with { "email": "nope" }422 with structured issues
  • npx doctreen drift report — shape mismatches vs live traffic
  • Postman collection download + LLM-friendly markdown for the route

Next up: Installation and Quick start.

On this page