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.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 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';
corresponds to code 201 (Created), and @ApiCreatedResponse()
represents code 404 Not Found - not 400, not 401 and not 403, because each of those has its own decorator in the list above.@ApiNotFoundResponse()
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.The codes fall into classes, and the class tells you whose fault it is:
200 OK means "done"; 201 Created means "done, and a new resource exists".500 Internal Server Error means 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 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:
opens the decorator, @ApiOkResponse({
explains to a human what they will get, description: 'List of legions',
names the DTO class, and type: LegionResponseDto
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.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
, can read more clearly when other options stand alongside it. The choice is a matter of style; Swagger generates an identical schema.isArray: true
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.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
and @ApiBearerAuth('JWT-auth')
. The first marks the endpoint as protected and adds a token field in Swagger UI - the name @ApiUnauthorizedResponse
'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.An edict lists its consequences, @name:
@Post, @Get or @HttpCode,@ApiOkResponse (200), @ApiCreatedResponse (201), @ApiBadRequestResponse (400), @ApiUnauthorizedResponse (401), @ApiForbiddenResponse (403), @ApiNotFoundResponse (404), @ApiInternalServerErrorResponse (500),@ApiNewResponse and @ApiSuccessResponse do not exist - there are no HTTP codes by those names,@ApiOkResponse({ → description: 'List of legions', → type: LegionResponseDto → }),type ties the response to a DTO, so Swagger builds a schema from its @ApiProperty decorators,type: [LegionDto] or type: LegionDto, isArray: true - both work correctly,type: Array<LegionDto> will not work, because generic types disappear at compilation,@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.