<CodeLearn/>
OpenAPI レッスン2

OpenAPIの書き方

paths、schemas、parameters、responsesの定義を実践しよう

paths定義(CRUD操作)

pathsはAPIのエンドポイントを定義するセクションです。 HTTPメソッド(GET / POST / PUT / DELETE)ごとに操作を記述します。 TODO APIを例に、CRUD操作の定義を見ていきましょう。

paths:
  /todos:
    get:
      tags:
        - todos
      summary: TODO一覧を取得
      description: 登録されているTODOの一覧を返します
      operationId: getTodos
      responses:
        "200":
          description: 成功
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Todo"

    post:
      tags:
        - todos
      summary: TODOを作成
      description: 新しいTODOを作成します
      operationId: createTodo
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateTodoRequest"
      responses:
        "201":
          description: 作成成功
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Todo"
        "400":
          description: バリデーションエラー
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /todos/{todoId}:
    put:
      tags:
        - todos
      summary: TODOを更新
      operationId: updateTodo
      parameters:
        - $ref: "#/components/parameters/TodoId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateTodoRequest"
      responses:
        "200":
          description: 更新成功
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Todo"
        "404":
          description: TODOが見つからない

    delete:
      tags:
        - todos
      summary: TODOを削除
      operationId: deleteTodo
      parameters:
        - $ref: "#/components/parameters/TodoId"
      responses:
        "204":
          description: 削除成功
        "404":
          description: TODOが見つからない

operationIdは 各操作に一意のIDを付けるもので、コード生成時に関数名として使われます。

parameters(パラメータ定義)

APIのパラメータにはpath(パスパラメータ)、query(クエリパラメータ)、header(ヘッダーパラメータ)の3種類があります。

# パスパラメータ: /todos/{todoId}
# URL の一部として値を渡す
parameters:
  - name: todoId
    in: path
    required: true          # pathパラメータは常にrequired: true
    description: TODOのID
    schema:
      type: integer
      example: 42

# クエリパラメータ: /todos?status=done&limit=10
# URLの ? 以降に付与される
  - name: status
    in: query
    required: false
    description: TODOのステータスでフィルタ
    schema:
      type: string
      enum:
        - todo
        - doing
        - done
      example: doing

  - name: limit
    in: query
    required: false
    description: 取得件数の上限
    schema:
      type: integer
      minimum: 1
      maximum: 100
      default: 20

# ヘッダーパラメータ
  - name: X-Request-Id
    in: header
    required: false
    description: リクエストの追跡用ID
    schema:
      type: string
      format: uuid

# components で再利用可能にする
components:
  parameters:
    TodoId:
      name: todoId
      in: path
      required: true
      description: TODOのID
      schema:
        type: integer
        example: 42

# 使い方: $ref で参照
# parameters:
#   - $ref: "#/components/parameters/TodoId"

よく使うパラメータはcomponents/parametersに 定義して$refで参照すると、重複を避けられます。

requestBody と responses

POST / PUT リクエストのボディと、各HTTPステータスコードに対応するレスポンスを定義します。

# requestBody の定義
requestBody:
  required: true
  description: 作成するTODOの情報
  content:
    application/json:           # Content-Type
      schema:
        $ref: "#/components/schemas/CreateTodoRequest"
      example:                  # リクエスト例
        title: "牛乳を買う"
        description: "スーパーで低脂肪牛乳を購入"
        priority: "high"

# responses の定義
responses:
  # 成功レスポンス
  "200":
    description: 成功
    content:
      application/json:
        schema:
          $ref: "#/components/schemas/Todo"
        example:
          id: 1
          title: "牛乳を買う"
          status: "todo"
          createdAt: "2025-01-15T10:30:00Z"

  # バリデーションエラー
  "400":
    description: リクエストが不正です
    content:
      application/json:
        schema:
          $ref: "#/components/schemas/Error"
        example:
          code: "VALIDATION_ERROR"
          message: "titleは必須です"
          details:
            - field: "title"
              message: "必須項目です"

  # 認証エラー
  "401":
    description: 認証が必要です

  # 権限エラー
  "403":
    description: アクセス権限がありません

  # Not Found
  "404":
    description: リソースが見つかりません
    content:
      application/json:
        schema:
          $ref: "#/components/schemas/Error"
        example:
          code: "NOT_FOUND"
          message: "指定されたTODOは存在しません"

  # サーバーエラー
  "500":
    description: サーバー内部エラー
    content:
      application/json:
        schema:
          $ref: "#/components/schemas/Error"
        example:
          code: "INTERNAL_ERROR"
          message: "予期しないエラーが発生しました"

exampleを記述しておくと、 Swagger UIで具体的な値が表示され、APIの使い方がわかりやすくなります。

schemas(スキーマ定義)

components/schemasにデータモデルを定義します。$refで参照することで 重複を排除し、allOfoneOfで スキーマを組み合わせることもできます。

components:
  schemas:
    # 基本の Todo スキーマ
    Todo:
      type: object
      required:
        - id
        - title
        - status
        - createdAt
      properties:
        id:
          type: integer
          description: TODOの一意なID
          example: 1
        title:
          type: string
          description: TODOのタイトル
          example: "牛乳を買う"
          maxLength: 100
        description:
          type: string
          description: TODOの詳細説明
          nullable: true
          example: "スーパーで低脂肪牛乳を購入"
        status:
          type: string
          enum:
            - todo
            - doing
            - done
          description: TODOのステータス
          example: "todo"
        priority:
          type: string
          enum:
            - low
            - medium
            - high
          default: "medium"
        createdAt:
          type: string
          format: date-time
          example: "2025-01-15T10:30:00Z"

    # 作成リクエスト用スキーマ
    CreateTodoRequest:
      type: object
      required:
        - title
      properties:
        title:
          type: string
          maxLength: 100
        description:
          type: string
          nullable: true
        priority:
          type: string
          enum: [low, medium, high]

    # 更新リクエスト用スキーマ(allOf で継承)
    UpdateTodoRequest:
      allOf:
        - $ref: "#/components/schemas/CreateTodoRequest"
        - type: object
          properties:
            status:
              type: string
              enum: [todo, doing, done]

    # エラーレスポンス
    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

    # oneOf の例:通知の種類で構造が変わる
    Notification:
      oneOf:
        - $ref: "#/components/schemas/EmailNotification"
        - $ref: "#/components/schemas/SlackNotification"
      discriminator:
        propertyName: type

    EmailNotification:
      type: object
      required: [type, to, subject]
      properties:
        type:
          type: string
          enum: [email]
        to:
          type: string
          format: email
        subject:
          type: string

    SlackNotification:
      type: object
      required: [type, channel]
      properties:
        type:
          type: string
          enum: [slack]
        channel:
          type: string

allOfは「すべてのスキーマを満たす」(継承に相当)、oneOfは「いずれか1つのスキーマを満たす」(ユニオン型に相当)です。

完全なTODO API仕様書

ここまで学んだ要素をすべて組み合わせた、完全なTODO API仕様書の全体像です。 実際のプロジェクトではこのようなファイルをopenapi.yamlとして管理します。

openapi: "3.0.3"
info:
  title: Todo API
  version: "1.0.0"
  description: |
    TODOアプリのREST API仕様書です。
    タスクの作成・取得・更新・削除(CRUD)をサポートします。

servers:
  - url: http://localhost:3000/api/v1
    description: 開発環境
  - url: https://api.example.com/v1
    description: 本番環境

tags:
  - name: todos
    description: TODO操作

paths:
  /todos:
    get:
      tags: [todos]
      summary: TODO一覧を取得
      operationId: getTodos
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [todo, doing, done]
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
      responses:
        "200":
          description: 成功
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Todo"
    post:
      tags: [todos]
      summary: TODOを作成
      operationId: createTodo
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateTodoRequest"
      responses:
        "201":
          description: 作成成功
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Todo"
        "400":
          $ref: "#/components/responses/BadRequest"

  /todos/{todoId}:
    get:
      tags: [todos]
      summary: TODO詳細を取得
      operationId: getTodo
      parameters:
        - $ref: "#/components/parameters/TodoId"
      responses:
        "200":
          description: 成功
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Todo"
        "404":
          $ref: "#/components/responses/NotFound"
    put:
      tags: [todos]
      summary: TODOを更新
      operationId: updateTodo
      parameters:
        - $ref: "#/components/parameters/TodoId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateTodoRequest"
      responses:
        "200":
          description: 更新成功
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Todo"
        "404":
          $ref: "#/components/responses/NotFound"
    delete:
      tags: [todos]
      summary: TODOを削除
      operationId: deleteTodo
      parameters:
        - $ref: "#/components/parameters/TodoId"
      responses:
        "204":
          description: 削除成功
        "404":
          $ref: "#/components/responses/NotFound"

components:
  parameters:
    TodoId:
      name: todoId
      in: path
      required: true
      schema:
        type: integer
  schemas:
    Todo:
      type: object
      required: [id, title, status, createdAt]
      properties:
        id:
          type: integer
        title:
          type: string
        description:
          type: string
          nullable: true
        status:
          type: string
          enum: [todo, doing, done]
        createdAt:
          type: string
          format: date-time
    CreateTodoRequest:
      type: object
      required: [title]
      properties:
        title:
          type: string
          maxLength: 100
        description:
          type: string
        priority:
          type: string
          enum: [low, medium, high]
    UpdateTodoRequest:
      allOf:
        - $ref: "#/components/schemas/CreateTodoRequest"
        - type: object
          properties:
            status:
              type: string
              enum: [todo, doing, done]
    Error:
      type: object
      required: [code, message]
      properties:
        code:
          type: string
        message:
          type: string
  responses:
    BadRequest:
      description: リクエストが不正です
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    NotFound:
      description: リソースが見つかりません
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"

components/responsesを 使うことで、共通のエラーレスポンスも再利用可能になります。

まとめ

  • pathsでエンドポイントとHTTPメソッドごとの操作を定義する
  • パラメータにはpathqueryheaderの3種類がある
  • requestBodyでPOST/PUTのリクエストボディを定義する
  • responsesでHTTPステータスコードごとのレスポンスを定義する
  • $refcomponentsでスキーマを再利用し、DRYに保つ
  • allOfでスキーマの継承、oneOfでユニオン型を表現できる