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
| Field | Type | Description |
|---|---|---|
description | string | Human-readable description shown in the UI |
headers | Record<string, string> | Request headers to document |
request.body | SchemaNode | ZodSchema | Request body shape |
request.query | SchemaNode | ZodSchema | Query parameter shape |
response | SchemaNode | ZodSchema | Success response shape |
errors | Record<number, string | { description?, schema? }> | Error responses by HTTP status |
hidden | boolean | Hide from docs UI and OpenAPI output |
tags | string[] | Group operations under named tags |
security | Array<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
nestAdapter(app, config)is called afterNestFactory.create()and beforeapp.listen().- 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.
- Route discovery reads NestJS's internal container (
app.container.getModules()) to enumerate all controllers and their methods. - For each controller method, it reads
Reflect.getMetadata('path', fn)andReflect.getMetadata('method', fn)— the same metadata keys set by@Get(),@Post(), etc. @DocRouteand@Doc*decorators attach their schemas under a separate metadata key on the same method function.- The global prefix (set via
app.setGlobalPrefix(...)) is prepended to all discovered paths. - Schema resolution order:
@DocRoute/@Doc*decorators (no JSDoc or runtime-inference fallback in NestJS).