Пошук уроків, статей та іншого контенту
Підключите Swagger і згенеруєте інтерактивну OpenAPI-специфікацію для REST API.
OpenAPI — це формат опису REST API. У специфікації OpenAPI можна описати:
доступні маршрути;
HTTP-методи;
параметри шляху та query-параметри;
структуру тіла запиту;
можливі відповіді;
коди помилок;
схеми авторизації.
Swagger UI — це вебінтерфейс, який відображає OpenAPI-специфікацію та дозволяє виконувати HTTP-запити безпосередньо з браузера.
У NestJS OpenAPI-специфікація генерується на основі:
типів і DTO;
декораторів контролерів;
декораторів параметрів;
описів відповідей.
Для NestJS потрібно встановити пакет @nestjs/swagger і адаптер Swagger UI:
npm install @nestjs/swagger swagger-ui-expressЯкщо застосунок використовує валідацію DTO, додатково встановіть:
npm install class-validator class-transformerSwagger підключають під час створення NestJS-застосунку у файлі main.ts.
import { ValidationPipe } from '@nestjs/common';
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);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
transform: true,
}),
);
const config = new DocumentBuilder()
.setTitle('Books API')
.setDescription('REST API для роботи з книжками')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('docs', app, document);
await app.listen(3000);
}
bootstrap();Після запуску застосунку Swagger UI буде доступний за адресою:
http://localhost:3000/docsDocumentBuilder формує метадані OpenAPI-документа:
const config = new DocumentBuilder()
.setTitle('Books API')
.setDescription('REST API для роботи з книжками')
.setVersion('1.0')
.build();Основні методи:
.setTitle() — назва API;
.setDescription() — опис API;
.setVersion() — версія API;
.addBearerAuth() — опис Bearer-аутентифікації;
.addTag() — додавання тегів до документа.
Без додаткових декораторів Swagger може не отримати достатньо інформації про властивості DTO. Для опису схеми використовують @ApiProperty().
import { ApiProperty } from '@nestjs/swagger';
import { IsInt, IsString, Min } from 'class-validator';
export class CreateBookDto {
@ApiProperty({
example: 'Clean Code',
description: 'Назва книжки',
})
@IsString()
title!: string;
@ApiProperty({
example: 'Robert C. Martin',
description: 'Автор книжки',
})
@IsString()
author!: string;
@ApiProperty({
example: 2008,
description: 'Рік публікації',
})
@IsInt()
@Min(0)
year!: number;
}@ApiProperty() впливає на OpenAPI-схему, а class-validator — на перевірку вхідних даних. Це різні механізми:
@ApiProperty() документує поле;
@IsString(), @IsInt(), @Min() перевіряють значення під час виконання.
Для необов’язкових властивостей використовують @ApiPropertyOptional():
import { ApiPropertyOptional } from '@nestjs/swagger';
import { IsOptional, IsString } from 'class-validator';
export class UpdateBookDto {
@ApiPropertyOptional({
example: 'Refactoring',
})
@IsOptional()
@IsString()
title?: string;
}Для групування маршрутів використовують @ApiTags().
import { Controller } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
@ApiTags('books')
@Controller('books')
export class BooksController {}У Swagger UI всі маршрути цього контролера будуть згруповані під тегом books.
Декоратор @ApiOperation() додає короткий опис маршруту:
import { Get } from '@nestjs/common';
import { ApiOperation } from '@nestjs/swagger';
@ApiOperation({
summary: 'Отримати список книжок',
description: 'Повертає всі книжки, доступні в системі',
})
@Get()
findAll() {
return [];
}summary відображається як коротка назва операції, а description містить розширений опис.
Для документування HTTP-відповідей використовують @ApiResponse() або скорочені декоратори:
@ApiOkResponse() — відповідь 200;
@ApiCreatedResponse() — відповідь 201;
@ApiBadRequestResponse() — помилка 400;
@ApiNotFoundResponse() — помилка 404;
@ApiUnauthorizedResponse() — помилка 401.
Приклад:
import { Get } from '@nestjs/common';
import { ApiOkResponse } from '@nestjs/swagger';
@ApiOkResponse({
description: 'Список книжок успішно отримано',
type: [BookDto],
})
@Get()
findAll(): BookDto[] {
return [];
}Якщо відповідь містить масив, тип вказують як [BookDto].
Для параметра шляху використовують @ApiParam():
import { Get, Param } from '@nestjs/common';
import { ApiNotFoundResponse, ApiOkResponse, ApiParam } from '@nestjs/swagger';
@ApiParam({
name: 'id',
example: 1,
description: 'Ідентифікатор книжки',
})
@ApiOkResponse({
description: 'Книжку знайдено',
type: BookDto,
})
@ApiNotFoundResponse({
description: 'Книжку не знайдено',
})
@Get(':id')
findOne(@Param('id') id: string): BookDto {
return {} as BookDto;
}Для query-параметра використовують @ApiQuery():
import { ApiQuery } from '@nestjs/swagger';
@ApiQuery({
name: 'author',
required: false,
example: 'Robert C. Martin',
description: 'Фільтр за автором',
})
@Get()
findAll(@Query('author') author?: string) {
return [];
}Якщо query-параметрів багато, зручніше описати їх окремим DTO. Для простих випадків @ApiQuery() робить контракт маршруту очевидним.
Нижче наведено мінімальний приклад NestJS API з:
підключеним Swagger;
DTO для створення книжки;
валідацією вхідних даних;
описом маршрутів і відповідей;
параметром шляху;
query-фільтром.
main.tsimport { ValidationPipe } from '@nestjs/common';
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);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
transform: true,
}),
);
const config = new DocumentBuilder()
.setTitle('Books API')
.setDescription('API для роботи з книжками')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('docs', app, document);
await app.listen(3000);
}
bootstrap();app.module.tsimport { Module } from '@nestjs/common';
import { BooksController } from './books.controller';
@Module({
controllers: [BooksController],
})
export class AppModule {}books.dto.tsimport { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsInt, IsOptional, IsString, Min } from 'class-validator';
export class CreateBookDto {
@ApiProperty({
example: 'Clean Code',
description: 'Назва книжки',
})
@IsString()
title!: string;
@ApiProperty({
example: 'Robert C. Martin',
description: 'Автор книжки',
})
@IsString()
author!: string;
@ApiProperty({
example: 2008,
description: 'Рік публікації',
})
@IsInt()
@Min(0)
year!: number;
}
export class UpdateBookDto {
@ApiPropertyOptional({
example: 'The Clean Coder',
description: 'Нова назва книжки',
})
@IsOptional()
@IsString()
title?: string;
@ApiPropertyOptional({
example: 2011,
description: 'Новий рік публікації',
})
@IsOptional()
@IsInt()
@Min(0)
year?: number;
}
export class BookDto {
@ApiProperty({
example: 1,
description: 'Унікальний ідентифікатор книжки',
})
id!: number;
@ApiProperty({
example: 'Clean Code',
})
title!: string;
@ApiProperty({
example: 'Robert C. Martin',
})
author!: string;
@ApiProperty({
example: 2008,
})
year!: number;
}books.controller.tsimport {
Body,
Controller,
Get,
NotFoundException,
Param,
Patch,
Post,
Query,
} from '@nestjs/common';
import {
ApiBadRequestResponse,
ApiCreatedResponse,
ApiNotFoundResponse,
ApiOkResponse,
ApiOperation,
ApiParam,
ApiQuery,
ApiTags,
} from '@nestjs/swagger';
import { BookDto, CreateBookDto, UpdateBookDto } from './books.dto';
@ApiTags('books')
@Controller('books')
export class BooksController {
private readonly books: BookDto[] = [
{
id: 1,
title: 'Clean Code',
author: 'Robert C. Martin',
year: 2008,
},
];
@Get()
@ApiOperation({
summary: 'Отримати список книжок',
})
@ApiQuery({
name: 'author',
required: false,
example: 'Robert C. Martin',
description: 'Фільтр за автором',
})
@ApiOkResponse({
description: 'Список книжок успішно отримано',
type: [BookDto],
})
findAll(@Query('author') author?: string): BookDto[] {
if (!author) {
return this.books;
}
return this.books.filter((book) => book.author === author);
}
@Get(':id')
@ApiOperation({
summary: 'Отримати книжку за ідентифікатором',
})
@ApiParam({
name: 'id',
example: 1,
description: 'Ідентифікатор книжки',
})
@ApiOkResponse({
description: 'Книжку успішно отримано',
type: BookDto,
})
@ApiNotFoundResponse({
description: 'Книжку не знайдено',
})
findOne(@Param('id') id: string): BookDto {
const book = this.books.find((item) => item.id === Number(id));
if (!book) {
throw new NotFoundException('Книжку не знайдено');
}
return book;
}
@Post()
@ApiOperation({
summary: 'Створити книжку',
})
@ApiCreatedResponse({
description: 'Книжку успішно створено',
type: BookDto,
})
@ApiBadRequestResponse({
description: 'Некоректні вхідні дані',
})
create(@Body() data: CreateBookDto): BookDto {
const book: BookDto = {
id: this.books.length + 1,
...data,
};
this.books.push(book);
return book;
}
@Patch(':id')
@ApiOperation({
summary: 'Оновити книжку',
})
@ApiParam({
name: 'id',
example: 1,
description: 'Ідентифікатор книжки',
})
@ApiOkResponse({
description: 'Книжку успішно оновлено',
type: BookDto,
})
@ApiNotFoundResponse({
description: 'Книжку не знайдено',
})
update(
@Param('id') id: string,
@Body() data: UpdateBookDto,
): BookDto {
const book = this.books.find((item) => item.id === Number(id));
if (!book) {
throw new NotFoundException('Книжку не знайдено');
}
Object.assign(book, data);
return book;
}
}Після запуску:
npm run start:devвідкрийте:
http://localhost:3000/docsУ Swagger UI можна:
розгорнути групу books;
переглянути параметри кожного маршруту;
переглянути схеми CreateBookDto, UpdateBookDto і BookDto;
натиснути Try it out;
заповнити параметри запиту;
виконати запит і переглянути реальну HTTP-відповідь.
NestJS може автоматично визначити частину інформації з метаданих TypeScript і декораторів:
HTTP-метод;
шлях маршруту;
назву параметра;
тип параметра;
тип тіла запиту;
тип відповіді, якщо його вказано в декораторі.
Однак автоматичного визначення недостатньо для повного контракту. Наприклад, Swagger не завжди може самостійно зрозуміти:
можливі коди помилок;
приклади значень;
опис полів;
необов’язковість параметрів;
структуру складних або поліморфних відповідей.
Тому важливі маршрути та DTO варто доповнювати декораторами @ApiProperty(), @ApiResponse(), @ApiParam() і @ApiQuery().
У цьому коді є два окремі кроки:
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('docs', app, document);SwaggerModule.createDocument() створює об’єкт OpenAPI-документа.
SwaggerModule.setup() публікує цей документ через Swagger UI за вказаним шляхом.
Тому Swagger UI — це лише спосіб перегляду та тестування специфікації. Сам документ OpenAPI може використовуватися іншими інструментами для генерації клієнтів, тестів або внутрішньої документації.
Якщо API використовує Bearer-токени, схему авторизації можна додати до документа:
const config = new DocumentBuilder()
.setTitle('Books API')
.setDescription('API для роботи з книжками')
.setVersion('1.0')
.addBearerAuth()
.build();Щоб позначити конкретний контролер або маршрут як такий, що використовує Bearer-токен, застосовують @ApiBearerAuth():
import { Controller, Get } from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
@ApiTags('profile')
@ApiBearerAuth()
@Controller('profile')
export class ProfileController {
@Get()
getProfile() {
return {
id: 1,
name: 'Olena',
};
}
}Це документує вимогу до авторизації, але саме по собі не перевіряє токен. Перевірку повинен виконувати механізм автентифікації застосунку.
Якщо маршрут повертає об’єкт, але не має type у декораторі відповіді, Swagger може показати неповну схему.
@ApiOkResponse({
type: BookDto,
})Для масиву використовуйте:
@ApiOkResponse({
type: [BookDto],
})@ApiProperty() не перевіряє дані. Наприклад:
@ApiProperty()
title!: string;лише описує поле для OpenAPI. Для перевірки типу потрібні декоратори class-validator і підключений ValidationPipe.
Swagger UI не знає про всі можливі помилки лише з того, що код кидає NotFoundException. Додайте відповідний декоратор:
@ApiNotFoundResponse({
description: 'Ресурс не знайдено',
})@ApiQuery() без required: falseЗа замовчуванням параметр query вважається обов’язковим у документації. Якщо параметр необов’язковий, це потрібно вказати явно:
@ApiQuery({
name: 'author',
required: false,
})Для відповіді BookDto[] потрібно вказати:
type: [BookDto]Якщо вказати лише type: BookDto, Swagger може показати один об’єкт замість масиву.
OpenAPI описує структуру та поведінку REST API.
Swagger UI відображає OpenAPI-документ в інтерактивному інтерфейсі.
У NestJS Swagger підключають через DocumentBuilder, SwaggerModule.createDocument() і SwaggerModule.setup().
@ApiTags() групує маршрути.
@ApiOperation() описує операцію.
@ApiProperty() описує властивості DTO.
@ApiResponse() та спеціалізовані декоратори описують HTTP-відповіді.
@ApiParam() документує параметри шляху.
@ApiQuery() документує query-параметри.
Документація Swagger і валідація вхідних даних — окремі механізми.