iT邦幫忙

2021 iThome 鐵人賽

DAY 27
0
Modern Web

NestJS 帶你飛!系列 第 27

[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 實例呼叫對應的授權機制方法,來開啟授權欄位。

上一篇
[NestJS 帶你飛!] DAY26 - Swagger (上)
下一篇
[NestJS 帶你飛!] DAY28 - CORS
系列文
NestJS 帶你飛!32

1 則留言

0
juck30808
iT邦新手 2 級 ‧ 2021-10-12 18:39:39

恭喜大大即將完賽XD !!!

HAO iT邦新手 3 級 ‧ 2021-10-13 08:58:46 檢舉

謝謝你的支持,也恭喜你即將完賽!

我要留言

立即登入留言