Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Dekoratory kontrolerów - pieczęcie na dekretach

Dekret bez pieczęci to kartka. Nie wiadomo, kto go wydał, czego dotyczy ani czy w ogóle obowiązuje - a urzędnik w prowincji musi to rozstrzygnąć, zanim cokolwiek zrobi.

Endpoint bez opisu jest dokładnie taki sam. Swagger sam odczyta z kodu ścieżkę i metodę HTTP, ale nie odgadnie, co ten endpoint robi, które parametry są obowiązkowe ani co oznacza zwrócony przez niego kod. Dekoratory z pakietu

@nestjs/swagger
dopisują to, czego z samego kodu nie widać.

Ważna uwaga na początek: wszystkie te dekoratory tylko opisują. Żaden nie zmienia zachowania aplikacji - endpoint bez

@ApiOperation
działa tak samo, po prostu w dokumentacji jest bezimienny.

@ApiTags i @ApiOperation

Pierwsze dwie pieczęcie odpowiadają na pytania „gdzie to szukać" i „co to robi":

1@ApiTags('Prowincje')
2@Controller('provinces')
3export class ProvincesController {
4  @ApiOperation({
5    summary: 'Lista prowincji',
6    description: 'Zwraca wszystkie prowincje Imperium wraz z ich namiestnikami.',
7  })
8  @Get()
9  findAll() {
10    return this.provincesService.findAll();
11  }
12}

@ApiTags()
grupuje endpointy w sekcje - w Swagger UI powstaje z tego rozwijana grupa. Stawiasz go nad klasą kontrolera, bo grupowanie dotyczy wszystkich jego metod naraz. Można podać kilka tagów po przecinku, gdy kontroler należy do dwóch obszarów.

@ApiOperation()
przyjmuje dwie główne właściwości:
summary
i
description
.
Nie
url
i
method
- te NestJS odczytuje z
@Get
i
@Controller
. Nie
host
i
port
- to konfiguracja serwera, nie opis endpointu. I nie
controller
ani
service
- to nazwy klas, o których dokumentacja nie musi wiedzieć.

Różnica między nimi jest praktyczna:

summary
to jedno zdanie widoczne na liście endpointów,
description
- dłuższy tekst rozwijany po kliknięciu. Jeśli masz napisać tylko jedno, pisz
summary
.

@ApiParam kontra @ApiQuery

Te dwa dekoratory mylą się najczęściej, bo oba opisują „coś, co przychodzi w adresie". Rozstrzyga miejsce w URL-u:

1@ApiParam({ name: 'id', description: 'Identyfikator prowincji' })
2@Get(':id')
3findOne(@Param('id') id: string) {
4  return this.provincesService.findOne(id);
5}

@ApiParam()
opisuje parametry w ścieżce URL (route parameters) - te, które w trasie zapisujesz z dwukropkiem, jak
:id
, i które są częścią adresu. W
/provinces/7
siódemka jest parametrem ścieżki.

@ApiQuery()
opisuje parametry zapytania - te po znaku
?
w URL.
W
/provinces?region=Gallia&page=2
parametrami zapytania są
region
i
page
. Nie wykonuje żadnego zapytania do bazy danych, nie definiuje schematu GraphQL i nie tworzy query buildera dla ORM-a - słowo „query" odnosi się tu wyłącznie do części adresu.

Pozostałe miejsca mają własne dekoratory: nagłówki HTTP opisuje

@ApiHeader
, a ciało żądania (request body) -
@ApiBody
. Cztery różne miejsca, cztery różne dekoratory; pomyłka nie da błędu, tylko dokumentację opisującą coś, czego tam nie ma.

Budowa @ApiQuery

Parametry zapytania bywają opcjonalne i właśnie to trzeba w dokumentacji zaznaczyć:

1@ApiQuery({
2  name: 'province',
3  required: false,
4})
5@Get('search')
6search(@Query('province') province?: string) {
7  return this.provincesService.search(province);
8}

Kolejność zapisu jest stała:

@ApiQuery({
otwiera dekorator,
name: 'province',
podaje nazwę parametru - to jedyne pole obowiązkowe -
required: false
oznacza go jako opcjonalny, a
})
domyka.

required
domyślnie ma wartość
true
, więc opcjonalność trzeba zaznaczyć jawnie. Pominięcie tego jest drobiazgiem, który kosztuje cudzy czas: w Swagger UI parametr pokaże się jako wymagany, a ktoś zbuduje klienta wysyłającego
region
przy każdym wywołaniu.

Kolejność na endpointcie

Gdy zbierzemy wszystko razem, dekoratory układają się od najogólniejszego do najbardziej szczegółowego:

1@ApiTags('Prowincje')
2@Controller('provinces')
3export class ProvincesController {
4  @ApiOperation({ summary: 'Szczegóły prowincji' })
5  @ApiResponse({ status: 200, description: 'Prowincja odnaleziona' })
6  @ApiResponse({ status: 404, description: 'Nie ma takiej prowincji' })
7  @ApiParam({ name: 'id', description: 'Identyfikator prowincji' })
8  @ApiBearerAuth('JWT-auth')
9  @Get(':id')
10  findOne(@Param('id') id: string) {
11    return this.provincesService.findOne(id);
12  }
13}

Czyta się to z góry na dół:

@ApiTags('Nazwa')
na klasie kontrolera obejmuje wszystkie metody, potem nad konkretną metodą
@ApiOperation({ summary })
mówi, co ona robi, następnie
@ApiResponse({ status })
wylicza możliwe odpowiedzi, a na końcu wykonuje się ciało metody.

@ApiResponse()
przyjmuje kod statusu i opis; można go podać wiele razy, po jednym na każdą możliwą odpowiedź. W następnych lekcjach zastąpimy go krótszymi odpowiednikami w rodzaju
@ApiOkResponse
.

@ApiBearerAuth('JWT-auth')
oznacza endpoint jako chroniony - w Swagger UI pojawi się przy nim pole na token, a podana nazwa musi zgadzać się z konfiguracją
addBearerAuth
.

Podsumowanie

Dekret bez pieczęci to kartka, @name:

  • dekoratory Swagger tylko opisują - nie zmieniają zachowania endpointu,
  • @ApiTags()
    grupuje endpointy
    i stoi nad klasą kontrolera,
  • @ApiOperation()
    przyjmuje
    summary
    i
    description
    - nie
    url
    /
    method
    , nie
    host
    /
    port
    , nie
    controller
    /
    service
    ,
  • @ApiParam()
    opisuje parametry w ścieżce URL
    (te z dwukropkiem, jak
    :id
    ),
  • @ApiQuery()
    opisuje parametry zapytania po znaku
    ?
    - nie odpytuje bazy, nie definiuje GraphQL-a, nie buduje zapytań ORM,
  • nagłówki opisuje
    @ApiHeader
    , ciało żądania -
    @ApiBody
    ,
  • budowa
    @ApiQuery
    :
    @ApiQuery({
    name: 'province',
    required: false
    })
    ; bez
    required: false
    parametr uchodzi za wymagany,
  • kolejność:
    @ApiTags('Nazwa')
    na klasie →
    @ApiOperation({ summary })
    nad metodą →
    @ApiResponse({ status })
    nad metodą → ciało metody,
  • @ApiBearerAuth('JWT-auth')
    oznacza endpoint chroniony tokenem.

W następnej lekcji zejdziemy poziom niżej - do dokumentowania samych DTO, czyli tego, co w tych żądaniach i odpowiedziach naprawdę siedzi. A na razie zapamiętaj: dokumentacja opisuje to, co napisałeś w dekoratorze, nie to, co robi kod. Rozjazd między nimi jest gorszy niż brak dokumentacji, bo w ten pierwszy ktoś uwierzy.

Przejdź do CodeWorlds