We use cookies to enhance your experience on the site
CodeWorlds

Controller decorators - seals on the decrees

A decree without a seal is a sheet of paper. There is no telling who issued it, what it concerns, or whether it is in force at all - and an official in the provinces must settle that before doing anything.

An endpoint without a description is exactly the same. Swagger will read the path and the HTTP method out of the code by itself, but it cannot guess what the endpoint does, which parameters are required, or what the code it returns signifies. The decorators of the

@nestjs/swagger
package add what the code alone does not show.

An important note at the outset: all these decorators only describe. None of them changes the application's behaviour - an endpoint without

@ApiOperation
works exactly as before, it is merely nameless in the documentation.

@ApiTags and @ApiOperation

The first two seals answer the questions "where do I find this" and "what does it do":

1@ApiTags('Provinces')
2@Controller('provinces')
3export class ProvincesController {
4  @ApiOperation({
5    summary: 'List of provinces',
6    description: 'Returns every province of the Empire together with its governor.',
7  })
8  @Get()
9  findAll() {
10    return this.provincesService.findAll();
11  }
12}

@ApiTags()
groups endpoints into sections - in Swagger UI this becomes a collapsible group. You place it above the controller class, because the grouping applies to all its methods at once. Several tags may be listed, separated by commas, when a controller belongs to two areas.

@ApiOperation()
accepts two main properties:
summary
and
description
.
Not
url
and
method
- NestJS reads those from
@Get
and
@Controller
. Not
host
and
port
- that is server configuration, not a description of an endpoint. And not
controller
or
service
- those are class names the documentation need know nothing about.

The difference between them is practical:

summary
is the single sentence shown in the endpoint list,
description
the longer text revealed on clicking. If you are to write only one, write the
summary
.

@ApiParam versus @ApiQuery

These two are the most often confused, because both describe "something arriving in the address". The place in the URL settles it:

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

@ApiParam()
describes route parameters in the URL path - the ones written with a colon in the route, like
:id
, which form part of the address. In
/provinces/7
the seven is a route parameter.

@ApiQuery()
describes query parameters - those after the
?
sign in the URL.
In
/provinces?region=Gallia&page=2
the query parameters are
region
and
page
. It executes no database query, defines no GraphQL schema and creates no query builder for an ORM - the word "query" here refers solely to a part of the address.

The remaining places have decorators of their own: HTTP headers are described by

@ApiHeader
, and the request body by
@ApiBody
. Four different places, four different decorators; a mix-up produces no error, only documentation describing something that is not there.

The shape of @ApiQuery

Query parameters are often optional, and that is precisely what the documentation must record:

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

The written order is fixed:

@ApiQuery({
opens the decorator,
name: 'province',
gives the parameter's name - the only obligatory field -
required: false
marks it optional, and
})
closes it.

required
defaults to
true
, so optionality must be stated explicitly. Omitting it is a small thing that costs somebody else's time: in Swagger UI the parameter appears as required, and someone builds a client that sends
region
on every call.

The order on an endpoint

Gathered together, the decorators run from the most general to the most particular:

1@ApiTags('Provinces')
2@Controller('provinces')
3export class ProvincesController {
4  @ApiOperation({ summary: 'Province details' })
5  @ApiResponse({ status: 200, description: 'Province found' })
6  @ApiResponse({ status: 404, description: 'No such province' })
7  @ApiParam({ name: 'id', description: 'The province identifier' })
8  @ApiBearerAuth('JWT-auth')
9  @Get(':id')
10  findOne(@Param('id') id: string) {
11    return this.provincesService.findOne(id);
12  }
13}

It reads from top to bottom:

@ApiTags('Name')
on the controller class covers every method, then above the particular method
@ApiOperation({ summary })
says what it does, next
@ApiResponse({ status })
lists the possible answers, and finally the method body runs.

@ApiResponse()
takes a status code and a description; it may be given several times, once for each possible response. In the coming lessons we shall replace it with shorter equivalents such as
@ApiOkResponse
.

@ApiBearerAuth('JWT-auth')
marks the endpoint as protected - a token field appears beside it in Swagger UI, and the name given must match the
addBearerAuth
configuration.

Summary

A decree without a seal is a sheet of paper, @name:

  • Swagger decorators only describe - they do not change an endpoint's behaviour,
  • @ApiTags()
    groups endpoints
    and stands above the controller class,
  • @ApiOperation()
    accepts
    summary
    and
    description
    - not
    url
    /
    method
    , not
    host
    /
    port
    , not
    controller
    /
    service
    ,
  • @ApiParam()
    describes route parameters in the URL path
    (those with a colon, like
    :id
    ),
  • @ApiQuery()
    describes query parameters after the
    ?
    sign
    - it does not query a database, define GraphQL, or build ORM queries,
  • headers are described by
    @ApiHeader
    , the request body by
    @ApiBody
    ,
  • the shape of
    @ApiQuery
    :
    @ApiQuery({
    name: 'province',
    required: false
    })
    ; without
    required: false
    a parameter passes for mandatory,
  • the order:
    @ApiTags('Name')
    on the class →
    @ApiOperation({ summary })
    above the method →
    @ApiResponse({ status })
    above the method → the method body,
  • @ApiBearerAuth('JWT-auth')
    marks an endpoint as token-protected.

In the next lesson we go a level deeper - to documenting the DTOs themselves, that is, what actually sits inside those requests and responses. For now remember: the documentation describes what you wrote in the decorator, not what the code does. A divergence between them is worse than no documentation, because the first one somebody will believe.

Go to CodeWorlds