OpenAPI仕様入門|Swagger UIでAPI仕様書を自動生成する方法

kento_morota 24分で読めます

API仕様書をExcelやWordで手書きしていて、実装との乖離が発生している——こうした課題を解決するのがOpenAPI仕様です。API定義を機械可読なフォーマットで記述し、仕様書の自動生成、クライアントコードの自動生成、バリデーションまでを自動化できます。

本記事では、OpenAPI仕様の基本的な書き方からSwagger UIの活用方法、コード生成ツール、CI/CDへの統合まで実践的に解説します。

OpenAPI仕様とは何か

OpenAPI Specification(OAS)は、REST APIのインターフェースを記述するための標準フォーマットです。もともとSwaggerと呼ばれていたプロジェクトが、2015年にLinux Foundation傘下のOpenAPI Initiativeに移管され、現在の名称になりました。

OpenAPIがもたらす価値

仕様書の自動生成
YAMLまたはJSON形式でAPI定義を書くだけで、Swagger UIにより美しいインタラクティブなAPIドキュメントが自動生成されます。手書きの仕様書と異なり、常に最新の状態を保てます。

コード生成
API定義からサーバースタブやクライアントライブラリを自動生成できます。TypeScript、Java、Python、Go、C#など、多くの言語に対応しています。

バリデーション
リクエスト・レスポンスがAPI定義に準拠しているかを自動的に検証できます。実装のバグを早期に発見できます。

テストの自動化
API定義からテストケースを自動生成し、APIの動作を自動検証できます。

API設計ファースト
コードを書く前にAPIの設計をOpenAPI仕様で定義し、フロントエンドとバックエンドの開発を並行して進められます。

Swagger UIとSwagger Editorの違い

Swagger Editor
ブラウザ上でOpenAPI定義ファイルを編集し、リアルタイムにプレビューできるエディタです。editor.swagger.ioで無料で利用できます。

Swagger UI
OpenAPI定義ファイルからインタラクティブなAPIドキュメントを生成するツールです。実際にAPIを呼び出すTry it out機能も備えています。

OpenAPI定義ファイルの基本構造

OpenAPI 3.0の定義ファイルの基本構造を見ていきましょう。

最小限の定義

# openapi.yaml
openapi: "3.0.3"  # OpenAPIのバージョン

info:
  title: ユーザー管理API
  description: ユーザーのCRUD操作を提供するREST API
  version: "1.0.0"
  contact:
    name: API サポート
    email: api-support@example.com

servers:
  - url: https://api.example.com/v1
    description: 本番環境
  - url: https://staging-api.example.com/v1
    description: ステージング環境
  - url: http://localhost:3000/v1
    description: ローカル開発環境

paths:
  /users:
    get:
      summary: ユーザー一覧を取得
      operationId: getUsers
      tags:
        - Users
      parameters:
        - name: page
          in: query
          description: ページ番号
          schema:
            type: integer
            default: 1
            minimum: 1
        - name: limit
          in: query
          description: 1ページあたりの件数
          schema:
            type: integer
            default: 20
            minimum: 1
            maximum: 100
      responses:
        "200":
          description: ユーザー一覧の取得に成功
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"

    post:
      summary: ユーザーを作成
      operationId: createUser
      tags:
        - Users
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateUserRequest"
            example:
              name: "田中太郎"
              email: "tanaka@example.com"
              age: 30
      responses:
        "201":
          description: ユーザーの作成に成功
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
        "400":
          $ref: "#/components/responses/BadRequest"
        "409":
          description: メールアドレスが既に登録されている
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /users/{userId}:
    get:
      summary: ユーザーの詳細を取得
      operationId: getUserById
      tags:
        - Users
      parameters:
        - name: userId
          in: path
          required: true
          description: ユーザーID
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: ユーザー詳細の取得に成功
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
        "404":
          $ref: "#/components/responses/NotFound"

スキーマ定義(components/schemas)

components:
  schemas:
    User:
      type: object
      required:
        - id
        - name
        - email
        - createdAt
      properties:
        id:
          type: string
          format: uuid
          description: ユーザーの一意識別子
          example: "550e8400-e29b-41d4-a716-446655440000"
        name:
          type: string
          description: ユーザー名
          minLength: 1
          maxLength: 100
          example: "田中太郎"
        email:
          type: string
          format: email
          description: メールアドレス
          example: "tanaka@example.com"
        age:
          type: integer
          minimum: 0
          maximum: 150
          description: 年齢
          example: 30
        role:
          type: string
          enum:
            - admin
            - member
            - guest
          default: member
          description: ユーザーの権限
        createdAt:
          type: string
          format: date-time
          description: 作成日時
          example: "2026-03-27T10:00:00Z"

    CreateUserRequest:
      type: object
      required:
        - name
        - email
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 100
        email:
          type: string
          format: email
        age:
          type: integer
          minimum: 0
          maximum: 150

    UserListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/User"
        pagination:
          $ref: "#/components/schemas/Pagination"

    Pagination:
      type: object
      properties:
        currentPage:
          type: integer
          example: 1
        totalPages:
          type: integer
          example: 10
        totalCount:
          type: integer
          example: 195
        limit:
          type: integer
          example: 20

    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
          description: エラーコード
          example: "VALIDATION_ERROR"
        message:
          type: string
          description: エラーメッセージ
          example: "入力値が不正です"
        details:
          type: array
          items:
            type: object
            properties:
              field:
                type: string
              message:
                type: string

  responses:
    BadRequest:
      description: リクエストが不正
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Unauthorized:
      description: 認証が必要
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    NotFound:
      description: リソースが見つからない
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"

  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key

security:
  - BearerAuth: []

Swagger UIでAPIドキュメントを公開する

OpenAPI定義ファイルからSwagger UIでインタラクティブなドキュメントを公開する方法を紹介します。

Express.jsに組み込む方法

// swagger-uiをExpressアプリに組み込む
import express from 'express';
import swaggerUi from 'swagger-ui-express';
import YAML from 'yamljs';

const app = express();

// OpenAPI定義ファイルを読み込み
const swaggerDocument = YAML.load('./openapi.yaml');

// Swagger UIのセットアップ
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument, {
  customCss: '.swagger-ui .topbar { display: none }',
  customSiteTitle: 'ユーザー管理API ドキュメント',
  swaggerOptions: {
    persistAuthorization: true,  // 認証情報を保持
    tryItOutEnabled: true,       // Try it outをデフォルトで有効
  },
}));

// APIのルート定義
app.use('/v1', apiRoutes);

app.listen(3000, () => {
  console.log('サーバー起動: http://localhost:3000');
  console.log('APIドキュメント: http://localhost:3000/api-docs');
});

Docker Composeで独立した環境を構築

# docker-compose.yml
version: '3.8'

services:
  swagger-ui:
    image: swaggerapi/swagger-ui
    ports:
      - "8080:8080"
    volumes:
      - ./openapi.yaml:/usr/share/nginx/html/openapi.yaml
    environment:
      - SWAGGER_JSON=/usr/share/nginx/html/openapi.yaml
      - BASE_URL=/api-docs

  swagger-editor:
    image: swaggerapi/swagger-editor
    ports:
      - "8081:8080"

上記の設定で、http://localhost:8080でSwagger UIを、http://localhost:8081でSwagger Editorをそれぞれ利用できます。

コード自動生成の活用

OpenAPI定義からクライアントコードやサーバースタブを自動生成する方法を紹介します。

TypeScriptクライアントの生成

# openapi-generatorのインストール
npm install -g @openapitools/openapi-generator-cli

# TypeScriptクライアントの生成
openapi-generator-cli generate \
  -i openapi.yaml \
  -g typescript-fetch \
  -o ./generated/client \
  --additional-properties=supportsES6=true,typescriptThreePlus=true

# 別のツール: openapi-typescript(型定義のみ生成)
npm install -D openapi-typescript
npx openapi-typescript openapi.yaml -o ./generated/api-types.ts

生成されたコードの使用例を見てみましょう。

// 自動生成されたクライアントの使用例
import { UsersApi, Configuration } from './generated/client';

const config = new Configuration({
  basePath: 'https://api.example.com/v1',
  headers: {
    'Authorization': 'Bearer eyJhbGci...',
  },
});

const usersApi = new UsersApi(config);

// 型安全なAPI呼び出し
async function fetchUsers() {
  // getUsers の引数と戻り値が型付けされている
  const response = await usersApi.getUsers({
    page: 1,
    limit: 20,
  });

  // response.data の型が UserListResponse として推論される
  for (const user of response.data) {
    console.log(user.name);  // 補完が効く
    console.log(user.email); // 型チェックされる
  }
}

openapi-typescriptとopenapi-fetchの組み合わせ

より軽量なアプローチとして、型定義だけを生成して既存のfetchクライアントと組み合わせる方法もあります。

# 型定義の生成
npm install -D openapi-typescript
npm install openapi-fetch

npx openapi-typescript openapi.yaml -o ./src/api/schema.d.ts
// src/api/client.ts
import createClient from 'openapi-fetch';
import type { paths } from './schema';

const client = createClient<paths>({
  baseUrl: 'https://api.example.com/v1',
});

// 型安全なAPI呼び出し
async function getUser(userId: string) {
  const { data, error } = await client.GET('/users/{userId}', {
    params: {
      path: { userId },
    },
  });

  if (error) {
    // error の型が自動的に推論される
    console.error(error.message);
    return;
  }

  // data の型が User として推論される
  console.log(data.name);
}

リクエスト・レスポンスのバリデーション

OpenAPI定義を使って、実際のAPIリクエストとレスポンスを自動的に検証できます。

express-openapi-validatorの導入

// express-openapi-validatorを使ったバリデーション
import express from 'express';
import * as OpenApiValidator from 'express-openapi-validator';

const app = express();
app.use(express.json());

// OpenAPIバリデーションミドルウェアの設定
app.use(
  OpenApiValidator.middleware({
    apiSpec: './openapi.yaml',
    validateRequests: true,   // リクエストのバリデーション
    validateResponses: true,  // レスポンスのバリデーション(開発環境のみ推奨)
    validateSecurity: {
      handlers: {
        BearerAuth: (req, scopes, schema) => {
          // JWTトークンの検証ロジック
          const token = req.headers.authorization?.split(' ')[1];
          if (!token) return false;
          // トークンの検証...
          return true;
        },
      },
    },
  })
);

// ルート定義
app.post('/v1/users', (req, res) => {
  // req.body は OpenAPI定義に基づいてバリデーション済み
  // name、emailが必須で、ageが0-150の範囲であることが保証されている
  const user = createUser(req.body);
  res.status(201).json(user);
});

// バリデーションエラーのハンドリング
app.use((err, req, res, next) => {
  if (err.status === 400) {
    res.status(400).json({
      code: 'VALIDATION_ERROR',
      message: 'リクエストが不正です',
      details: err.errors.map(e => ({
        field: e.path,
        message: e.message,
      })),
    });
  } else {
    next(err);
  }
});

バリデーションを導入することで、APIコントローラー内でのバリデーションコードを大幅に削減できます。OpenAPI定義が「信頼できる唯一の情報源(Single Source of Truth)」として機能します。

CI/CDパイプラインへの統合

OpenAPI定義をCI/CDパイプラインに組み込むことで、API品質を継続的に維持できます。

定義ファイルのリント・バリデーション

# Spectral を使ったOpenAPI定義のリント
npm install -D @stoplight/spectral-cli

# リントの実行
npx spectral lint openapi.yaml
# .spectral.yml - カスタムルールの定義
extends:
  - spectral:oas

rules:
  # operationIdの必須化
  operation-operationId: error

  # レスポンスにdescriptionを必須化
  oas3-valid-media-example: warn

  # カスタムルール: パス名はケバブケースを強制
  path-kebab-case:
    description: "パス名はケバブケースで記述すること"
    severity: error
    given: "$.paths[*]~"
    then:
      function: pattern
      functionOptions:
        match: "^(/[a-z0-9-]+|/{[a-zA-Z]+})+$"

GitHub Actionsでの自動チェック

# .github/workflows/openapi.yml
name: OpenAPI Validation

on:
  pull_request:
    paths:
      - 'openapi.yaml'
      - 'openapi/**'

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: OpenAPI定義のバリデーション
        run: |
          npm install -g @stoplight/spectral-cli
          spectral lint openapi.yaml

      - name: 破壊的変更の検出
        run: |
          npm install -g oasdiff
          # mainブランチの定義と比較して破壊的変更を検出
          git fetch origin main
          git show origin/main:openapi.yaml > openapi-base.yaml
          oasdiff breaking openapi-base.yaml openapi.yaml

      - name: TypeScript型定義の再生成
        run: |
          npx openapi-typescript openapi.yaml -o ./src/api/schema.d.ts
          git diff --exit-code src/api/schema.d.ts || {
            echo "型定義が更新されています。再生成してコミットしてください。"
            exit 1
          }

破壊的変更の自動検出は特に重要です。エンドポイントの削除、必須パラメータの追加、レスポンスフィールドの削除などを自動的に検出し、意図しない互換性破壊を防ぎます。

OpenAPI定義の設計ベストプラクティス

実務でOpenAPI定義を書く際のベストプラクティスをまとめます。

スキーマ設計のポイント

$refを活用して再利用性を高める
共通のスキーマはcomponents/schemasに定義し、$refで参照します。定義の重複を避け、変更時の影響を最小化します。

exampleを充実させる
各フィールドやリクエスト・レスポンスにexampleを記載することで、Swagger UIでの理解がしやすくなります。

バリデーションルールを明記する
minLengthmaxLengthminimummaximumpattern(正規表現)、enumなどを使って、フィールドの制約を明確にします。

エラーレスポンスを統一する
エラーレスポンスの形式を全エンドポイントで統一し、共通のErrorスキーマを$refで参照します。

ファイル分割の方法

大規模なAPIでは、1つのファイルに全定義を書くと管理が困難になります。ファイルを分割しましょう。

# ディレクトリ構成の例
openapi/
├── openapi.yaml          # メインファイル($refで他ファイルを参照)
├── paths/
│   ├── users.yaml        # /users エンドポイントの定義
│   ├── posts.yaml        # /posts エンドポイントの定義
│   └── comments.yaml
├── schemas/
│   ├── User.yaml
│   ├── Post.yaml
│   └── Error.yaml
└── parameters/
    └── common.yaml       # 共通パラメータ
# openapi.yaml(メインファイル)からの参照
paths:
  /users:
    $ref: './paths/users.yaml#/users'
  /users/{userId}:
    $ref: './paths/users.yaml#/users~1{userId}'

components:
  schemas:
    User:
      $ref: './schemas/User.yaml'

まとめ

OpenAPI仕様は、API開発の品質と効率を大幅に向上させる強力なツールです。本記事のポイントを振り返りましょう。

仕様駆動開発
OpenAPI定義をAPI設計の起点とし、コード実装前にフロントエンド・バックエンド間でAPIの合意を取ることで、手戻りを大幅に削減できます。

Swagger UIの活用
インタラクティブなAPIドキュメントにより、APIの理解と動作確認が容易になります。

コード生成の恩恵
TypeScriptクライアントの自動生成により、型安全なAPI呼び出しを実現し、実装ミスを防ぎます。

バリデーションの自動化
express-openapi-validatorなどを使って、リクエスト・レスポンスの検証を自動化し、手動バリデーションコードを削減します。

CI/CDとの統合
リント、破壊的変更検出、型定義の自動更新をパイプラインに組み込み、API品質を継続的に維持します。

まずは既存のAPIに対してOpenAPI定義ファイルを書き、Swagger UIで可視化してみることをおすすめします。仕様書の自動生成だけでも十分な価値がありますし、そこからコード生成やバリデーションへと段階的に活用範囲を広げていけます。

#OpenAPI#Swagger#API仕様書
共有:
無料メルマガ

週1回、最新の技術記事をお届け

AI・クラウド・開発の最新記事を毎週月曜にメールでお届けします。登録は無料、いつでも解除できます。

プライバシーポリシーに基づき管理します

起業準備に役立つ情報、もっとありますよ。

まずは話だけ聞いてもらう