DocTreen
Reference

TypeScript

Declaration files shipped alongside the JavaScript — no @types/doctreen needed.

DocTreen ships declaration files alongside the JavaScript. No separate @types/doctreen package is needed.

Express

import express from 'express';
import { expressAdapter, defineRoute, RouteSchemas } from 'doctreen/express';
import { s } from 'doctreen';

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

app.post('/users', defineRoute<{ name: string }, never, { id: number; name: string }>(
  (req, res) => res.status(201).json({ id: 1, name: req.body.name }),
  {
    description: 'Create a user',
    request:  { body: s.object({ name: s.string() }) },
    response: s.object({ id: s.number(), name: s.string() }),
    errors:   { 409: 'Email already in use' },
  }
));

app.use(expressAdapter(app, { meta: { title: 'My API', version: '1.0.0' } }));
app.listen(3000);

Fastify

import Fastify from 'fastify';
import { fastifyAdapter } from 'doctreen/fastify';

const fastify = Fastify();
fastifyAdapter(fastify, { meta: { title: 'My API', version: '1.0.0' } });

fastify.get('/users', async (req, reply) => reply.send([]));
fastify.listen({ port: 3000 });

Hono

import { Hono } from 'hono';
import { honoAdapter } from 'doctreen/hono';

const app = new Hono();
honoAdapter(app, { meta: { title: 'My API', version: '1.0.0' } });

Koa

import Koa from 'koa';
import Router from '@koa/router';
import { koaAdapter } from 'doctreen/koa';

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

koaAdapter(router, { meta: { title: 'My API', version: '1.0.0' } });

app.use(router.routes());
app.use(router.allowedMethods());

NestJS

import 'reflect-metadata';
import { NestFactory } from '@nestjs/core';
import { Controller, Get, Post, Body, Module } from '@nestjs/common';
import { nestAdapter, DocRoute, DocDescription, DocResponse } from 'doctreen/nest';
import { z } from 'zod';

const UserSchema = z.object({ id: z.number(), name: z.string() });

@Controller('users')
class UsersController {
  @Get()
  @DocDescription('List all users')
  @DocResponse(z.array(UserSchema))
  getUsers() { return []; }

  @Post()
  @DocRoute({
    description: 'Create a user',
    request:  { body: z.object({ name: z.string() }) },
    response: UserSchema,
    errors:   { 409: 'Email already in use' },
  })
  createUser(@Body() body: { name: string }) {
    return { id: 1, ...body };
  }
}

@Module({ controllers: [UsersController] })
class AppModule {}

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  nestAdapter(app, { meta: { title: 'My API', version: '1.0.0' } });
  await app.listen(3000);
}
bootstrap();

Available types

import type {
  SchemaNode,       // { type, properties?, items?, optional? }
  RouteEntry,       // { method, path, params, description, errors, ... }
  ErrorEntry,       // { status, description, schema }
  UserConfig,       // full config object shape
  NormalizedConfig,
  ApiMeta,
  RouteRegistry,
} from 'doctreen';

import type { RouteSchemas, ExpressLike }              from 'doctreen/express';
import type { RouteSchemas, FastifyLike }              from 'doctreen/fastify';
import type { RouteSchemas, HonoLike }                 from 'doctreen/hono';
import type { RouteSchemas, KoaRouterLike }            from 'doctreen/koa';
import type { NestRouteSchemas, NestApplicationLike }  from 'doctreen/nest';

The structural interfaces (ExpressLike, FastifyLike, etc.) let you use doctreen without depending on a specific framework's type package.

On this page