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.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}
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.@ApiTags()
accepts two main properties: @ApiOperation()
and summary
. Not description
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.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}
describes route parameters in the URL path - the ones written with a colon in the route, like @ApiParam()
:id, which form part of the address. In /provinces/7 the seven is a route parameter.
describes query parameters - those after the @ApiQuery()
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.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:
opens the decorator, @ApiQuery({
gives the parameter's name - the only obligatory field - name: 'province',
marks it optional, and required: false
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.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:
on the controller class covers every method, then above the particular method @ApiTags('Name')
says what it does, next @ApiOperation({ summary })
lists the possible answers, and finally the method body runs.@ApiResponse({ status })
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 @ApiResponse()
@ApiOkResponse.
marks the endpoint as protected - a token field appears beside it in Swagger UI, and the name given must match the @ApiBearerAuth('JWT-auth')
addBearerAuth configuration.A decree without a seal is a sheet of paper, @name:
@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,@ApiHeader, the request body by @ApiBody,@ApiQuery: @ApiQuery({ → name: 'province', → required: false → }); without required: false a parameter passes for mandatory,@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.