OpenAPI仕様書完全ガイド: REST APIドキュメント自動生成


REST APIを開発する際、ドキュメント作成と保守は常に課題です。OpenAPI Specification(OAS)を使えば、API仕様を標準フォーマットで記述し、ドキュメント生成からクライアントコード生成まで自動化できます。本記事では、2026年最新のOpenAPI 3.1を使った実践的なAPI設計を解説します。

OpenAPI Specificationとは

OpenAPI Specification(旧Swagger Specification)は、REST APIを記述するための業界標準のフォーマットです。YAML/JSON形式でAPI仕様を記述すると、以下が自動生成できます。

  • インタラクティブなAPIドキュメント
  • クライアントSDK(TypeScript, Python, Goなど)
  • サーバースタブ
  • API検証ツール
  • モックサーバー

OpenAPI 3.1の主な改善点

  • JSON Schema 2020-12完全互換
  • Webhookのサポート
  • $refの柔軟性向上
  • Discriminatorの改善

基本構造

最小限のOpenAPI仕様

openapi: 3.1.0
info:
  title: Blog API
  version: 1.0.0
  description: シンプルなブログAPIの例

servers:
  - url: https://api.example.com/v1
    description: 本番環境
  - url: https://staging-api.example.com/v1
    description: ステージング環境

paths:
  /posts:
    get:
      summary: 記事一覧取得
      description: 公開されている全記事を取得します
      responses:
        '200':
          description: 成功
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Post'

components:
  schemas:
    Post:
      type: object
      required:
        - id
        - title
        - content
      properties:
        id:
          type: string
          format: uuid
          description: 記事ID
        title:
          type: string
          minLength: 1
          maxLength: 200
          description: タイトル
        content:
          type: string
          description: 本文
        createdAt:
          type: string
          format: date-time
          description: 作成日時

実践的なAPI設計

1. RESTful Paths設計

paths:
  # コレクション操作
  /posts:
    get:
      summary: 記事一覧取得
      operationId: listPosts
      tags:
        - posts
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
            minimum: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            minimum: 1
            maximum: 100
        - name: status
          in: query
          schema:
            type: string
            enum: [draft, published, archived]
      responses:
        '200':
          description: 成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Post'
                  pagination:
                    $ref: '#/components/schemas/Pagination'

    post:
      summary: 記事作成
      operationId: createPost
      tags:
        - posts
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePostRequest'
      responses:
        '201':
          description: 作成成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Post'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'

  # リソース個別操作
  /posts/{postId}:
    parameters:
      - name: postId
        in: path
        required: true
        schema:
          type: string
          format: uuid

    get:
      summary: 記事詳細取得
      operationId: getPost
      tags:
        - posts
      responses:
        '200':
          description: 成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Post'
        '404':
          $ref: '#/components/responses/NotFound'

    patch:
      summary: 記事更新
      operationId: updatePost
      tags:
        - posts
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdatePostRequest'
      responses:
        '200':
          description: 更新成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Post'

    delete:
      summary: 記事削除
      operationId: deletePost
      tags:
        - posts
      security:
        - bearerAuth: []
      responses:
        '204':
          description: 削除成功

  # ネストリソース
  /posts/{postId}/comments:
    parameters:
      - $ref: '#/components/parameters/PostId'

    get:
      summary: コメント一覧取得
      operationId: listPostComments
      tags:
        - comments
      responses:
        '200':
          description: 成功
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Comment'

2. スキーマ定義のベストプラクティス

components:
  schemas:
    # 基本型の再利用
    UUID:
      type: string
      format: uuid
      example: "550e8400-e29b-41d4-a716-446655440000"

    Timestamp:
      type: string
      format: date-time
      example: "2026-02-05T12:00:00Z"

    # ベースモデル
    BaseModel:
      type: object
      properties:
        id:
          $ref: '#/components/schemas/UUID'
        createdAt:
          $ref: '#/components/schemas/Timestamp'
        updatedAt:
          $ref: '#/components/schemas/Timestamp'

    # 継承を使ったスキーマ
    Post:
      allOf:
        - $ref: '#/components/schemas/BaseModel'
        - type: object
          required:
            - title
            - content
            - status
            - authorId
          properties:
            title:
              type: string
              minLength: 1
              maxLength: 200
              example: "OpenAPI入門"
            content:
              type: string
              minLength: 1
              example: "OpenAPIは..."
            excerpt:
              type: string
              maxLength: 500
              description: "要約(省略可能)"
            status:
              type: string
              enum:
                - draft
                - published
                - archived
              default: draft
            authorId:
              $ref: '#/components/schemas/UUID'
            tags:
              type: array
              items:
                type: string
              maxItems: 10
              example: ["api", "openapi", "rest"]
            publishedAt:
              allOf:
                - $ref: '#/components/schemas/Timestamp'
                - nullable: true

    # リクエストボディ
    CreatePostRequest:
      type: object
      required:
        - title
        - content
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 200
        content:
          type: string
          minLength: 1
        excerpt:
          type: string
          maxLength: 500
        tags:
          type: array
          items:
            type: string
          maxItems: 10
        status:
          type: string
          enum: [draft, published]
          default: draft

    UpdatePostRequest:
      type: object
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 200
        content:
          type: string
          minLength: 1
        excerpt:
          type: string
          maxLength: 500
        tags:
          type: array
          items:
            type: string
        status:
          type: string
          enum: [draft, published, archived]

    # ページネーション
    Pagination:
      type: object
      required:
        - page
        - limit
        - total
        - totalPages
      properties:
        page:
          type: integer
          minimum: 1
          example: 1
        limit:
          type: integer
          minimum: 1
          maximum: 100
          example: 20
        total:
          type: integer
          minimum: 0
          example: 156
        totalPages:
          type: integer
          minimum: 0
          example: 8

    # エラーレスポンス
    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
          example: "VALIDATION_ERROR"
        message:
          type: string
          example: "リクエストに不正な値が含まれています"
        details:
          type: array
          items:
            type: object
            properties:
              field:
                type: string
              message:
                type: string

3. 認証・認可

components:
  securitySchemes:
    # Bearer Token認証
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: "JWT形式のアクセストークン"

    # API Key認証
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: "APIキー"

    # OAuth 2.0
    oauth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://auth.example.com/oauth/authorize
          tokenUrl: https://auth.example.com/oauth/token
          scopes:
            read:posts: "記事の読み取り"
            write:posts: "記事の作成・更新"
            delete:posts: "記事の削除"

# グローバルセキュリティ設定
security:
  - bearerAuth: []

paths:
  /posts:
    get:
      summary: 記事一覧
      # このエンドポイントは認証不要
      security: []

  /admin/posts:
    post:
      summary: 管理者用記事作成
      # 複数の認証方式を要求
      security:
        - bearerAuth: []
          oauth2: [write:posts]

4. 再利用可能なコンポーネント

components:
  parameters:
    # パスパラメータ
    PostId:
      name: postId
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: "記事ID"

    # クエリパラメータ
    Page:
      name: page
      in: query
      schema:
        type: integer
        default: 1
        minimum: 1
      description: "ページ番号"

    Limit:
      name: limit
      in: query
      schema:
        type: integer
        default: 20
        minimum: 1
        maximum: 100
      description: "1ページあたりの件数"

  responses:
    # 共通エラーレスポンス
    BadRequest:
      description: "不正なリクエスト"
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: "VALIDATION_ERROR"
            message: "入力値が不正です"
            details:
              - field: "title"
                message: "タイトルは必須です"

    Unauthorized:
      description: "認証エラー"
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: "UNAUTHORIZED"
            message: "認証が必要です"

    NotFound:
      description: "リソースが見つかりません"
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: "NOT_FOUND"
            message: "指定されたリソースが見つかりません"

    InternalServerError:
      description: "サーバーエラー"
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: "INTERNAL_ERROR"
            message: "サーバーエラーが発生しました"

TypeScript型生成

openapi-typescript使用例

# インストール
npm install -D openapi-typescript

# 型生成
npx openapi-typescript openapi.yaml -o src/types/api.ts
// src/types/api.ts(自動生成)
export interface paths {
  "/posts": {
    get: operations["listPosts"]
    post: operations["createPost"]
  }
  "/posts/{postId}": {
    get: operations["getPost"]
    patch: operations["updatePost"]
    delete: operations["deletePost"]
  }
}

export interface components {
  schemas: {
    Post: {
      id: string
      title: string
      content: string
      status: "draft" | "published" | "archived"
      createdAt: string
      updatedAt: string
    }
    // ...
  }
}

型安全なAPIクライアント

import createClient from "openapi-fetch"
import type { paths } from "./types/api"

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

// 型安全なリクエスト
const { data, error } = await client.GET("/posts", {
  params: {
    query: {
      page: 1,
      limit: 20,
      status: "published" // 型チェックされる
    }
  }
})

if (data) {
  data.forEach(post => {
    console.log(post.title) // 型推論が効く
  })
}

// POST
const { data: newPost, error: createError } = await client.POST("/posts", {
  body: {
    title: "新しい記事",
    content: "本文",
    status: "draft"
  },
  headers: {
    Authorization: `Bearer ${token}`
  }
})

ドキュメント生成ツール

Swagger UI

# docker-compose.yml
version: '3.8'
services:
  swagger-ui:
    image: swaggerapi/swagger-ui
    ports:
      - "8080:8080"
    environment:
      SWAGGER_JSON: /openapi/openapi.yaml
    volumes:
      - ./openapi.yaml:/openapi/openapi.yaml

Redoc

<!DOCTYPE html>
<html>
  <head>
    <title>API Documentation</title>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
    <style>
      body {
        margin: 0;
        padding: 0;
      }
    </style>
  </head>
  <body>
    <redoc spec-url='./openapi.yaml'></redoc>
    <script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>
  </body>
</html>

Scalar

2026年注目の新しいドキュメントツール。

npm install @scalar/api-reference
import { ApiReference } from '@scalar/api-reference'

function App() {
  return (
    <ApiReference
      configuration={{
        spec: {
          url: '/openapi.yaml',
        },
      }}
    />
  )
}

バリデーション・テスト

仕様のバリデーション

# Spectral(OpenAPIリンター)
npm install -g @stoplight/spectral-cli

spectral lint openapi.yaml

カスタムルール

# .spectral.yaml
extends: spectral:oas

rules:
  # 全エンドポイントにoperationIdを要求
  operation-operationId: error

  # タグを必須に
  operation-tags: error

  # 説明を必須に
  info-description: error
  operation-description: warn

  # カスタムルール
  no-numeric-ids:
    message: "IDは文字列型を使用してください"
    severity: error
    given: "$.components.schemas.*.properties.id"
    then:
      field: type
      function: pattern
      functionOptions:
        notMatch: "number"

まとめ

OpenAPI Specificationを使うことで、以下のメリットが得られます。

開発効率向上

  • ドキュメントが自動生成される
  • クライアント/サーバーコードが自動生成できる
  • 型安全性が確保される

品質向上

  • API仕様が統一される
  • バリデーションが自動化できる
  • テストが容易になる

コミュニケーション改善

  • チーム間の認識齟齬が減る
  • フロントエンド/バックエンドの並行開発が可能
  • 外部への公開ドキュメントとして使える

2026年現在、OpenAPI 3.1は多くのツールでサポートされており、API開発のデファクトスタンダードとなっています。新規API開発では必ず導入を検討すべきです。

参考リンク