[NestJS 带你飞!] DAY27 - Swagger (下)

API 操作设计

上一篇我们让 API 的参数能够顺利显示在 Swagger UI 中,在设计完参数之後,我们可以针对 API 做分类,甚至把请求与回应的相关内容也设计至 Swagger UI 上。

Tags

这个功能是用来将 API 进行分类,透过 @ApiTags 装饰器就可以将特定的 Controller 打上标签,在 Swagger UI 上更容易找到对应的 API。

我们修改一下 TodoController 的内容,将 @ApiTags 套用上去,并指定标签为 Todo

import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { ApiBody, ApiTags } from '@nestjs/swagger';

import { TodoService } from './todo.service';

import { CreateTodoDto } from './dto/create-todo.dto';

// 添加标签
@ApiTags('Todo')
@Controller('todos')
export class TodoController {
  constructor(private readonly todoService: TodoService) {}

  @Post()
  createTodo(@Body() data: CreateTodoDto) {
    return this.todoService.createTodo(data);
  }

  @ApiBody({ type: [CreateTodoDto] })
  @Post('bulk')
  createTodos(@Body() todos: CreateTodoDto[]) {
    return todos.map((todo) => this.todoService.createTodo(todo));
  }

  @Get(':id')
  getTodo(@Param('id') id: string) {
    return this.todoService.getTodo(id);
  }
}

透过浏览器查看 http://localhost:3000/api 会发现多了一个 Todo 的区块,里面有 TodoController 设计的 API:

https://ithelp.ithome.com.tw/upload/images/20210802/20119338pF4mkIWrxX.png

Headers

这个功能让 API 在 Swagger UI 上可以有一个介面去配置特定 Header 的内容,透过 @ApiHeader 装饰器给定 name 属性,就能在 UI 上看到该栏位。

修改一下 TodoController 的内容,在 getTodo 套用 @ApiHeader 装饰器,并指定 nameX-Custom

import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { ApiBody, ApiHeader, ApiTags } from '@nestjs/swagger';

import { TodoService } from './todo.service';

import { CreateTodoDto } from './dto/create-todo.dto';

@ApiTags('Todo')
@Controller('todos')
export class TodoController {
  constructor(private readonly todoService: TodoService) {}

  @Post()
  createTodo(@Body() data: CreateTodoDto) {
    return this.todoService.createTodo(data);
  }

  @ApiBody({ type: [CreateTodoDto] })
  @Post('bulk')
  createTodos(@Body() todos: CreateTodoDto[]) {
    return todos.map((todo) => this.todoService.createTodo(todo));
  }

  // 在 Swagger UI 上可以看到 X-Custom 的栏位可以填写
  @ApiHeader({
    name: 'X-Custom',
    description: 'Try to set custom header.',
  })
  @Get(':id')
  getTodo(@Param('id') id: string) {
    return this.todoService.getTodo(id);
  }
}

透过浏览器查看 http://localhost:3000/api 会看到下方结果:

https://ithelp.ithome.com.tw/upload/images/20210802/201193386d8tgcIDGA.png

Responses

这个功能可以在 Swagger UI 上显示该 API 回传的各个 HttpCode 是什麽意思,透过 @ApiResponse 装饰器给定 statusdescription 即可。

修改 TodoController 的内容,在 createTodo 上套用 @ApiResponse 并指定 statusHttpStatus.CREATED

import { Body, Controller, Get, HttpStatus, Param, Post } from '@nestjs/common';
import { ApiBody, ApiHeader, ApiResponse, ApiTags } from '@nestjs/swagger';

import { TodoService } from './todo.service';

import { CreateTodoDto } from './dto/create-todo.dto';

@ApiTags('Todo')
@Controller('todos')
export class TodoController {
  constructor(private readonly todoService: TodoService) {}

  // 在 Swagger UI 上可以看到状态 201 的描述
  @ApiResponse({
    status: HttpStatus.CREATED,
    description: 'The todo has been successfully created.',
  })
  @Post()
  createTodo(@Body() data: CreateTodoDto) {
    return this.todoService.createTodo(data);
  }

  @ApiBody({ type: [CreateTodoDto] })
  @Post('bulk')
  createTodos(@Body() todos: CreateTodoDto[]) {
    return todos.map((todo) => this.todoService.createTodo(todo));
  }

  @ApiHeader({
    name: 'X-Custom',
    description: 'Try to set custom header.',
  })
  @Get(':id')
  getTodo(@Param('id') id: string) {
    return this.todoService.getTodo(id);
  }
}

透过浏览器查看 http://localhost:3000/api 会看到状态 201 的描述为 The todo has been successfully created.

https://ithelp.ithome.com.tw/upload/images/20210802/20119338XgxY5NYRTo.png

事实上 Nest 有将各种状态独立包装起来,共有以下这几个,有兴趣可以自行实验看看:

  • @ApiOkResponse
  • @ApiCreatedResponse
  • @ApiAcceptedResponse
  • @ApiNoContentResponse
  • @ApiMovedPermanentlyResponse
  • @ApiBadRequestResponse
  • @ApiUnauthorizedResponse
  • @ApiNotFoundResponse
  • @ApiForbiddenResponse
  • @ApiMethodNotAllowedResponse
  • @ApiNotAcceptableResponse
  • @ApiRequestTimeoutResponse
  • @ApiConflictResponse
  • @ApiTooManyRequestsResponse
  • @ApiGoneResponse
  • @ApiPayloadTooLargeResponse
  • @ApiUnsupportedMediaTypeResponse
  • @ApiUnprocessableEntityResponse
  • @ApiInternalServerErrorResponse
  • @ApiNotImplementedResponse
  • @ApiBadGatewayResponse()
  • @ApiServiceUnavailableResponse
  • @ApiGatewayTimeoutResponse
  • @ApiDefaultResponse

API 授权设计

如果 API 需要经过授权才能使用的话,在 Swagger 要如何添加授权机制呢?可以透过指定的装饰器来实作,让开发人员可以在 Swagger UI 上配置授权资讯来存取 API,非常实用的功能!下面我会挑选三个授权方法进行解说:

Basic

使用 Basic 授权方法,透过 @ApiBasicAuth 装饰器将要授权的 API 套上此装饰器,并呼叫 DocumentBuilder 实例中的 addBasicAuth 方法来将授权栏位开启。

修改 TodoController 的内容,在 TodoController 带上 @ApiBasicAuth 装饰器:

import { Body, Controller, Get, HttpStatus, Param, Post } from '@nestjs/common';
import { ApiBasicAuth, ApiBody, ApiHeader, ApiResponse, ApiTags } from '@nestjs/swagger';

import { TodoService } from './todo.service';

import { CreateTodoDto } from './dto/create-todo.dto';

// Basic Auth
@ApiBasicAuth()
@ApiTags('Todo')
@Controller('todos')
export class TodoController {
  constructor(private readonly todoService: TodoService) {}

  @ApiResponse({
    status: HttpStatus.CREATED,
    description: 'The todo has been successfully created.',
  })
  @Post()
  createTodo(@Body() data: CreateTodoDto) {
    return this.todoService.createTodo(data);
  }

  @ApiBody({ type: [CreateTodoDto] })
  @Post('bulk')
  createTodos(@Body() todos: CreateTodoDto[]) {
    return todos.map((todo) => this.todoService.createTodo(todo));
  }

  @ApiHeader({
    name: 'X-Custom',
    description: 'Try to set custom header.',
  })
  @Get(':id')
  getTodo(@Param('id') id: string) {
    return this.todoService.getTodo(id);
  }
}

接着修改 main.ts,呼叫 builderaddBasicAuth 方法:

import { NestFactory } from '@nestjs/core';
import { INestApplication } from '@nestjs/common';
import { DocumentBuilder, SwaggerCustomOptions, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  setupSwagger(app);
  await app.listen(3000);
}

function setupSwagger(app: INestApplication) {
  const builder = new DocumentBuilder();
  const config = builder
    .setTitle('TodoList')
    .setDescription('This is a basic Swagger document.')
    .setVersion('1.0')
    .addBasicAuth() // 开启授权栏位
    .build();
  const document = SwaggerModule.createDocument(app, config);
  const options: SwaggerCustomOptions = {
    explorer: true,
  };
  SwaggerModule.setup('api', app, document, options);
}

bootstrap();

透过浏览器查看 http://localhost:3000/api 会发现 Todo 区块的 API 都有了锁头符号,这表示需要经过授权才能呼叫,可以透过画面上的 Authorize 按钮来填入授权资讯:

https://ithelp.ithome.com.tw/upload/images/20210802/2011933851lX0SXbUr.png

Bearer

使用 Bearer 授权方法,透过 @ApiBearerAuth 装饰器将要授权的 API 套上此装饰器,并呼叫 DocumentBuilder 实例中的 addBearerAuth 方法来将授权栏位开启。

修改 TodoController 的内容,在 TodoController 带上 @ApiBearerAuth 装饰器:

import { Body, Controller, Get, HttpStatus, Param, Post } from '@nestjs/common';
import { ApiBearerAuth, ApiBody, ApiHeader, ApiResponse, ApiTags } from '@nestjs/swagger';

import { TodoService } from './todo.service';

import { CreateTodoDto } from './dto/create-todo.dto';

// Bearer Auth
@ApiBearerAuth()
@ApiTags('Todo')
@Controller('todos')
export class TodoController {
  constructor(private readonly todoService: TodoService) {}

  @ApiResponse({
    status: HttpStatus.CREATED,
    description: 'The todo has been successfully created.',
  })
  @Post()
  createTodo(@Body() data: CreateTodoDto) {
    return this.todoService.createTodo(data);
  }

  @ApiBody({ type: [CreateTodoDto] })
  @Post('bulk')
  createTodos(@Body() todos: CreateTodoDto[]) {
    return todos.map((todo) => this.todoService.createTodo(todo));
  }

  @ApiHeader({
    name: 'X-Custom',
    description: 'Try to set custom header.',
  })
  @Get(':id')
  getTodo(@Param('id') id: string) {
    return this.todoService.getTodo(id);
  }
}

接着修改 main.ts,呼叫 builderaddBearerAuth 方法:

import { NestFactory } from '@nestjs/core';
import { INestApplication } from '@nestjs/common';
import { DocumentBuilder, SwaggerCustomOptions, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  setupSwagger(app);
  await app.listen(3000);
}

function setupSwagger(app: INestApplication) {
  const builder = new DocumentBuilder();
  const config = builder
    .setTitle('TodoList')
    .setDescription('This is a basic Swagger document.')
    .setVersion('1.0')
    .addBearerAuth() // 开启授权栏位
    .build();
  const document = SwaggerModule.createDocument(app, config);
  const options: SwaggerCustomOptions = {
    explorer: true,
  };
  SwaggerModule.setup('api', app, document, options);
}

bootstrap();

透过浏览器查看 http://localhost:3000/api 效果与 Basic 相同,差别在於授权方式不同:

https://ithelp.ithome.com.tw/upload/images/20210802/201193388TOUqCGWac.png

OAuth2

使用 OAuth2 授权方法,透过 @ApiOAuth2 装饰器将要授权的 API 套上此装饰器,并呼叫 DocumentBuilder 实例中的 addOAuth2 方法来将授权栏位开启,但这里需要特别设置 typeflow 参数来完成。

修改 TodoController 的内容,在 TodoController 带上 @ApiOAuth2 装饰器:

import { Body, Controller, Get, HttpStatus, Param, Post } from '@nestjs/common';
import { ApiBody, ApiHeader, ApiOAuth2, ApiResponse, ApiTags } from '@nestjs/swagger';

import { TodoService } from './todo.service';

import { CreateTodoDto } from './dto/create-todo.dto';

// OAuth2 Auth
@ApiOAuth2(['write', 'read', 'update'])
@ApiTags('Todo')
@Controller('todos')
export class TodoController {
  constructor(private readonly todoService: TodoService) {}

  @ApiResponse({
    status: HttpStatus.CREATED,
    description: 'The todo has been successfully created.',
  })
  @Post()
  createTodo(@Body() data: CreateTodoDto) {
    return this.todoService.createTodo(data);
  }

  @ApiBody({ type: [CreateTodoDto] })
  @Post('bulk')
  createTodos(@Body() todos: CreateTodoDto[]) {
    return todos.map((todo) => this.todoService.createTodo(todo));
  }

  @ApiHeader({
    name: 'X-Custom',
    description: 'Try to set custom header.',
  })
  @Get(':id')
  getTodo(@Param('id') id: string) {
    return this.todoService.getTodo(id);
  }
}

接着修改 main.ts,呼叫 builderaddOAuth2 方法,将 type 指定为 oauth2flow 则是添加 implicit 参数并配置 authorizationUrltokenUrl 以及 scopes

import { NestFactory } from '@nestjs/core';
import { INestApplication } from '@nestjs/common';
import { DocumentBuilder, SwaggerCustomOptions, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  setupSwagger(app);
  await app.listen(3000);
}

function setupSwagger(app: INestApplication) {
  const builder = new DocumentBuilder();
  const config = builder
    .setTitle('TodoList')
    .setDescription('This is a basic Swagger document.')
    .setVersion('1.0')
    .addOAuth2({
      type: 'oauth2',
      flows: {
        implicit: {
          authorizationUrl: '<AUTHORIZATION_URL>', // 授权位址
          tokenUrl: '<TOKEN_URL>', // 授权用 token
          scopes: { // 权限选项
            read: 'read',
            write: 'write',
            update: 'update',
            delete: 'delete',
          },
        },
      },
    })
    .build();
  const document = SwaggerModule.createDocument(app, config);
  const options: SwaggerCustomOptions = {
    explorer: true,
  };
  SwaggerModule.setup('api', app, document, options);
}

bootstrap();

透过浏览器查看 http://localhost:3000/api 效果与 Basic 相同,但在授权资讯配置那里会有差异,Swagger 会向该服务请求授权:

https://ithelp.ithome.com.tw/upload/images/20210802/20119338clMnG07v9u.png

小结

Swagger UI 设计的好坏会影响整体开发人员的效率,只要善用上一篇学到的 参数设计 以及本篇讲解的 操作设计授权设计,就可以大幅降低前後端的沟通成本,绝对是当今非常值得学习的工具!这里附上今天的懒人包:

  1. 使用 @ApiTags 将 API 进行分类。
  2. 使用 @ApiHeader 开启指定的 Header 栏位。
  3. 使用 @ApiResponses 可以在 Swagger UI 上显示该 API 回传的各个 HttpCode 是什麽意思。
  4. Nest 将各个状态独立包装起来,如:@ApiOkResponse
  5. 授权设计可以参考 @ApiBasicAuth@ApiBearerAuth 以及 @ApiOAuth2
  6. 授权设计需要在 DocumentBuilder 实例呼叫对应的授权机制方法,来开启授权栏位。

<<:  Day27 - 云端交易主机 - Ubuntu SSH登入 & 远端桌面

>>:  Facade 外观模式

23 - Prettier - 格式化程序码工具

制定程序码格式规范对於可读性来说是必须的,如果开发者写程序时都有各自的格式规范,那整个专案的程序码会...

[day17]Vue实作-浏览列加入登入及注册钮

延续之前的浏览列的实作,这次要增加登入跟注册纽,其实我也还在想这个网站是否需要注册功能,毕竟是私人社...

[Day26] 透过GCP实作(2/4):进行前後端分离

在昨日的文章中,简单地向各位展示直接藉由Function抓取API 所能得到的架构会是何者 而今天...

Day18 - Interpreting Machines - 1 :什麽是 Interpret ?

还记得我们在 Day 09、Day 10 有 2 个自制版本的 createMacine [Day ...

Day 15 Platform module

15 天了,没想到过半了,再接再厉~~ 讲了几天的 React , 现在让我们把焦点拉回 React...