5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Redocly + Dockerで複数のOpenAPI YAMLを1つのドキュメントポータルに統合した話

5
Posted at

こんにちは!株式会社DearOneの小川です。
バックエンドのチームで、BaaS基盤の開発・運用を担当しています。

この記事でわかること
- 複数のOpenAPI(YAML)をRedoclyで1つのWebポータルに統合・共有する方法
- Dockerを活用した、ローカル環境を汚さないドキュメント自動生成手順
- 開発時に発生しやすい「ブラウザキャッシュ問題」の具体的な解決策
- 仕様違反をコミット前に自動検知する、lintの検証・運用方法

Qiita Tech Festa 2026の『はじめての記事投稿』テーマに参加しています!

はじめに

社内で管理している OpenAPI の YAML ファイルが増えてきたとき、こんな状況になっていませんか?

  • YAML はあるが、きれいなドキュメントとして見る手段が統一されていない
  • メンバーによって Swagger、Stoplight、VS Code 拡張などツールがバラバラ
  • 「このAPIどうやって確認するの?」という問い合わせが頻発する

この記事では、こうした課題を Redocly + 自作ポータル + Docker で解決した構成を紹介します。


Before:問い合わせが絶えない状態

私たちのリポジトリには、マイクロサービスごとの OpenAPI YAML が複数ファイル存在していました。

api_definitions/
├── Service_A_API.yaml
├── Service_B_API.yaml
├── Service_C_API.yaml
└── ...

YAML 自体は管理されていましたが、ドキュメントとして人が読める形にする仕組みがありませんでした。
確認方法は人それぞれで、チームに新しいメンバーが加わるたびに「どのツールで見ればいいの?」という質問が来ていました。


解決方針:全 API を一か所で見られるポータルを作る

単に「Redocly を入れてドキュメントを生成する」だけでは根本的な解決になりません。
API ファイルが複数あるため、全 API をまとめて閲覧できるポータルを作ることにしました。

ツール選定

まず候補に挙がったツールを比較しました。

ツール 概要 見送った理由
Swagger デファクトスタンダードとして広く普及しているAPIドキュメントビューア。Try it out でその場でリクエストを試せる lint 機能がなく、品質チェックのために別ツールを入れる必要がある
Stoplight GUI でフォームを埋める感覚で YAML を編集できる 操作の学習コストが高く、チーム共有に必要な機能が有料プランに限られる

Redocly を選んだ理由

上記を踏まえて Redocly を選びました。

  • lint と HTML 生成が1ツールで完結するredocly lint(仕様検証)と redocly build-docs(HTML 生成)が同一 CLI に揃っている
  • 無料で使える — **コアとなる CLI 機能はオープンソース(OSS)のため、**チーム規模に関係なくコストがかからない
  • CLI ベースなので Docker に組み込みやすいnpm install -g @redocly/cli だけで使えるので、Dockerfile に簡単な記述を追加するだけで環境が揃う

After:構成の全体像

最終的なディレクトリ構成はこうなりました。

api_definitions/
├── Service_A_API.yaml          # API 定義(YAML)
├── Service_B_API.yaml
├── Service_C_API.yaml
├── html/
│   ├── index.html              # ポータルのエントリポイント(サイドバー付き)
│   ├── service_a.html          # 各 API の Redoc HTML(自動生成)
│   └── service_b.html
├── generate_html_from_yaml.py  # HTML を自動生成するスクリプト
├── redocly.yaml                # lint 設定
├── Dockerfile
└── docker-compose.yml

html/index.htmlサイドバー + iframe の構成になっており、左のリストから API を選ぶと右フレームに Redoc のドキュメントが表示されます。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>API ポータル</title>
  <style>
    body { display: flex; margin: 0; height: 100vh; }
    nav  { width: 220px; background: #f5f5f5; overflow-y: auto; padding: 16px; flex-shrink: 0; }
    iframe { flex: 1; border: none; }
    ul { list-style: none; padding: 0; margin: 0; }
    li a { display: block; padding: 6px 8px; text-decoration: none; color: #333; border-radius: 4px; }
    li a:hover { background: #e0e0e0; }
  </style>
</head>
<body>
  <nav>
    <ul>
      <li><a href="service_a.html" target="api-frame">Service A</a></li>
      <li><a href="service_b.html" target="api-frame">Service B</a></li>
      <li><a href="service_c.html" target="api-frame">Service C</a></li>
    </ul>
  </nav>
  <iframe name="api-frame" src="service_a.html"></iframe>
</body>
</html>

新しい API を追加したときはリストに <li> を1行足すだけです。


実装:YAML から HTML を自動生成する

generate_html_from_yaml.py というスクリプトを書き、YAML ファイルから Redoc の HTML を一括生成します。

import os

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
OUT_DIR  = os.path.join(BASE_DIR, 'html')
REDOC_CDN = 'https://cdn.redocly.com/redoc/v2.5.1/bundles/redoc.standalone.js'

HTML_TEMPLATE = '''<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>{title}</title>
  <script src="{redoc_cdn}"></script>
</head>
<body>
  <div id="redoc-container"></div>
  <script>
    Redoc.init('{spec_url}', {{}}, document.getElementById('redoc-container'));
  </script>
</body>
</html>
'''

def find_yaml_files():
    return sorted(f for f in os.listdir(BASE_DIR) if f.endswith('_API.yaml'))

def yaml_to_html_name(yaml_name):
    # 例: Service_A_API.yaml → service_a.html
    return yaml_name.replace('_API.yaml', '').lower() + '.html'

def main():
    os.makedirs(OUT_DIR, exist_ok=True)
    for yaml_name in find_yaml_files():
        html_name = yaml_to_html_name(yaml_name)
        out_path  = os.path.join(OUT_DIR, html_name)
        if os.path.exists(out_path):
            continue  # 既存はスキップ(--force で上書き可にもできる)
        title    = os.path.splitext(yaml_name)[0]
        spec_url = f'../{yaml_name}'
        html     = HTML_TEMPLATE.format(title=title, redoc_cdn=REDOC_CDN, spec_url=spec_url)
        with open(out_path, 'w', encoding='utf-8') as f:
            f.write(html)
        print(f'生成: {out_path}')

if __name__ == '__main__':
    main()

*_API.yaml を列挙して HTML を出力するだけです。新しい YAML を追加したあとにスクリプトを実行すると HTML が1ファイル増えます。
※既存のHTMLがあると生成をスキップするため、YAMLの変更を反映したい場合は一度HTMLを削除して再実行するか、上書き処理を追加してください。


Docker で環境を統一する

Redocly CLI は Node.js が必要です。チーム全員にインストールしてもらうのは手間なので、Docker でまるごと閉じる構成にしました。

FROM node:20-slim

RUN apt-get update && apt-get install -y python3 && rm -rf /var/lib/apt/lists/*

# Redocly CLI をインストール
RUN npm install -g @redocly/cli

WORKDIR /app
# docker-compose.yml
services:
  api-docs:
    build: .
    volumes:
      - .:/app
    ports:
      - "8901:8901"
    command: sh -c "python3 generate_html_from_yaml.py && python3 -m http.server 8901"

これで docker compose up を叩くだけで、HTML 生成 → サーバー起動まで完了します。
Node.js も Python も、ローカルにインストール不要です。


ブラウザキャッシュ問題と no-cache サーバー

YAML を編集してブラウザをリロードしても、304 Not Modified が返って変更が反映されないという問題がありました。

http.server はデフォルトで Last-Modified ヘッダを返すため、ブラウザがキャッシュを使ってしまいます。

対策として、キャッシュを無効化する簡易サーバー serve_no_cache.py を用意しました。

import http.server, socketserver, os, mimetypes

class NoCacheHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
    def end_headers(self):
        self.send_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
        self.send_header('Pragma', 'no-cache')
        self.send_header('Expires', '0')
        super().end_headers()

    def send_head(self):
        path = self.translate_path(self.path)
        if os.path.isdir(path) or not os.path.exists(path):
            return super().send_head()
        try:
            f = open(path, 'rb')
        except OSError:
            return super().send_head()
        self.send_response(200)
        self.send_header('Content-Type', self.guess_type(path))
        self.send_header('Content-Length', str(os.fstat(f.fileno())[6]))
        # Last-Modified を返さないことで次回の If-Modified-Since を防ぐ
        self.end_headers()
        return f

PORT = 8901
with socketserver.TCPServer(('', PORT), NoCacheHTTPRequestHandler) as httpd:
    print(f'Serving on http://127.0.0.1:{PORT}')
    httpd.serve_forever()

ローカル確認用なので本番用途ではありませんが、開発中のストレスが大きく減りました。


lint で品質ゲートを設ける

YAML の書き間違いを早期に検出するため、redocly.yaml で lint ルールを設定しています。

rules:
  no-unresolved-refs: error  # 壊れた $ref を検出
  struct: error              # OpenAPI 仕様違反を検出

コミット前に以下を実行します。

redocly lint Service_A_API.yaml

# Docker 経由
docker compose run --rm api-docs redocly lint Service_A_API.yaml

結果の見方:

結果 意味
Woohoo! Your API description is valid. 問題なし
Warning 軽微な問題(対応推奨)
❌ Validation failed 仕様違反。必ず修正してからコミット

まとめ

Before After
ドキュメント YAML を直接読む ブラウザで Redoc 表示
確認ツール 各自バラバラ docker compose up で統一
品質管理 なし lint でコミット前に検証
問い合わせ 「どうやって見るの?」が多かった セットアップ手順1行で完結

Redocly 自体のセットアップは簡単ですが、複数 YAML の管理・Docker 化・ポータル構成まで含めて整備することで、チーム全体の開発体験が改善しました。

同じような状況の方の参考になれば幸いです。

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?