Пошук уроків, статей та іншого контенту
Організуєте опис ресурсів, параметрів, відповідей і сценаріїв використання для розробників API.
Документація REST API описує контракт між сервером і клієнтом. Вона має відповідати на такі запитання:
які ресурси доступні;
які HTTP-методи підтримуються;
які параметри можна передавати;
яку структуру має тіло запиту;
які відповіді повертає сервер;
що означають коди помилок;
як використовувати операцію в типовому сценарії.
У NestJS для цього зазвичай використовують пакет @nestjs/swagger. Він генерує OpenAPI-документ на основі декораторів у контролерах і DTO, а також надає інтерактивний Swagger UI.
Встановіть пакет:
npm install @nestjs/swaggerУ main.ts створіть опис API та підключіть Swagger UI:
import { NestFactory } from '@nestjs/core';
import {
DocumentBuilder,
SwaggerModule,
} from '@nestjs/swagger';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const config = new DocumentBuilder()
.setTitle('Tasks API')
.setDescription('REST API для керування завданнями')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('docs', app, document);
await app.listen(3000);
}
bootstrap();Після запуску застосунку документація буде доступна за адресою:
http://localhost:3000/docsDocumentBuilder задає загальну інформацію про API:
назву;
опис;
версію;
схеми авторизації;
зовнішні сервери, якщо вони потрібні.
SwaggerModule.setup() визначає шлях, за яким буде доступний Swagger UI.
Кожен контролер зазвичай представляє окремий ресурс. Декоратор @ApiTags() групує операції в Swagger UI.
Для опису окремої операції використовують @ApiOperation():
import {
Controller,
Get,
} from '@nestjs/common';
import {
ApiOperation,
ApiTags,
} from '@nestjs/swagger';
@ApiTags('tasks')
@Controller('tasks')
export class TasksController {
@Get()
@ApiOperation({
summary: 'Отримати список завдань',
description:
'Повертає завдання поточного користувача з можливістю фільтрації за статусом.',
})
findAll() {
return [];
}
}summary — короткий опис, який видно у списку операцій.
description — детальніший опис сценарію використання. Його варто використовувати для пояснення:
призначення операції;
правил фільтрації;
особливостей сортування;
обмежень;
залежності від поточного користувача.
Опис має пояснювати поведінку API, а не повторювати назву методу контролера.
DTO описує структуру даних, які надходять у запиті або повертаються у відповіді.
Розглянемо DTO для створення завдання:
import { ApiProperty } from '@nestjs/swagger';
import {
IsEnum,
IsString,
MaxLength,
MinLength,
} from 'class-validator';
export enum TaskStatus {
TODO = 'todo',
IN_PROGRESS = 'in_progress',
DONE = 'done',
}
export class CreateTaskDto {
@ApiProperty({
example: 'Підготувати документацію API',
description: 'Назва завдання',
minLength: 3,
maxLength: 120,
})
@IsString()
@MinLength(3)
@MaxLength(120)
title: string;
@ApiProperty({
example: 'Додати опис параметрів і приклади відповідей',
description: 'Детальний опис завдання',
required: false,
})
@IsString()
@MaxLength(1000)
description?: string;
@ApiProperty({
enum: TaskStatus,
enumName: 'TaskStatus',
example: TaskStatus.TODO,
description: 'Поточний статус завдання',
})
@IsEnum(TaskStatus)
status: TaskStatus;
}@ApiProperty() описує поле DTO для OpenAPI-схеми.
Корисні властивості:
example — приклад значення;
description — призначення поля;
required — чи є поле обов’язковим;
enum — перелік допустимих значень;
minLength і maxLength — обмеження для документації.
Важливо: декоратори Swagger лише описують API. Вони не виконують валідацію даних. Для перевірки значень потрібні декоратори class-validator і налаштований ValidationPipe.
Для необов’язкового поля зручно використовувати @ApiPropertyOptional():
import {
ApiProperty,
ApiPropertyOptional,
} from '@nestjs/swagger';
export class UpdateTaskDto {
@ApiPropertyOptional({
example: 'Оновлена назва завдання',
})
title?: string;
@ApiPropertyOptional({
example: 'todo',
enum: TaskStatus,
})
status?: TaskStatus;
}Такий запис чітко показує клієнту, що під час оновлення можна передати лише частину полів.
Не слід позначати поле як необов’язкове тільки тому, що сервер іноді не повертає його. Потрібно окремо визначати DTO для запитів і відповідей, якщо їхня структура відрізняється.
Параметри маршруту описують за допомогою @ApiParam():
import {
Controller,
Get,
Param,
} from '@nestjs/common';
import {
ApiOperation,
ApiParam,
ApiTags,
} from '@nestjs/swagger';
@ApiTags('tasks')
@Controller('tasks')
export class TasksController {
@Get(':id')
@ApiOperation({
summary: 'Отримати завдання за ідентифікатором',
})
@ApiParam({
name: 'id',
type: String,
example: 'task_123',
description: 'Унікальний ідентифікатор завдання',
})
findOne(@Param('id') id: string) {
return {
id,
title: 'Підготувати документацію API',
status: 'todo',
};
}
}Назва в @ApiParam() має збігатися з назвою параметра в маршруті:
@Get(':id')і:
@ApiParam({ name: 'id' })Якщо ідентифікатор має числовий тип, це потрібно явно вказати:
@ApiParam({
name: 'id',
type: Number,
example: 42,
})Query-параметри використовують для фільтрації, пагінації та сортування.
import {
Controller,
Get,
Query,
} from '@nestjs/common';
import {
ApiOperation,
ApiQuery,
ApiTags,
} from '@nestjs/swagger';
@ApiTags('tasks')
@Controller('tasks')
export class TasksController {
@Get()
@ApiOperation({
summary: 'Отримати список завдань',
})
@ApiQuery({
name: 'status',
required: false,
enum: TaskStatus,
description: 'Фільтр за статусом',
example: TaskStatus.IN_PROGRESS,
})
@ApiQuery({
name: 'page',
required: false,
type: Number,
example: 1,
description: 'Номер сторінки',
})
@ApiQuery({
name: 'limit',
required: false,
type: Number,
example: 20,
description: 'Кількість елементів на сторінці',
})
findAll(
@Query('status') status?: TaskStatus,
@Query('page') page = 1,
@Query('limit') limit = 20,
) {
return {
data: [],
meta: {
page: Number(page),
limit: Number(limit),
total: 0,
},
filters: {
status,
},
};
}
}Для необов’язкового параметра потрібно явно вказувати required: false. Інакше Swagger може показати його як обов’язковий.
Якщо query-параметр має складну структуру, краще створити окремий DTO для фільтрів. Це зменшує кількість декораторів у контролері та спрощує повторне використання опису.
Якщо метод приймає DTO через @Body(), Swagger може використати його як схему запиту:
import {
Body,
Controller,
Post,
} from '@nestjs/common';
import {
ApiCreatedResponse,
ApiOperation,
ApiTags,
} from '@nestjs/swagger';
@ApiTags('tasks')
@Controller('tasks')
export class TasksController {
@Post()
@ApiOperation({
summary: 'Створити завдання',
description:
'Створює нове завдання та повертає його разом із системними полями.',
})
@ApiCreatedResponse({
description: 'Завдання успішно створено',
type: TaskResponseDto,
})
create(@Body() dto: CreateTaskDto): TaskResponseDto {
return {
id: 'task_123',
title: dto.title,
description: dto.description,
status: dto.status,
createdAt: new Date().toISOString(),
};
}
}Якщо тип DTO не вказаний у відповіді, Swagger може показати відповідь без повної структури. Тому для публічного API бажано явно документувати response DTO.
Для відповідей використовують спеціалізовані декоратори:
@ApiOkResponse() — успішна відповідь із кодом 200;
@ApiCreatedResponse() — ресурс створено, код 201;
@ApiNoContentResponse() — успішна відповідь без тіла, код 204;
@ApiBadRequestResponse() — некоректні дані, код 400;
@ApiUnauthorizedResponse() — відсутня або некоректна автентифікація, код 401;
@ApiForbiddenResponse() — недостатньо прав, код 403;
@ApiNotFoundResponse() — ресурс не знайдено, код 404;
@ApiConflictResponse() — конфлікт даних, код 409.
DTO відповіді може мати інший вигляд, ніж DTO запиту:
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class TaskResponseDto {
@ApiProperty({
example: 'task_123',
description: 'Унікальний ідентифікатор завдання',
})
id: string;
@ApiProperty({
example: 'Підготувати документацію API',
})
title: string;
@ApiPropertyOptional({
example: 'Додати опис параметрів і приклади відповідей',
})
description?: string;
@ApiProperty({
enum: TaskStatus,
example: TaskStatus.TODO,
})
status: TaskStatus;
@ApiProperty({
example: '2026-08-20T10:30:00.000Z',
format: 'date-time',
})
createdAt: string;
}Тепер операцію можна описати повністю:
@Get(':id')
@ApiOperation({
summary: 'Отримати завдання',
description:
'Повертає одне завдання за ідентифікатором, доступне поточному користувачу.',
})
@ApiParam({
name: 'id',
type: String,
example: 'task_123',
})
@ApiOkResponse({
description: 'Завдання знайдено',
type: TaskResponseDto,
})
@ApiBadRequestResponse({
description: 'Ідентифікатор має некоректний формат',
})
@ApiNotFoundResponse({
description: 'Завдання не знайдено',
})
findOne(@Param('id') id: string): TaskResponseDto {
return {
id,
title: 'Підготувати документацію API',
status: TaskStatus.TODO,
createdAt: new Date().toISOString(),
};
}Документація має описувати не лише успішний сценарій. Клієнту важливо знати, що означає кожна очікувана помилка.
Нижче наведено узгоджений приклад DTO та контролера для ресурсу tasks.
import {
Body,
Controller,
Get,
Param,
Post,
Query,
} from '@nestjs/common';
import {
ApiBadRequestResponse,
ApiCreatedResponse,
ApiNotFoundResponse,
ApiOkResponse,
ApiOperation,
ApiParam,
ApiProperty,
ApiPropertyOptional,
ApiQuery,
ApiTags,
} from '@nestjs/swagger';
import {
IsEnum,
IsOptional,
IsString,
MaxLength,
MinLength,
} from 'class-validator';
enum TaskStatus {
TODO = 'todo',
IN_PROGRESS = 'in_progress',
DONE = 'done',
}
class CreateTaskDto {
@ApiProperty({
example: 'Підготувати документацію API',
minLength: 3,
maxLength: 120,
})
@IsString()
@MinLength(3)
@MaxLength(120)
title: string;
@ApiPropertyOptional({
example: 'Описати параметри та відповіді API',
})
@IsOptional()
@IsString()
@MaxLength(1000)
description?: string;
@ApiProperty({
enum: TaskStatus,
example: TaskStatus.TODO,
})
@IsEnum(TaskStatus)
status: TaskStatus;
}
class TaskResponseDto {
@ApiProperty({
example: 'task_123',
})
id: string;
@ApiProperty({
example: 'Підготувати документацію API',
})
title: string;
@ApiPropertyOptional({
example: 'Описати параметри та відповіді API',
})
description?: string;
@ApiProperty({
enum: TaskStatus,
example: TaskStatus.TODO,
})
status: TaskStatus;
@ApiProperty({
example: '2026-08-20T10:30:00.000Z',
format: 'date-time',
})
createdAt: string;
}
@ApiTags('tasks')
@Controller('tasks')
export class TasksController {
@Get()
@ApiOperation({
summary: 'Отримати список завдань',
description:
'Повертає список завдань із фільтрацією за статусом і пагінацією.',
})
@ApiQuery({
name: 'status',
required: false,
enum: TaskStatus,
example: TaskStatus.TODO,
})
@ApiQuery({
name: 'page',
required: false,
type: Number,
example: 1,
})
@ApiQuery({
name: 'limit',
required: false,
type: Number,
example: 20,
})
@ApiOkResponse({
description: 'Список завдань успішно отримано',
type: [TaskResponseDto],
})
findAll(
@Query('status') status?: TaskStatus,
@Query('page') page = 1,
@Query('limit') limit = 20,
): TaskResponseDto[] {
// У реальному застосунку тут буде виклик сервісу та запит до бази даних.
void status;
void page;
void limit;
return [];
}
@Get(':id')
@ApiOperation({
summary: 'Отримати завдання за ідентифікатором',
})
@ApiParam({
name: 'id',
type: String,
example: 'task_123',
description: 'Унікальний ідентифікатор завдання',
})
@ApiOkResponse({
description: 'Завдання знайдено',
type: TaskResponseDto,
})
@ApiBadRequestResponse({
description: 'Ідентифікатор має некоректний формат',
})
@ApiNotFoundResponse({
description: 'Завдання не знайдено',
})
findOne(@Param('id') id: string): TaskResponseDto {
return {
id,
title: 'Підготувати документацію API',
status: TaskStatus.TODO,
createdAt: new Date().toISOString(),
};
}
@Post()
@ApiOperation({
summary: 'Створити завдання',
description:
'Створює завдання та повертає його з ідентифікатором і датою створення.',
})
@ApiCreatedResponse({
description: 'Завдання успішно створено',
type: TaskResponseDto,
})
@ApiBadRequestResponse({
description: 'Тіло запиту не пройшло валідацію',
})
create(@Body() dto: CreateTaskDto): TaskResponseDto {
return {
id: 'task_123',
title: dto.title,
description: dto.description,
status: dto.status,
createdAt: new Date().toISOString(),
};
}
}Такий контролер документує:
групу ресурсу tasks;
призначення кожної операції;
параметр id;
query-параметри;
DTO запиту;
DTO відповіді;
успішні статуси;
очікувані помилки.
Опис операції має допомогти розробнику зрозуміти послідовність взаємодії з API.
Наприклад, для створення завдання корисно вказати:
Клієнт надсилає POST /tasks.
У тілі запиту передає title і status.
Сервер перевіряє дані.
Сервер повертає код 201.
У відповіді клієнт отримує id, createdAt та інші поля ресурсу.
Цей сценарій можна зафіксувати в description операції:
@ApiOperation({
summary: 'Створити завдання',
description: `
Створює нове завдання.
Сценарій:
1. Передайте назву та статус у тілі запиту.
2. Сервер перевірить коректність значень.
3. У разі успіху буде повернено створений ресурс із кодом 201.
4. Якщо обов'язкове поле відсутнє, сервер поверне код 400.
`,
})У багаторядкових рядках і коментарях коду потрібно стежити, щоб текст залишався коротким і корисним. Swagger-документація не повинна перетворюватися на дублювання всієї внутрішньої реалізації.
Якщо однакові помилки виникають у багатьох операціях, їх можна додавати через спільні декоратори або власні обгортки.
Наприклад, для методу, який потребує автентифікації, потрібно документувати 401:
import {
ApiBearerAuth,
ApiUnauthorizedResponse,
} from '@nestjs/swagger';
@ApiBearerAuth()
@ApiUnauthorizedResponse({
description: 'Потрібен дійсний токен доступу',
})
@Get('profile')
getProfile() {
return {
id: 'user_123',
name: 'Ada Lovelace',
};
}@ApiBearerAuth() лише показує в документації, що операція використовує Bearer-токен. Реальну перевірку токена виконують guard і стратегія автентифікації.
Документація є корисною лише тоді, коли вона відповідає фактичній поведінці API.
Перевіряйте:
чи збігається HTTP-статус у декораторі з реальною відповіддю;
чи всі обов’язкові поля позначені як обов’язкові;
чи приклади відповідають реальним типам;
чи описані помилки, які справді може отримати клієнт;
чи збігаються назви параметрів із маршрутами;
чи не повертає сервер поля, яких немає в response DTO;
чи не документується поле як рядок, якщо сервер повертає число або дату в іншому форматі.
Зручно перевіряти документацію після зміни контролера, DTO або формату відповіді. Swagger UI у такому разі працює як швидкий спосіб виявити розбіжності в контракті.
Клієнту потрібно знати не лише формат 200 або 201, а й причини типових помилок.
Погано:
@ApiOkResponse({
type: TaskResponseDto,
})Краще додати очікувані помилки:
@ApiOkResponse({
type: TaskResponseDto,
})
@ApiNotFoundResponse({
description: 'Завдання не знайдено',
})Назва findAll() не пояснює, чи підтримуються фільтри, пагінація або сортування. Цю інформацію потрібно додати до @ApiOperation() і @ApiQuery().
@ApiProperty() формує схему OpenAPI, але не забороняє некоректні значення під час виконання. Для валідації використовуйте class-validator і ValidationPipe.
Без прикладів розробнику доводиться здогадуватися про формат і допустимі значення. Для ідентифікаторів, дат, enum і складних полів приклади особливо важливі.
DTO створення, оновлення та відповіді часто мають різні вимоги. Наприклад, id і createdAt не надсилаються під час створення, але повертаються клієнту.
Для маршруту:
@Get(':taskId')потрібно використовувати:
@ApiParam({
name: 'taskId',
type: String,
})Інакше Swagger показуватиме параметр, якого немає в реальному URL.
@nestjs/swagger генерує OpenAPI-документацію для NestJS.
DocumentBuilder налаштовує загальну інформацію про API.
@ApiTags() групує операції за ресурсами.
@ApiOperation() описує призначення та сценарій використання операції.
@ApiParam() документує параметри маршруту.
@ApiQuery() документує query-параметри.
@ApiProperty() і @ApiPropertyOptional() описують поля DTO.
@ApiOkResponse(), @ApiCreatedResponse() та інші декоратори описують відповіді й помилки.
DTO запиту та DTO відповіді не обов’язково мають бути однаковими.
Swagger-декоратори описують контракт, але не замінюють валідацію.
Документація має відповідати фактичній поведінці REST API.