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開発では必ず導入を検討すべきです。
参考リンク