OpenAPI и согласованная документация API
Контракт уже существует в DTO, controller и CONTRACT.md. Сделаем его обозримым через OpenAPI и Swagger UI, чтобы другой клиент мог узнать формы операций без чтения SQL. Результат главы — схема запросов, ответов и Bearer-доступа, согласованная с написанным API. При этом генерация документа не заменяет ValidationPipe и не доказывает фактическое выполнение маршрутов: схема объясняет договор, а runtime-поведение необходимо отдельно воспроизвести.
Для серии выбран @nestjs/swagger 12.0.2. Его официальный package manifest объявляет ESM и peers Nest 12, что соответствует common/core12.0.1. @nestjs/config также зафиксирован 12.0.1. Это современные профили, сверенные по первичным источникам. Npm install не выполнялся и lockfile не создан, поэтому совместимость полного dependency tree ещё не подтверждена окружением. Версии в статье обозначают авторский выбор, а не отчёт о успешная сборка и открытом Swagger интерфейсе.
Создание документа
Фрагмент ниже находится в main.ts после создания app и global prefix. DocumentBuilder задаёт название, версию договора и Bearer-схему. Фабрика createDocument передаётся setup, который размещает UI на docs. Это полный участок настройки Swagger для лаборатории; AppModule и routes уже существуют. Версия 1.0 относится к API-contract, а не к номеру Nest. Изменение framework patch не обязано автоматически менять wire-семантику GET или PATCH.
const swagger = new DocumentBuilder()
.setTitle('Диспетчер: учебный API')
.setVersion('1.0')
.addBearerAuth()
.build();
SwaggerModule.setup('docs',app,() => SwaggerModule.createDocument(app,swagger));
Ожидаемый UI находится /docs, JSON —/docs-json. Предметные routes остаются /api/tickets и /api/tickets/:id. Prefix не нужно вручную повторять в decorators controller только ради красивого документа. Bearer declaration описывает ожидаемое Authorization, а ApiBearerAuth у controller связывает операции с этим требованием. Нажатие Authorize в будущем интерфейсе сможет помочь читателю отправить публичный учебный token, но сама декларация не выполняет проверку. Её реализуют guards, которые могут отказать независимо от схемы.
Входная и выходная модели
PatchTicketDto содержит ApiProperty для title/status/version и class-validator decorators для runtime-проверки. Эти наборы metadata имеют разные назначения. Например, minimum в OpenAPI объясняет число генератору клиента, а Min и IsInt проверяют реальное тело на сервере. Если удалить validation decorators, красивое документированное правило не станет автоматически действовать. Если удалить описание Schema, pipe может продолжать проверять, но читатель документа потеряет часть информации. Поэтому совпадение двух договоров должно проверяться осознанно.
TicketResponse описывает пять выдаваемых полей, TicketListResponse — items и total, ErrorResponse — envelope отказа. Controller явно указывает типы через ApiOkResponse и декларирует 400/401/403/404/409 по месту. Patch возвращает Ticket, не исходный body: id, новый version и updatedAt являются серверным результатом. Такая разница хорошо видна в документации и помогает другому клиенту не отправлять полный объект GET как PATCH с лишними служебными полями.
Enums status используют тот же statuses из model.ts. Это предотвращает отдельную ручную копию, которая могла бы забыть done или добавить archived лишь в Schema. Общий список значений остаётся runtime-объектом, пригодным обоим decorators. Но TypeScript union сам по себе не перечисляет свои варианты во время выполнения. Поэтому попытка вывести enum только из типа Status не заменит metadata. В исходниках явно есть и массив, и связанный тип, каждый для нужного уровня.
Что документация не подтверждает
Схема не знает, что repository действительно удерживает один client на транзакцию. Она описывает 409, но не исполняет конкурентный сценарий A/B. Unique ticket_id/version и row lock находятся в SQL и repository. Если code implementation нарушит этот инвариант, Swagger продолжит отображать правильное намерение. Поэтому при передаче проекта следует различать описанный контракт, написанный код и выполненную проверку. В сессии подтверждён только авторский текст и чтение официальных источников, а не HTTP-поведение документа.
Response DTO также не является автоматическим runtime serializer при возвращении обычного объекта. Controller использует их для документации, а repository явно строит Ticket. Если вернуть другой объект из метода без дополнительной проверки, декоратор ApiOkResponse не остановит ошибку. Angular readTicket/readList добавляет собственную проверку полученного shape на клиентской границе. Это не делает backend гарантированно правильным, но помогает заметить несовпадение договора до ошибочного отображения карточки.
Дополнительные правила находятся в CONTRACT.md: список ограничен 200, q ищет буквальный фрагмент, arbitrary Unicode-equivalence не гарантирована, статусы меняются свободно, public tokens только учебные. Краткий OpenAPI-field maxLength не объясняет все эти семантические ограничения. Поэтому human-readable контракт остаётся полезен рядом со схемой. Не следует переносить длинные технические границы в каждую кнопку продукта, но разработчику клиента они нужны для корректного выбора поиска, повторов и восстановления после 409.
Документ для самостоятельного клиента
Представьте, что Angular заменён маленьким CLI-клиентом. По Schema он узнает path id, Bearer header, body title/status/version и формы ответов. По CONTRACT.md — значение version и неоднозначный outcome при потере ответа. По README — локальные адреса и подготовку базы. Эти три источника дополняют друг друга. Ни один сам по себе не содержит всех деталей, но вместе они дают воспроизводимый договор без чтения каждой строки внутреннего repository.
Для будущей проверки читатель может открыть /docs-json и сравнить required-поля PatchTicketDto, enum status и response types с controller. Затем выполнить одну операцию через UI и сравнить настоящий JSON с документом. В этой сессии даже генерация схемы не выполнялась, поэтому файл generated OpenAPI не прилагается как якобы полученный результат. Сам source настройка сохранена полностью, а ожидаемые адреса указаны с честным статусом not_run.
При production-развитии нужно решить, где и кому доступна документация, какие серверные адреса она объявляет и как отделить public tokens лаборатории от настоящего удостоверения. Эти решения не покрыты loopback setup автоматически. Последняя глава соберёт конфигурацию запуска, lifecycle и границы передачи. Схема уже даёт структурированное описание выбранного wire-контракта, сохраняя различие между документированным намерением и фактически проверенной реализацией.
Статус примера — not_run. Зависимости, приложение, SQL, HTTP и тесты не запускались. Описанные ответы и изменения интерфейса ожидаемые; чтение документации не является проверкой выполнением. Полная конечная версия находится в архиве исходников, а её ограничения и действия читателя — в README и CONTRACT.md.
Перейдите к оглавлению серии или общему маршруту интерактивной веб-разработки. Механизм этой главы сверяется с официальным первичным источником; конкретные версии и непроверенные части перечислены в договоре курса.