We use cookies to enhance your experience on the site
CodeWorlds

Advanced responses - imperial edicts

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 OK
    means "done";
    201 Created
    means "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 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 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, @name:

  • response decorators only document; the status code is set by
    @Post
    ,
    @Get
    or
    @HttpCode
    ,
  • the decorator's name is the code's name:
    @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,
  • 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
    })
    ,
  • type
    ties the response to a DTO, so Swagger builds a schema from its
    @ApiProperty
    decorators,
  • an array is written in two equivalent ways:
    type: [LegionDto]
    or
    type: 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.

Go to CodeWorlds