Swaggerファイルを自動生成する

# はじめに
**Swagger形式のOASを自動生成してくれる**パッケージは無いか探していたところ、、、
[TSOA](https://www.npmjs.com/package/tsoa)という良さげなやつを見つけたので試してみました！

本記事で紹介する技術は以下になります。

- TSOA
- Node.js
- TypeScript
- express
- swagger

**作業環境** 

```
OS: Windows 10 Pro
Node: 12.1.0
npm: 6.9.0 
```

## 準備
Node+TypeScriptの環境を構築します。

```
PS C:\Users\user\Desktop\> mkdir project; cd project;
PS C:\Users\user\Desktop\project> npm init -y 
PS C:\Users\user\Desktop\project> npm install -D typescript @types/node ts-node
PS C:\Users\user\Desktop\project> ./node_modules/.bin/tsc --init
```

**tsconfig.jsonの設定**

今回、デコレータ、importでJSONの読み込みを行うので下記のように設定にしました。

```tsconfig.json
{
  "compilerOptions": {
    // importでJSON読み込み用
    "moduleResolution": "node",
    "resolveJsonModule": true,
    // デコレータ用
    "experimentalDecorators": true,
    // 初期のまま
    "target": "es5",
    "module": "commonjs", 
    "strict": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true
  }
}
```

**package.jsonの設定**

`ts-node`をnpmスクリプトで実行できるように`package.json`を書き換えます。

```package.json
  "scripts": {
    "ts-node": "ts-node"
  }
```

## 簡易的なAPIを作ってみる

プロジェクトに`TSOA`をNPMでインストールします。

```
PS C:\Users\user\Desktop\project> npm install tsoa --save
```

今回は下記のような単純なAPIを用意します。

- GETでリクエストされたユーザIDからユーザ情報を取得するAPI
- POSTでリクエストされた情報でユーザ情報を登録するAPI

まずは、**APIのエンドポイントとなるController**を作成します。

```
PS C:\Users\user\Desktop\project> vi /controllers/usersController.ts
```
```/controllers/usersController.ts
import { Body, Controller, Get, Header, Path, Post, Query, Route, SuccessResponse } from 'tsoa'
import { User, RequestBody } from '../models/user'
import { getUser, createUser } from '../services/userService';
 
@Route('users')
export class UsersController extends Controller {
  // IDをPATHに含めたGETなAPIを定義
  @Get('/get/{id}')
  public getUser(id: number): User {
    return getUser(id)
  }
  // POSTなAPI
  @Post('/create')
  public createUser(@Body() requestBody: RequestBody): string {
    createUser(requestBody)
    return 'success'
  }
}
```

続いて、**ユーザ情報の型定義となるModel**を作成します。

```
PS C:\Users\user\Desktop\project> vi /models/user.ts
```
```/models/user.ts
// 「id」と「name」をプロパティに持つUser情報の型定義
export interface User {
  id: number
  name: string
}
// 「name」をプロパティに持つリクエスト情報の型定義
export interface RequestBody {
  name: string
}
```

最後に、**API側で処理を行う部分となるService**を作成します。

```
PS C:\Users\user\Desktop\project> vi /services/userService.ts
```
```/services/userService.ts
import { User, RequestBody } from '../models/user'

// ※Databaseとかに下記のようなデータ格納されているとする
const users: Array<User> = [
  {
    id: 1,
    name: 'Tanka'
  },
  {
    id: 2,
    name: 'Suzuki'
  }
]

// IDでユーザ情報を取得する関数
export const getUser = (id: number): User => {
  return users.find((user) => user.id === id) || {}
}
// リクエストされた情報からユーザ情報を登録する関数
export const createUser = (body: RequestBody): void => {
  users.push({
    id: users.length + 1,
    name: body.name
  })
}
```

簡易的なAPIの作成が完了しました。

続いて、**Swagger.jsonとルーティングファイルを自動生成するプログラム**の作成に取り掛かります。

## 自動生成プログラムの作成

プロジェクト直下に下記のような自動生成用プログラムを用意しましょう。

```
PS C:\Users\user\Desktop\project> vi /generate.ts
```
```/generate.ts
import { generateRoutes, generateSwaggerSpec, RoutesConfig, SwaggerConfig } from 'tsoa'

(async () => {
  // 自動生成するSwagger.jsonの設定を定義
  const swaggerOptions: SwaggerConfig = {
    basePath: '/api',
    entryFile: './api/server.ts',
    specVersion: 3,
    outputDirectory: './api/dist',
    controllerPathGlobs: ['./controllers/*Controller.ts'],
  }
  // 自動生成するルーティングファイルの設定を定義
  const routeOptions: RoutesConfig = {
    basePath: '/api',
    entryFile: './api/server.ts',
    routesDir: './api',
  }
  // 自動生成の実行
  await generateSwaggerSpec(swaggerOptions, routeOptions)
  await generateRoutes(routeOptions, swaggerOptions)
})();
```

ここまでの作成が終わったらプロジェクトのディレクトリ構成は以下のような感じになっているのではないでしょうか。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/518115/ee566256-7106-75a7-c77a-afdedd4138db.png)


では、実際に自動生成を行いたいと思います。

## 自動生成を行う

下記のコマンドを実行します。

```
PS C:\Users\user\Desktop\project> npm run ts-node generate.ts
```
すると、無事にSwagger.jsonとルーティングファイルが自動生成されました。
![20191120103750.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/518115/032d1fab-145f-3f3f-eec2-6a247b335242.png)


## 生成したファイルを使ってみる

実際に生成されたファイルを使ってみます。
`express`を使ってAPIサーバを立ち上げて、自動生成されたルーティングファイルを使ってみましょう。
ついてに、`swagger-ui-express`を使って、自動生成されたSwagger.jsonからSwaggerUI画面を立ち上げてみましょう。

```
PS C:\Users\user\Desktop\project> npm install --save express @types/express
PS C:\Users\user\Desktop\project> npm install --save swagger-ui-express @types/swagger-ui-express
PS C:\Users\user\Desktop\project> vi /api/server.ts
```
```/api/server.ts
import express from 'express'
import { RegisterRoutes } from './routes'
import '../controllers/usersController'

import swaggerUi from 'swagger-ui-express'
import swaggerDocument from './dist/swagger.json'

// ユーザリクエストの解析等
const app = express()
app.use(express.json())
app.use(express.urlencoded({ extended: true }))

// ルーティングファイルの登録
RegisterRoutes(app)

// Swagger UIの登録
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument))

// サーバ起動
app.listen(3000, () => {
　console.log('App running at http://localhost:3000')
})
```
サーバの準備ができたら起動しましょう。

```
PS C:\Users\user\Desktop\project> npm run ts-node api/server.ts
App running at http://localhost:3000
```

CurlでAPIを叩いてみます。

```bash
# 「Satou」さんを登録してみる
$ curl -X POST "http://localhost:3000/api/users/create" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"name\"
:\"Satou\"}"
"success"
# 「Satou」さんを取得してみる
$ curl -X GET http://localhost:3000/api/users/get/3 -H "accept: application/json"
{"id":3,"name":"Satou"}
```
無事に、ユーザの登録/取得が出来ました。

`http://localhost:3000/api-docs/`にアクセスして`SwaggerUI`の画面も確認してみましょう。
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/518115/445de07c-0c5e-8c33-2a89-19a978db6437.png)

無事に、SwaggerUIの立ち上げも出来ました。

以上、TSOAを使ってみたでした！