Настраиваем автодокументирование для express-приложений
Вместо предисловия
Долгое время в express было принято использовать решения, которые довольно большую часть работы перекладывают на разработчика. Одним из самых популярных решений и по сей день является swagger-jsdoc.
Поскольку, оно является и самым проверенным, я продолжал рассказывать о нём студентам из года в год. Но в этом году что-то пошло не так... Один из студентов спросил: "а нет ли чего-то такого же удобного, как в Nest.JS?". Тут-то всё и началось.
Я прочитал более десятка статей, пересмотрел несколько роликов в поисках оптимального решения. И вот мы здесь. Я выделил 3 решения для настройки автодокументации и подготовил сравнения и примеры для них.
Старый-добрый swagger-jsdoc
Крайне простое в использовании решение, которое требует от вас только терпения и времени. По сути, вы описываете все эндпоинты через специальные jsdoc-комментарии, по формату совпадающие с OpenAPI в YAML.
Библиотека же ходит по вашему коду, собирает все такие комментарии и встраивает их в общий файлик OpenAPI.
/**
* @openapi
* /v1/testCreate:
* post:
* produces:
* - application/json
* parameters:
* - name: username
* in: formData
* required: true
* type: string
* - name: password
* in: formData
* required: true
* type: string
* description: Test create
* responses:
* 200:
* description: Returns a created object.
*/
router
.route('/testCreate')
.post(exampleController.post)Плюсы у такого решения вполне очевидны, оно достаточно простое и универсальное. Подходит, даже если вы не используете typescript на бэкенде (лучше всё-таки использовать...). Минусы, думаю, вы и сами понимаете.
Плюсы и минусы
Полный код примера
Полный код примера доступен на github.
Пишем как на Nest: routing-controllers
Если вы начинаете с express, но планируете перейти на Nest позднее, то routing-controllers - ваш выбор. Синтаксически это почти один-в-один совпадает с Nest.
@JsonController()
export class ExampleController {
@OpenAPI({ summary: 'Test create' })
@ResponseSchema(TestCreateResponseDto, { statusCode: 200 })
@Post('/testCreate')
post(
@Body({ type: TestCreateDto }) body: TestCreateDto,
@Res() response: Response,
): void {
const uuid: string = randomUUID();
const responseBody = {
uuid,
...body,
};
response.status(201).send(responseBody);
}
}
Как видите, от привычного express тут мало что осталось, но контроллеры становятся куда более структурированными за счёт аннотаций.
Типы для документации собираются на основе class-validator, как и в Nest. Что фактические вынуждает вас к валидации входящих данных.
Стоит отметить, что само по себе, решение не предоставляет возможностей для генерации документации, это делается за счёт использования библиотеки routing-controllers-openapi. У неё есть проблема, из-за которой могут быть проблемы с работой рантайма tsx. Не могу сказать, что я как-то сильно погружался, но по моим ощущениям, это связано с некорректной обработкой данных из метадаты. К счастью, это исправляется либо за счёт monkey-патча, либо за счёт использования моего форка.
У меня есть даже свой бойлерплейт на основе routing-controllers. Правда, он не до конца написан, но начало положено...
Плюсы и минусы
Полный код примера
Полный код примера доступен на github.
Ещё немножко декораторов: tsoa
Да, снова декораторы. Но tsoa - решение куда более комплексное, чем routing-controllers. По крайней мере, на мой взгляд.
@Route()
@Tags('Example')
export class ExampleController extends Controller {
@Post('/testCreate')
@Response<TestCreateResponseDto>(201, 'Returns a created object.')
public async post(
@Body() body: TestCreateDto,
): Promise<TestCreateResponseDto> {
const uuid: string = randomUUID();
return {
uuid,
...body,
};
}
}
Это решение предоставляет минимально-необходимый набор инструментов для реализации автодокументируемого кода.
Для составления схем используются интерфейсы, валидация в них осуществляется за счёт добавления специальных комментариев. Код всё также хорошо структурирован, как и в routing-controllers. Никаких проблем с рантаймами тайпскрипта тут нет.
Для меня tsoa - самое оптимальное решение. Буду во все новые проекты на Node брать именно эту библиотеку.
Плюсы и минусы
- код более структурирован
- встроенная валидация
- единое решение для контроллеров и автодокументирования
- синтаксически близко к Nest
Полный код примера
Полный код примера доступен на github.
Выводы
Хотите посмотреть на сухое сравнение, тогда вам сюда. А по моим личным ощущениям, tsoa - лучший выбор.