Настраиваем автодокументирование для 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 - лучший выбор.