tsoa x Express を使ってAPI仕様書の自動生成環境をセットアップした際の、学習の備忘録です。
- tsoaの概要と仕組み
- Express環境での導入手順と設定方法
tsoaの理解
tsoaとは OpenAPI仕様書(Swagger)を自動生成するためのライブラリ です。
Express や NestJS などの既存のサーバーに組み込むことができます。
tsoaを使うと、通常のExpressの書き方とは異なり、
tsoaが提供するデコレータ(@)構文を使ってControllerやルートを定義します。
ここからは、tsoaを導入してAPI仕様書を自動生成できるようにするまでの手順を解説します。
1. パッケージインストール
npm i tsoa swagger-ui-express
npm i -D concurrently @types/swagger-ui-express2. tsoa.json の設定
API仕様書の自動生成を行うための初期設定です。
{
"entryFile": "src/app.ts",
// 追加のプロパティを許可しない設定。予期しないプロパティがあればthrowする
"noImplicitAdditionalProperties": "throw-on-extras",
// どのファイルがコントローラーとして扱われるか指定
"controllerPathGlobs": ["src/**/*Controller.ts"],
// API仕様書の設定
"spec": {
// 生成された仕様書が保存されるディレクトリ
"outputDirectory": "build",
// 仕様のバージョン
"specVersion": 3
},
// ルーティングに関する設定
"routes": {
// ルーティングの設定ファイルが生成されるディレクトリ
"routesDir": "build",
// ミドルウェア
"middleware": {
"auth": "./src/middleware/authMiddleware"
}
}
}"middleware":定義のみ(実際の適用はコントローラ側で指定)
3. tsconfig.json の設定
experimentalDecorators と emitDecoratorMetadata を true にしました。他は任意の設定でOKです。
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./build",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"resolveJsonModule": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
}
4. 認証ミドルウェア(authMiddleware.ts)
作成は任意ですが、認証を行う場合の例として記載します。
// src/middleware/authMiddleware.ts
import { Request, NextFunction, Response } from "express";
export interface AuthenticatedRequest extends Request {
userId?: string;
req: AuthenticatedRequest;
}
export const authMiddleware = (
req: AuthenticatedRequest,
res: Response,
next: NextFunction
) => {
// リクエストヘッダーからトークンを取得
const token = req.headers["x-access-token"];
// トークンが存在しない場合は401エラーを返す
if (!token) {
return res.status(401).json({ message: "No token privided" });
}
// 仮のトークン値を設定
const expectedToken = "your-hardcoded-token-value";
// トークンのチェック
if (token === expectedToken) {
// トークンが正しい場合、仮のユーザーIDをリクエストオブジェクトに追加
req.userId = "123"; // 仮のユーザーID
next(); // 認証成功。次のミドルウェアへ進む
} else {
return res.status(403).json({ message: "Invalid token" });
}
};
5. 型定義ファイル(user.ts)
後述の userController.ts で使用する型定義です。
// src/users/user.ts
export interface User {
id: number;
email: string;
name: string;
status?: "Happy" | "Sad";
phoneNumbers: string[];
}6. コントローラーファイル(userController.ts)
tsoaの肝です。
ここに定義した内容をもとに、API仕様書が自動生成されます。
// src/users/userController.ts
import {
Body,
Controller,
Get,
Header,
Middlewares,
Path,
Post,
Query,
Request,
Response,
Route,
SuccessResponse,
} from "tsoa";
import { User } from "./user";
import { UserCreationParams, UsersService } from "./usersService";
import {
AuthenticatedRequest,
authMiddleware,
} from "../middleware/authMiddleware";
interface ValidateErrorJSON {
message: "Validation failed";
details: { [name: string]: unknown };
}
// このファイルで定義するエンドポイントを/usersに定義
@Route("users")
@Middlewares([authMiddleware])
export class UsersController extends Controller {
@Get("{userId}")
public async getUser(
@Header("X-Access-Token") _token: string,
@Request() req: AuthenticatedRequest,
@Path() userId: number,
@Query() name?: string
): Promise<User> {
return new UsersService().get(userId, name);
}
@Response<ValidateErrorJSON>(422, "Validation Failed")
@SuccessResponse("201", "Created")
@Post()
public async createUser(
@Header("X-Access-Token") _token: string,
@Body() requestBody: UserCreationParams
): Promise<void> {
this.setStatus(201);
new UsersService().create(requestBody);
return;
}
}7. SwaggerUI ミドルウェア設定
// src/app.ts
import express, {
json,
urlencoded,
Response as ExResponse, // ExはExpress用の型定義だとわかりやすくするため
Request as ExRequest,
NextFunction,
} from "express";
import swaggerUi from "swagger-ui-express";
import { RegisterRoutes } from "../build/routes";
import { ValidateError } from "tsoa";
export const app = express();
app.use(urlencoded({ extended: true }));
app.use(json());
// SwaggerUIを生成するミドルウェア
// http://localhost:3000/docsで参照
app.use("/docs", swaggerUi.serve, async (_req: ExRequest, res: ExResponse) => {
res.send(swaggerUi.generateHTML(await import("../build/swagger.json")));
});
app.use(function errorHandler(
err: unknown,
req: ExRequest,
res: ExResponse,
next: NextFunction
) {
// ValidateError:tsoa専用のエラークラス。リクエストパラメータ/ボディのバリデーション失敗時に発生
if (err instanceof ValidateError) {
// fields:リクエストパラメータ/ボディのキーとバリデーションエラーの詳細
// 例: { fields: { "age": { message: "Age must be a positive number" } } }
console.warn(`Caught Validation Error for ${req.path}:`, err.fields);
// 422ステータスとmessage, detailsを返す
res.status(422).json({
message: "Validation Failed",
details: err?.fields,
});
}
if (err instanceof Error) {
// 500ステータスとmessageを返す
res.status(500).json({
message: "Internal Server Error",
});
}
next();
});
// tsoaが自動生成したルーティング情報を Express アプリケーションに登録
RegisterRoutes(app);
swaggerUi.serve:SwaggerUIを実行するミドルウェア_req:未使用の引数にアンダースコア。未使用だけどreqは記述しなければならないためgenerateHTML:Swagger.jsonを読み込んでHTML化
8. package.json のスクリプト設定
package.jsonからscriptsのみ抜粋して解説します。
"scripts": {
// コード変更の度にAPI仕様書を自動で最新化
// 左側の nodemonはアプリケーションサーバーを再起動
// 右側の nodemonは APIのスペックとルートを自動再生成
"dev": "concurrently \"nodemon\" \"nodemon -x tsoa spec-and-routes\"",
// コンパイルしてAPI仕様書を生成し、TypeScriptをJavaScriptに変換
"build": "tsoa spec-and-routes && tsc",
"start": "node build/src/server.js"
},concurrently: 複数のコマンドを同時に実行するためのツールnodemon: コードの変更を監視し、自動的にサーバーを再起動するツール。-xで特定のコマンドを実行する。tsoa spec-and-routes: APIの仕様とルーティングを生成tsc: TypeScriptコードをJavaScriptにコンパイル
9. API仕様書の生成コマンド
開発中:
npm run dev
# http://localhost:3000/docs にアクセスデプロイ時:
npm run build
# buildディレクトリとswagger.jsonが自動生成されますまとめ
以上で、tsoaを使ったOpenAPI仕様書の自動生成までの一連の手順が完了しました。
tsoaを導入することで、API設計と実装の一貫性を保ちながら、ドキュメントも自動化できます。
開発チームでAPI仕様を共有する際にも非常に便利なので、ぜひ活用してみてください。

コメント