Kurs NestJS · Moduł 11: Swagger i OpenAPI
Dekoratory kontrolerów - pieczęcie na dekretach
W tej lekcji5
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:
- dekoratory Swagger tylko opisują - nie zmieniają zachowania endpointu,
@ApiTags()grupuje endpointy i stoi nad klasą kontrolera,@ApiOperation()przyjmujesummaryidescription- nieurl/method, niehost/port, niecontroller/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→}); bezrequired: falseparametr 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.
Kod do tej lekcji: src/province.controller.ts
1// Dekoratory Swagger na kontrolerze prowincji
2import { Controller, Get, Post, Param, Body, Query } from '@nestjs/common';
3import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from '@nestjs/swagger';
4
5// TODO: Dodaj dekorator @ApiTags('Provinciae')
6@Controller('provinciae')
7export class ProvinceController {
8
9 // TODO: Dodaj @ApiOperation z summary i description
10 // TODO: Dodaj @ApiResponse dla statusu 200
11 @Get()
12 findAll() {
13 return [
14 { name: 'Pannonia', governor: 'Lucius Verus' },
15 { name: 'Britannia', governor: 'Agricola' },
16 { name: 'Africa', governor: 'Septimius' },
17 ];
18 }
19
20 // TODO: Dodaj @ApiOperation z summary
21 // TODO: Dodaj @ApiParam opisujacy parametr 'id'
22 // TODO: Dodaj @ApiResponse dla 200 i 404
23 @Get(':id')
24 findOne(@Param('id') id: string) {
25 return { name: 'Pannonia', governor: 'Lucius Verus', legions: 3 };
26 }
27
28 // TODO: Dodaj @ApiOperation z summary
29 // TODO: Dodaj @ApiQuery dla 'region' (opcjonalne)
30 // TODO: Dodaj @ApiQuery dla 'minLegions' (opcjonalne, typ Number)
31 @Get('search')
32 search(
33 @Query('region') region?: string,
34 @Query('minLegions') minLegions?: number,
35 ) {
36 return [{ name: 'Pannonia', region: 'Europa', legions: 3 }];
37 }
38
39 // TODO: Dodaj @ApiOperation z summary
40 // TODO: Dodaj @ApiResponse dla 201
41 @Post()
42 create(@Body() dto: any) {
43 return { id: '1', ...dto };
44 }
45}
46
47console.log("Dekoratory kontrolerow Swagger:");
48console.log("@ApiTags('nazwa') - grupuje endpointy");
49console.log("@ApiOperation({ summary, description })");
50console.log("@ApiResponse({ status, description })");
51console.log("@ApiParam({ name, description, example })");
52console.log("@ApiQuery({ name, required, description })");
53Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Jakie główne właściwości przyjmuje dekorator @ApiOperation()?
2. Kiedy używamy dekoratora @ApiParam() w kontrolerze NestJS?
To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Edytor kodu
Dodaj @ApiQuery do endpointu wyszukiwania i @ApiParam do endpointu z parametrem :id
- Układanie w pionie
Uporządkuj dekoratory Swagger na endpointcie od dekoratora klasy do ciała metody:
- Klikanie w kolejności
Ułóż elementy dekoratora @ApiQuery w prawidłowej kolejności:
- Edytor kodu
Dodaj @ApiTags, @ApiOperation, @ApiResponse, @ApiParam i @ApiQuery do kontrolera prowincji