DocTreen

Quick start

Mount the docs UI on your existing app in under five minutes.

Pick your framework. Each example mounts the docs UI at /docs next to your existing router — no rewrite required.

const express = require('express');
const { expressAdapter } = require('doctreen/express');

const app = express();
app.use(express.json());

app.get('/users',  (req, res) => res.json([]));
app.post('/users', (req, res) => res.status(201).json({ id: 1 }));

// Mount after your routes
app.use(expressAdapter(app, {
  meta: { title: 'My API', version: '1.0.0' },
}));

app.listen(3000, () => console.log('Docs at http://localhost:3000/docs'));
const fastify = require('fastify')();
const { fastifyAdapter } = require('doctreen/fastify');

// Call BEFORE registering routes — uses the onRoute hook
fastifyAdapter(fastify, {
  meta: { title: 'My API', version: '1.0.0' },
});

fastify.get('/users', async (req, reply) => reply.send([]));

fastify.listen({ port: 3000 });
// Hono v4 is ESM-only — run with: npx tsx hono-app.js
import { Hono } from 'hono';
import { serve } from '@hono/node-server';
import { honoAdapter } from 'doctreen/hono';

const app = new Hono();

app.get('/users', (c) => c.json([]));

// Can be called before or after routes
honoAdapter(app, { meta: { title: 'My API', version: '1.0.0' } });

serve({ fetch: app.fetch, port: 3000 });
const Koa    = require('koa');
const Router = require('@koa/router');
const { koaAdapter } = require('doctreen/koa');

const app    = new Koa();
const router = new Router();

router.get('/users', (ctx) => { ctx.body = []; });

// Can be called before or after routes
koaAdapter(router, { meta: { title: 'My API', version: '1.0.0' } });

app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000);
import 'reflect-metadata';
import { NestFactory } from '@nestjs/core';
import { nestAdapter } from 'doctreen/nest';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Call after NestFactory.create(), before app.listen()
  nestAdapter(app, {
    meta: { title: 'My API', version: '1.0.0' },
  });

  await app.listen(3000);
  console.log('Docs at http://localhost:3000/docs');
}
bootstrap();

Then annotate your controller methods with @DocRoute — see NestJS adapter.

from flask import Flask
from doctreen.adapters.flask import flask_adapter

app = Flask(__name__)


@app.get("/users")
def list_users():
    return []


# Mount anywhere — routes registered later are still picked up.
app.register_blueprint(flask_adapter(app, {
    "meta": {"title": "My API", "version": "1.0.0"},
}))

# flask run  →  docs at http://localhost:5000/docs

Then annotate your views with @define_route — see Flask adapter.

from fastapi import FastAPI
from doctreen.adapters.fastapi import mount_doctreen

app = FastAPI(title="My API", version="1.0.0")


@app.get("/users")
def list_users():
    return []


# Reads app.openapi() — FastAPI's own /docs stays untouched.
mount_doctreen(app, {"drift": {"enabled": True}})

# uvicorn main:app  →  DocTreen UI at http://localhost:8000/doctreen

FastAPI keeps its own docs and validation; DocTreen adds drift detection and the shared UI — see FastAPI adapter.

# urls.py
from django.urls import include, path
from doctreen.adapters.django import doctreen_urls

urlpatterns = [
    path("api/", include(router.urls)),
    *doctreen_urls({"meta": {"title": "My API", "version": "1.0.0"}}),
]

# settings.py — only needed for drift sampling
MIDDLEWARE = [..., "doctreen.adapters.django.DocTreenMiddleware"]

# python manage.py runserver  →  docs at http://localhost:8000/docs

Schemas come from your DRF serializers — see Django / DRF adapter.

// Nothing to mount: the service provider is auto-discovered by Composer,
// and GET /docs + GET /docs/openapi.json are registered automatically.

// Optionally publish the config to customise title, path, validation, drift:
//   php artisan vendor:publish --tag=doctreen-config

// routes/api.php — document a route with the ->doc() macro:
use Doctreen\Schema\S;

Route::get('/users', [UserController::class, 'index'])->doc([
    'description' => 'List users',
    'response' => S::array(S::object(['id' => S::number(), 'name' => S::string()])),
]);

// php artisan serve  →  docs at http://localhost:8000/docs

See the Laravel adapter for validation, drift, and the CLI.

Visit the configured docsPath (default: /docs) to see your documentation.

Next steps

On this page