NestJS course Β· Module 11: Swagger and OpenAPI
Advanced responses - imperial edicts
In this lesson7
An imperial edict did not end with the order. It always listed the consequences: what happens once the command is carried out, what if the petitioner has no right to ask, and what if the matter does not appear in the annals at all. Only such a document could be applied without sending back to the chancery for a ruling.
Your API works the same way. In the previous lesson we documented a response with the general @ApiResponse, passing the status code by hand. But NestJS has a separate decorator for each code - and this lesson is about them.
The decorator describes, it does not set
Let us begin with something that often causes confusion. These decorators do not change the application's behaviour. They go into the documentation and nowhere else:
1@ApiResponse({ status: 200, description: 'List of legions' })
2@Get()
3findAll() {}
4
5@ApiOkResponse({ description: 'List of legions' })
6@Get()
7findAll() {}Both forms produce exactly the same thing in Swagger UI. The second is shorter and harder to get wrong, because you do not supply the status code - it sits in the decorator's name.
What the client actually receives is settled elsewhere: @Post returns 201 by default, the other methods 200, and @HttpCode(204) overrides that explicitly. Adding @ApiCreatedResponse above a @Get method will not make it return 201 - it will only make the documentation lie.
The code lives in the decorator's name
The rule is simple: the decorator's name is the official name of the HTTP code. The whole set can therefore be reconstructed from memory:
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';@ApiCreatedResponse() corresponds to code 201 (Created), and @ApiNotFoundResponse() represents code 404 Not Found - not 400, not 401 and not 403, because each of those has its own decorator in the list above.
The rule works in the other direction too, and lets you reject names that do not exist. @ApiNewResponse() and @ApiSuccessResponse() are not real - they sound sensible, but HTTP has no code named "New" or "Success". If you cannot name the code a decorator refers to, that is a sign the decorator does not exist.
Three classes of code
The codes fall into classes, and the class tells you whose fault it is:
- 2xx - success.
200 OKmeans "done";201 Createdmeans "done, and a new resource exists". - 4xx - the client's fault. The request was malformed or not permitted; repeating it unchanged will achieve nothing.
- 5xx - the server's fault.
500 Internal Server Errormeans the application fell over. The request may have been perfectly correct.
Ordered from success to server error, they run: 200 OK β 201 Created β 401 Unauthorized β 500 Internal Server Error.
Within 4xx one pair is worth committing to memory. 401 Unauthorized means "I do not know who you are" - the token is missing or invalid. 403 Forbidden means "I know who you are, and you may not" - the token is fine, but the role is not enough. The first is fixed by signing in; the second cannot be fixed at all.
The shape of the decorator
The decorator takes an object, and the order of its fields is conventional though in practice always the same - description first, then type:
1@ApiOkResponse({
2 description: 'List of legions',
3 type: LegionResponseDto,
4})
5@Get()
6findAll(): LegionResponseDto[] {
7 return this.legionService.findAll();
8}It reads in four steps: @ApiOkResponse({ opens the decorator, description: 'List of legions', explains to a human what they will get, type: LegionResponseDto names the DTO class, and }) closes it.
description becomes the response's description in Swagger UI. type does more: Swagger reaches for the @ApiProperty decorators on that class - the ones from the previous lesson - and builds a full response schema from them, example values included. Without type a reader learns they will get a 200, but not what is inside it.
A response that is an array
When an endpoint returns a list you have two equivalent forms, and both work correctly:
1@ApiOkResponse({ type: [LegionResponseDto] })
2@Get()
3findAll() {}
4
5@ApiOkResponse({ type: LegionResponseDto, isArray: true })
6@Get()
7findAllAgain() {}The first - the class in square brackets - is shorter. The second, with an explicit isArray: true, can read more clearly when other options stand alongside it. The choice is a matter of style; Swagger generates an identical schema.
What will not work is type: Array<LegionResponseDto>. The reason runs deeper than syntax: generic types disappear when TypeScript is compiled. A decorator receives a value that exists at run time, and after compilation Array<LegionResponseDto> leaves nothing but Array - with no trace of what it is an array of. Square brackets and isArray work precisely because they pass the LegionResponseDto class as an ordinary value.
The full set on one endpoint
In practice an endpoint documents every response a client might meet:
1@ApiOperation({ summary: 'Promote a legionary' })
2@ApiOkResponse({
3 description: 'The legionary has been promoted',
4 type: LegionaryResponseDto,
5})
6@ApiBadRequestResponse({ description: 'Invalid target rank' })
7@ApiUnauthorizedResponse({ description: 'No valid JWT token' })
8@ApiNotFoundResponse({ description: 'Legionary not found' })
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}Note the pair @ApiBearerAuth('JWT-auth') and @ApiUnauthorizedResponse. The first marks the endpoint as protected and adds a token field in Swagger UI - the name 'JWT-auth' must match the one given when configuring addBearerAuth. The second describes what happens without a token. A protected endpoint without @ApiUnauthorizedResponse promises the reader that a 401 will never occur - and one will, on the first attempt.
Summary
An edict lists its consequences:
- response decorators only document; the status code is set by
@Post,@Getor@HttpCode, - the decorator's name is the code's name:
@ApiOkResponse(200),@ApiCreatedResponse(201),@ApiBadRequestResponse(400),@ApiUnauthorizedResponse(401),@ApiForbiddenResponse(403),@ApiNotFoundResponse(404),@ApiInternalServerErrorResponse(500), @ApiNewResponseand@ApiSuccessResponsedo not exist - there are no HTTP codes by those names,- classes of code: 2xx success, 4xx the client's fault, 5xx the server's fault; from success to server error: 200 OK β 201 Created β 401 Unauthorized β 500 Internal Server Error,
- 401 is "I do not know who you are", 403 is "I know, and you may not",
- the decorator's shape:
@ApiOkResponse({βdescription: 'List of legions',βtype: LegionResponseDtoβ}), typeties the response to a DTO, so Swagger builds a schema from its@ApiPropertydecorators,- an array is written in two equivalent ways:
type: [LegionDto]ortype: LegionDto, isArray: true- both work correctly, type: Array<LegionDto>will not work, because generic types disappear at compilation,- a protected endpoint is marked with the pair
@ApiBearerAuth('JWT-auth')and@ApiUnauthorizedResponse.
In the next lesson we shall turn to the appearance of the Forum of Annals itself - configuring Swagger UI. For now remember: response documentation is not ornament. It is the only place a client learns what to prepare for, before their code finds out the hard way.
Code for this lesson: src/tributum.controller.ts
1// Advanced response decorators
2import { Controller, Get, Post, Param, Body, Delete, UseGuards } from '@nestjs/common';
3import {
4 ApiTags, ApiOperation, ApiBearerAuth,
5 ApiOkResponse, ApiCreatedResponse,
6 ApiNotFoundResponse, ApiBadRequestResponse,
7 ApiUnauthorizedResponse,
8} from '@nestjs/swagger';
9
10@ApiTags('Tributum')
11@Controller('tributum')
12export class TributumController {
13
14 // TODO: Add @ApiOperation with summary
15 // TODO: Add @ApiOkResponse with description
16 @Get()
17 findAll() {
18 return [
19 { id: '1', province: 'Pannonia', amount: 50000, status: 'collected' },
20 { id: '2', province: 'Britannia', amount: 30000, status: 'pending' },
21 ];
22 }
23
24 // TODO: Add @ApiOperation with summary
25 // TODO: Add @ApiCreatedResponse with description
26 // TODO: Add @ApiBadRequestResponse with description
27 @Post()
28 create(@Body() dto: any) {
29 return { id: '3', ...dto, status: 'pending' };
30 }
31
32 // TODO: Add @ApiOperation with summary
33 // TODO: Add @ApiOkResponse with description
34 // TODO: Add @ApiNotFoundResponse with description
35 @Get(':id')
36 findOne(@Param('id') id: string) {
37 return { id, province: 'Pannonia', amount: 50000 };
38 }
39
40 // TODO: Add @ApiBearerAuth('JWT-auth')
41 // TODO: Add @ApiOperation with summary
42 // TODO: Add @ApiOkResponse with description
43 // TODO: Add @ApiNotFoundResponse with description
44 // TODO: Add @ApiUnauthorizedResponse with description
45 @Delete(':id')
46 remove(@Param('id') id: string) {
47 return { message: 'Tribute cancelled', id };
48 }
49}
50
51console.log("Specialized response decorators:");
52console.log("@ApiOkResponse - 200 OK");
53console.log("@ApiCreatedResponse - 201 Created");
54console.log("@ApiNotFoundResponse - 404 Not Found");
55console.log("@ApiBadRequestResponse - 400 Bad Request");
56console.log("@ApiUnauthorizedResponse - 401 Unauthorized");
57Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. Which Swagger decorator corresponds to HTTP status code 201 (Created)?
2. What HTTP status code does the @ApiNotFoundResponse() decorator represent?
These are 2 of 3 questions for this lesson. Solve the rest in the game.
Hands-on tasks in the game
- Code editor
Add @ApiOkResponse, @ApiCreatedResponse, @ApiNotFoundResponse, and @ApiBadRequestResponse to the endpoints
- Vertical ordering
Order the HTTP status codes from success to server error:
- Click in order
Arrange the elements of the @ApiOkResponse decorator with a DTO type:
- Code editor
Mark endpoints as JWT-protected using @ApiBearerAuth('JWT-auth') and @ApiUnauthorizedResponse