ERDとAPIは表裏一体
バックエンド開発では、データベース設計(ERD)とAPI設計が密接に連携しています。ERDで定義したエンティティと関係性が、そのままAPIのエンドポイント設計に反映されることがほとんどです。しかし、データモデルとAPIの不整合が蓄積すると、N+1問題、過剰なデータ取得、複雑なマッピングロジックといった問題が発生します。ERDを起点にAPIを設計することで、一貫性のある保守しやすいシステムを構築できます。
ステップ1:ERDからリソースを特定する
REST APIの「リソース」はERDの「エンティティ」に対応します。まずERDのテーブルをリストアップし、どれがAPIリソースになるかを判断します。
-- ERDのテーブル定義
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(200) NOT NULL,
owner_id UUID REFERENCES users(id) NOT NULL,
description TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE project_members (
project_id UUID REFERENCES projects(id),
user_id UUID REFERENCES users(id),
role VARCHAR(20) DEFAULT 'viewer', -- owner/admin/editor/viewer
joined_at TIMESTAMP DEFAULT NOW(),
PRIMARY KEY (project_id, user_id)
);
このERDから以下のAPIリソースが導かれます:
/users— ユーザーリソース/projects— プロジェクトリソース/projects/{id}/members— プロジェクトメンバー(関係リソース)
ステップ2:クエリパターンからAPIを設計する
APIのレスポンス設計は、実際に必要なクエリパターンから逆算します。
-- ユースケース1:ダッシュボード - 自分のプロジェクト一覧
-- GET /projects?member=me
SELECT
p.id,
p.name,
p.description,
p.created_at,
u.name AS owner_name,
pm.role AS my_role,
COUNT(pm2.user_id) AS member_count
FROM projects p
JOIN users u ON p.owner_id = u.id
JOIN project_members pm ON p.id = pm.project_id AND pm.user_id = $1
LEFT JOIN project_members pm2 ON p.id = pm2.project_id
GROUP BY p.id, u.name, pm.role
ORDER BY p.created_at DESC;
-- ユースケース2:プロジェクト詳細 + メンバー一覧
-- GET /projects/{id}
SELECT
p.*,
json_agg(
json_build_object(
'user_id', u.id,
'name', u.name,
'email', u.email,
'role', pm.role
) ORDER BY pm.joined_at
) AS members
FROM projects p
LEFT JOIN project_members pm ON p.id = pm.project_id
LEFT JOIN users u ON pm.user_id = u.id
WHERE p.id = $1
GROUP BY p.id;
ステップ3:ネストvsフラット化のトレードオフ
APIレスポンスを深くネストするか、フラットにするかはデータモデルの関係性に依存します。
-- アプローチA:ネストされたレスポンス(関係性が強い場合)
-- GET /projects/{id}/members → 専用エンドポイント
SELECT
u.id,
u.name,
u.email,
pm.role,
pm.joined_at
FROM project_members pm
JOIN users u ON pm.user_id = u.id
WHERE pm.project_id = $1
ORDER BY
CASE pm.role
WHEN 'owner' THEN 1
WHEN 'admin' THEN 2
WHEN 'editor' THEN 3
ELSE 4
END;
-- アプローチB:フラットなレスポンス(検索・フィルタリングが多い場合)
-- GET /members?project={id}&role=editor
SELECT
pm.project_id,
p.name AS project_name,
u.id AS user_id,
u.name AS user_name,
pm.role
FROM project_members pm
JOIN projects p ON pm.project_id = p.id
JOIN users u ON pm.user_id = u.id
WHERE pm.project_id = $1
AND ($2::text IS NULL OR pm.role = $2);
ステップ4:ページネーションとカーソルベース設計
大規模なデータセットには、オフセットベースよりもカーソルベースのページネーションが推奨されます。
-- ❌ オフセットベース(大量データで遅くなる)
SELECT * FROM projects ORDER BY created_at DESC LIMIT 20 OFFSET 1000;
-- ✅ カーソルベース(常に一定速度)
-- GET /projects?cursor={last_seen_id}&limit=20
SELECT *
FROM projects
WHERE created_at < $1 -- cursor = 最後に取得したcreated_at
ORDER BY created_at DESC
LIMIT 20;
-- 次ページのカーソルはレスポンスに含める
-- { "data": [...], "next_cursor": "2026-04-17T10:30:00Z" }
Erdmatesで設計を共有
ErdmatesはERDの視覚化だけでなく、APIと連携するデータフローを設計チームとバックエンドチームが共同でレビューする場としても活用できます。ERDからAPIエンドポイントのマッピングドキュメントを生成する機能を使えば、フロントエンドとバックエンドの認識齟齬を防げます。
まとめ
ERDとAPI設計は分離して考えるのではなく、常に整合性を保つことが重要です。ERDを起点にリソースを特定し、実際のクエリパターンからAPIの形状を決定するアプローチにより、「データベースにあるものをAPIで提供する」という一貫した設計が実現します。変更が必要になった際も、ERDを更新することでAPI設計への影響を素早く把握できます。