OpenAPI × API First開発ガイド2026|スキーマ駆動・コード生成・モック・テスト


API First開発とは

API First開発は、実装の前にAPI仕様を定義するアプローチです。OpenAPI(旧Swagger)仕様書をSingle Source of Truthとし、そこからサーバー/クライアントのコード、ドキュメント、テストを自動生成します。

従来の開発 vs API First

従来(Code First):
  サーバー実装 → API仕様を後付けで作成 → フロントエンドが待ち

API First:
  API仕様を定義 → サーバー/クライアント並行開発 → モックで即テスト

メリット

メリット説明
並行開発フロント/バックが同時に開発できる
型安全スキーマからコード生成で型の不一致なし
ドキュメント仕様書がそのままドキュメント
モック実装前にAPIをテスト可能
契約テスト仕様と実装の乖離を自動検出

OpenAPI仕様の書き方

基本構造

# openapi.yaml
openapi: 3.1.0
info:
  title: My API
  version: 1.0.0
  description: ユーザー管理API

servers:
  - url: http://localhost:8080/api/v1
    description: ローカル開発
  - url: https://api.example.com/v1
    description: 本番環境

paths:
  /users:
    get:
      summary: ユーザー一覧取得
      operationId: listUsers
      tags: [Users]
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
            minimum: 1
        - name: per_page
          in: query
          schema:
            type: integer
            default: 20
            minimum: 1
            maximum: 100
        - name: search
          in: query
          schema:
            type: string
            maxLength: 100
      responses:
        '200':
          description: ユーザー一覧
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginatedUsers'

    post:
      summary: ユーザー作成
      operationId: createUser
      tags: [Users]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUserRequest'
      responses:
        '201':
          description: 作成成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '409':
          description: メールアドレスが重複
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /users/{id}:
    get:
      summary: ユーザー詳細取得
      operationId: getUser
      tags: [Users]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: ユーザー詳細
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '404':
          $ref: '#/components/responses/NotFound'

components:
  schemas:
    User:
      type: object
      required: [id, name, email, createdAt]
      properties:
        id:
          type: integer
          example: 1
        name:
          type: string
          example: 田中太郎
        email:
          type: string
          format: email
          example: tanaka@example.com
        age:
          type: integer
          nullable: true
          minimum: 0
          maximum: 150
        createdAt:
          type: string
          format: date-time

    CreateUserRequest:
      type: object
      required: [name, email, password]
      properties:
        name:
          type: string
          minLength: 2
          maxLength: 50
        email:
          type: string
          format: email
        password:
          type: string
          minLength: 8
          maxLength: 100
        age:
          type: integer
          minimum: 0
          maximum: 150

    PaginatedUsers:
      type: object
      required: [items, total, page, perPage]
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/User'
        total:
          type: integer
        page:
          type: integer
        perPage:
          type: integer

    Error:
      type: object
      required: [message]
      properties:
        message:
          type: string
        errors:
          type: object
          additionalProperties:
            type: string

  responses:
    NotFound:
      description: リソースが見つかりません
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

security:
  - bearerAuth: []

コード自動生成

TypeScriptクライアント生成(openapi-typescript)

npx openapi-typescript openapi.yaml -o src/api/types.ts
// 生成された型を使用
import type { paths, components } from './api/types';
import createClient from 'openapi-fetch';

const client = createClient<paths>({
  baseUrl: 'http://localhost:8080/api/v1',
});

// 完全に型安全なAPI呼び出し
const { data, error } = await client.GET('/users', {
  params: {
    query: { page: 1, per_page: 20 },
  },
});

// data は PaginatedUsers 型
if (data) {
  data.items.forEach(user => {
    console.log(user.name); // 型安全
  });
}

// POST
const { data: newUser } = await client.POST('/users', {
  body: {
    name: '田中太郎',
    email: 'tanaka@example.com',
    password: 'password123',
  },
});

Zodスキーマ生成

npx openapi-zod-client openapi.yaml -o src/api/zodSchemas.ts
// 生成されたZodスキーマでバリデーション
import { CreateUserRequestSchema } from './api/zodSchemas';

const result = CreateUserRequestSchema.safeParse(formData);
if (!result.success) {
  console.log(result.error.issues);
}

Pythonクライアント生成

pip install openapi-python-client
openapi-python-client generate --path openapi.yaml

モックサーバー(Prism)

# Prismのインストール
npm install -g @stoplight/prism-cli

# モックサーバー起動
prism mock openapi.yaml

# → http://localhost:4010 でモックAPIが使える
# リクエスト例
curl http://localhost:4010/users
# → OpenAPIのexampleに基づいたレスポンスが返る

curl -X POST http://localhost:4010/users \
  -H "Content-Type: application/json" \
  -d '{"name":"test","email":"test@example.com","password":"password123"}'
# → 201レスポンス

# バリデーション
curl -X POST http://localhost:4010/users \
  -H "Content-Type: application/json" \
  -d '{"name":"t"}'
# → 422エラー(minLength違反)

Prismのプロキシモード

# 実際のAPIに対してリクエスト/レスポンスをOpenAPIに対して検証
prism proxy openapi.yaml http://localhost:8080

リンティング(Spectral)

npm install -g @stoplight/spectral-cli
# .spectral.yaml
extends:
  - spectral:oas

rules:
  # カスタムルール
  operation-description:
    severity: warn
    given: "$.paths[*][*]"
    then:
      field: description
      function: truthy

  # operationIdは必須
  operation-operationId:
    severity: error

  # 日本語でsummaryを書く
  operation-summary-length:
    severity: warn
    given: "$.paths[*][*].summary"
    then:
      function: length
      functionOptions:
        min: 5
        max: 50
spectral lint openapi.yaml

# 出力例
# /users:get.description - Operation should have a description (operation-description)
# ✖ 1 problem (0 errors, 1 warning)

契約テスト

Schemathesisによる自動テスト

pip install schemathesis

# OpenAPIスキーマに基づいてファジングテスト
schemathesis run http://localhost:8080/openapi.json \
  --checks all \
  --base-url http://localhost:8080

# 見つかる問題の例:
# - 5xxエラーを返すエッジケース
# - スキーマと異なるレスポンス
# - 想定外の入力での挙動

Prismでの契約テスト

// テストでPrismを使って仕様との一致を検証
import { createClientFromPrism } from './test-helpers';

test('GET /users のレスポンスがスキーマに一致する', async () => {
  const response = await fetch('http://localhost:8080/api/v1/users');
  const data = await response.json();

  // Zodスキーマで検証
  const result = PaginatedUsersSchema.safeParse(data);
  expect(result.success).toBe(true);
});

CI/CDとの統合

# .github/workflows/api-check.yml
name: API Schema Check

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

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: OpenAPIリンティング
        run: npx @stoplight/spectral-cli lint openapi.yaml

  breaking-changes:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: 破壊的変更の検出
        run: |
          npx oasdiff breaking \
            <(git show main:openapi.yaml) \
            openapi.yaml

  generate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - name: 型生成
        run: npx openapi-typescript openapi.yaml -o src/api/types.ts
      - name: 差分確認
        run: git diff --exit-code src/api/types.ts

まとめ

API Firstの開発フロー

1. OpenAPIでAPI仕様を定義
2. Spectralでリンティング
3. Prismでモックサーバー起動
4. フロント: openapi-fetchで型安全にAPI呼び出し
5. バック: スキーマに合わせて実装
6. CI: 契約テスト + 破壊的変更検出
ツール用途
openapi-typescriptTypeScript型生成
openapi-fetch型安全HTTPクライアント
Prismモックサーバー/プロキシ
Spectralリンティング
oasdiff破壊的変更検出
Schemathesisファジングテスト

API Firstは初期コストがかかりますが、チーム開発のスピードと品質を大幅に向上させます。特にフロント/バック分業のプロジェクトでは、その効果は絶大です。

関連記事