DocTreen
Adapters

NestJS

Decorator-based API for NestJS — @DocRoute or granular @Doc* decorators.

DocTreen provides a decorator-based API for NestJS that integrates naturally with standard NestJS controller patterns.

Setup

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);
  nestAdapter(app, { meta: { title: 'My API', version: '2.0.0' } });
  await app.listen(3000);
}
bootstrap();

No changes to AppModule are required. DocTreen reads NestJS's internal route metadata directly — no DiscoveryModule import needed.

Works with both @nestjs/platform-express (default) and @nestjs/platform-fastify.

@DocRoute — full schema on one decorator

import { Controller, Post, Body } from '@nestjs/common';
import { DocRoute } from 'doctreen/nest';
import { z } from 'zod';

const importSchema = z.object({
  products: z.array(z.object({ sku: z.string(), price: z.number() })),
});

const importResponseSchema = z.object({
  imported: z.number(),
  skipped:  z.number(),
});

@Controller('products')
export class ProductsController {
  @Post('import')
  @DocRoute({
    description: 'Bulk import partner inventory',
    headers: {
      'x-partner-api-key': 'Partner API key',
      'Content-Type':      'application/json',
    },
    request: {
      body: importSchema,
    },
    response: importResponseSchema,
    errors: {
      400: 'Validation failed',
      401: 'Missing or invalid API key',
      429: 'Rate limit exceeded',
    },
  })
  importProducts(@Body() body: any) {
    return { imported: body.products.length, skipped: 0 };
  }
}

Granular decorators

Use the smaller decorators when you want to keep each concern separate, or when composing with other decorator libraries:

import { Controller, Get, Post, Delete, Param, Body } from '@nestjs/common';
import {
  DocDescription,
  DocHeaders,
  DocRequest,
  DocResponse,
  DocErrors,
} from 'doctreen/nest';
import { z } from 'zod';

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

@Controller('users')
export class UsersController {
  @Get()
  @DocDescription('List all users')
  @DocRequest({ query: z.object({ page: z.number().optional(), role: z.string().optional() }) })
  @DocResponse(z.array(UserSchema))
  getUsers() { return []; }

  @Post()
  @DocDescription('Create a new user')
  @DocHeaders({ Authorization: 'Bearer <token>' })
  @DocRequest({ body: z.object({ name: z.string(), email: z.string() }) })
  @DocResponse(UserSchema)
  @DocErrors({ 409: 'Email already in use', 422: 'Validation failed' })
  createUser(@Body() body: any) { return { id: 1, ...body }; }

  @Delete(':id')
  @DocDescription('Delete a user by ID')
  @DocErrors({ 404: 'User not found', 403: 'Insufficient permissions' })
  deleteUser(@Param('id') id: string) { return { success: true }; }
}

All @Doc* decorators merge onto the same metadata key — stack any combination on the same method.

@DocRoute schema reference

FieldTypeDescription
descriptionstringHuman-readable description shown in the UI
headersRecord<string, string>Request headers to document
request.bodySchemaNode | ZodSchemaRequest body shape
request.querySchemaNode | ZodSchemaQuery parameter shape
responseSchemaNode | ZodSchemaSuccess response shape
errorsRecord<number, string | { description?, schema? }>Error responses by HTTP status
hiddenbooleanHide from docs UI and OpenAPI output
tagsstring[]Group operations under named tags
securityArray<Record<string, string[]>>Per-route security override

Hidden routes

// Full bag
@Get('flags') @DocRoute({ hidden: true }) flags() { /* ... */ }

// Shorthand decorator
@Get('flags') @DocHidden() flags() { /* ... */ }

How it works

  1. nestAdapter(app, config) is called after NestFactory.create() and before app.listen().
  2. It registers the docs route directly on the underlying HTTP adapter (Express or Fastify platform), bypassing NestJS guards and interceptors — this is intentional for an internal docs endpoint.
  3. Route discovery reads NestJS's internal container (app.container.getModules()) to enumerate all controllers and their methods.
  4. For each controller method, it reads Reflect.getMetadata('path', fn) and Reflect.getMetadata('method', fn) — the same metadata keys set by @Get(), @Post(), etc.
  5. @DocRoute and @Doc* decorators attach their schemas under a separate metadata key on the same method function.
  6. The global prefix (set via app.setGlobalPrefix(...)) is prepended to all discovered paths.
  7. Schema resolution order: @DocRoute / @Doc* decorators (no JSDoc or runtime-inference fallback in NestJS).

On this page