Edykt cesarski nie kończył się na rozkazie. Zawsze wymieniał następstwa: co się stanie, gdy polecenie wykonano, co gdy petent nie ma prawa prosić, a co gdy sprawa w ogóle nie istnieje w annałach. Dopiero taki dokument dawał się stosować bez pytania kancelarii o zdanie.
Twoje API działa tak samo. W poprzedniej lekcji dokumentowaliśmy odpowiedź ogólnym
@ApiResponse, podając kod statusu ręcznie. NestJS ma jednak osobny dekorator dla każdego kodu - i o nich jest ta lekcja.Zacznijmy od rzeczy, która bywa źródłem nieporozumień. Te dekoratory nie zmieniają zachowania aplikacji. Trafiają wyłącznie do dokumentacji:
1@ApiResponse({ status: 200, description: 'Lista legionów' })
2@Get()
3findAll() {}
4
5@ApiOkResponse({ description: 'Lista legionów' })
6@Get()
7findAll() {}Oba zapisy dają w Swagger UI dokładnie to samo. Drugi jest krótszy i trudniej się w nim pomylić, bo kodu statusu nie podajesz - siedzi on w nazwie dekoratora.
O tym, jaki kod naprawdę wróci do klienta, decyduje co innego:
@Post domyślnie zwraca 201, pozostałe metody 200, a @HttpCode(204) nadpisuje to jawnie. Dopisanie @ApiCreatedResponse nad metodą @Get nie sprawi, że zacznie ona zwracać 201 - sprawi tylko, że dokumentacja skłamie.Reguła jest prosta: nazwa dekoratora to oficjalna nazwa kodu HTTP. Stąd cały zestaw daje się odtworzyć z pamięci:
1import {
2 ApiOkResponse, // 200 OK
3 ApiCreatedResponse, // 201 Created
4 ApiBadRequestResponse, // 400 Bad Request
5 ApiUnauthorizedResponse, // 401 Unauthorized
6 ApiForbiddenResponse, // 403 Forbidden
7 ApiNotFoundResponse, // 404 Not Found
8 ApiInternalServerErrorResponse, // 500 Internal Server Error
9} from '@nestjs/swagger';
odpowiada kodowi 201 (Created), a @ApiCreatedResponse()
reprezentuje kod 404 Not Found - nie 400, nie 401 i nie 403, bo każdy z nich ma własny dekorator z listy powyżej.@ApiNotFoundResponse()
Reguła działa też w drugą stronę i pozwala odrzucić nazwy, których nie ma.
@ApiNewResponse() ani @ApiSuccessResponse() nie istnieją - brzmią sensownie, ale w protokole HTTP nie ma kodu o nazwie „New" ani „Success". Jeśli nie umiesz wskazać kodu, do którego dekorator się odnosi, to znak, że taki dekorator nie istnieje.Kody układają się w klasy, a klasa mówi, kto zawinił:
200 OK to „zrobione", 201 Created - „zrobione i powstał nowy zasób".500 Internal Server Error oznacza, że aplikacja się wywróciła. Żądanie mogło być całkiem poprawne.Uporządkowane od powodzenia do błędu serwera dają kolejność: 200 OK → 201 Created → 401 Unauthorized → 500 Internal Server Error.
W obrębie 4xx warto zapamiętać jedną parę. 401 Unauthorized znaczy „nie wiem, kim jesteś" - brakuje tokenu albo jest nieważny. 403 Forbidden znaczy „wiem, kim jesteś, i nie wolno ci" - token jest w porządku, ale rola nie wystarcza. Pierwsze naprawia się logowaniem, drugiego nie naprawi się wcale.
Dekorator przyjmuje obiekt, a kolejność jego pól jest umowna, choć w praktyce zawsze ta sama - najpierw opis, potem typ:
1@ApiOkResponse({
2 description: 'Lista legionów',
3 type: LegionResponseDto,
4})
5@Get()
6findAll(): LegionResponseDto[] {
7 return this.legionService.findAll();
8}Czyta się to w czterech krokach:
otwiera dekorator, @ApiOkResponse({
wyjaśnia człowiekowi, co dostanie, description: 'Lista legionów',
wskazuje klasę DTO, a type: LegionResponseDto
domyka.})
description trafia do opisu odpowiedzi w Swagger UI. type robi więcej: Swagger sięga po dekoratory @ApiProperty z tej klasy - te z poprzedniej lekcji - i buduje z nich pełny schemat odpowiedzi wraz z przykładowymi wartościami. Bez type czytelnik dowie się, że dostanie 200, ale nie dowie się, co w środku.Gdy endpoint zwraca listę, masz dwa równoważne zapisy i oba działają poprawnie:
1@ApiOkResponse({ type: [LegionResponseDto] })
2@Get()
3findAll() {}
4
5@ApiOkResponse({ type: LegionResponseDto, isArray: true })
6@Get()
7findAllAgain() {}Pierwszy - klasa w nawiasach kwadratowych - jest krótszy. Drugi, z jawnym
, bywa czytelniejszy, gdy obok stoją inne opcje. Wybór jest kwestią stylu; Swagger wygeneruje identyczny schemat.isArray: true
Nie zadziała natomiast
type: Array<LegionResponseDto>. Powód jest głębszy niż składnia: typy generyczne znikają przy kompilacji TypeScriptu. Dekorator dostaje wartość istniejącą w czasie działania programu, a z Array<LegionResponseDto> zostaje po kompilacji samo Array - bez śladu po tym, czego jest tablicą. Nawiasy kwadratowe i isArray działają właśnie dlatego, że przekazują klasę LegionResponseDto jako zwykłą wartość.W praktyce endpoint dokumentuje wszystkie odpowiedzi, jakich klient może się spodziewać:
1@ApiOperation({ summary: 'Awansuj legionistę' })
2@ApiOkResponse({
3 description: 'Legionista został awansowany',
4 type: LegionaryResponseDto,
5})
6@ApiBadRequestResponse({ description: 'Nieprawidłowa ranga docelowa' })
7@ApiUnauthorizedResponse({ description: 'Brak ważnego tokenu JWT' })
8@ApiNotFoundResponse({ description: 'Legionista nie znaleziony' })
9@ApiBearerAuth('JWT-auth')
10@UseGuards(JwtAuthGuard)
11@Patch(':id/promote')
12promote(@Param('id') id: string, @Body() dto: PromoteLegionaryDto) {
13 return this.legionService.promote(id, dto);
14}Zwróć uwagę na parę
i @ApiBearerAuth('JWT-auth')
. Pierwszy zaznacza endpoint jako chroniony i włącza w Swagger UI pole na token - nazwa @ApiUnauthorizedResponse
'JWT-auth' musi zgadzać się z tą podaną przy konfiguracji addBearerAuth. Drugi opisuje, co się stanie bez tokenu. Chroniony endpoint bez @ApiUnauthorizedResponse obiecuje czytelnikowi, że 401 nigdy nie nastąpi - a nastąpi przy pierwszej próbie.Edykt wymienia następstwa, @name:
@Post, @Get albo @HttpCode,@ApiOkResponse (200), @ApiCreatedResponse (201), @ApiBadRequestResponse (400), @ApiUnauthorizedResponse (401), @ApiForbiddenResponse (403), @ApiNotFoundResponse (404), @ApiInternalServerErrorResponse (500),@ApiNewResponse i @ApiSuccessResponse nie istnieją - nie ma kodów HTTP o takich nazwach,@ApiOkResponse({ → description: 'Lista legionów', → type: LegionResponseDto → }),type wiąże odpowiedź z DTO, więc Swagger zbuduje schemat z jego @ApiProperty,type: [LegionDto] albo type: LegionDto, isArray: true - oba działają poprawnie,type: Array<LegionDto> nie zadziała, bo typy generyczne znikają przy kompilacji,@ApiBearerAuth('JWT-auth') i @ApiUnauthorizedResponse.W następnej lekcji zajmiemy się wyglądem samego Forum Annałów - konfiguracją Swagger UI. A na razie zapamiętaj: dokumentacja odpowiedzi nie jest ozdobnikiem. To jedyne miejsce, w którym klient dowie się, na co ma się przygotować, zanim jego kod się o tym przekona.