バックエンド目線で見る「フロントにとって使いやすいAPI設計」
はじめに
バックエンドエンジニアとして API を設計・実装していると、
- フロントから「この API 使いづらいです」と言われる
- 仕様通りに作ったはずなのに、フロント側で加工コードが増えている
といった経験はないでしょうか。
それはスキル不足ではなく、「バックエンド視点だけで API を設計している」 ことが原因であるケースがほとんどです。
この記事では、バックエンドエンジニアがフロントエンドの実装・状態管理を理解した上で 「使いやすい API とは何か」を説明できるようになる ことを目的に、実務視点で API 設計を整理します。
1. 問題点:よくある「バックエンド都合 API」
❌ 実装は楽だが、フロントが困る例
GET /api/users
[
{
"id": 1,
"first_name": "Taro",
"last_name": "Yamada",
"created_at": "2025-01-01T10:00:00Z",
"updated_at": "2025-01-10T10:00:00Z"
}
]
バックエンド的には自然です。
- users テーブルをそのまま返している
- 正規化されたカラム構成
しかしフロントから見ると、次の問題があります。
- 表示に不要な項目が多い
- 表示用の
fullNameを毎回組み立てる必要がある - 一覧画面と詳細画面で欲しい情報量が違う
👉 「DB に近い API」になっている
2. フロント実装を見据えた設計の 3 つの視点
API は DB の公開窓口ではなく、「画面のためのデータ供給口」 です。フロントエンドの実装負荷を下げるために、以下の 3 つの視点を意識しましょう。
視点1:画面(UI)単位でデータを考える
フロントは「エンドポイント」ではなく「画面」を作っています。
ユーザー一覧画面
- 名前
- メールアドレス
- ステータス
この画面のための API であれば、レスポンスはこうあるのがよいでしょう。
{
"users": [
{
"id": 1,
"name": "山田 太郎",
"email": "taro@example.com",
"status": "active"
}
]
}
- 表示に必要な形で返す
- フロントでの加工を最小化する
👉 API は画面のためのデータ供給口
視点2:フロントエンドに判断を委ねない
バックエンドで判定できるロジックは、判断結果をそのまま返しましょう。
{
"status": 1
}
// フロント側
const statusLabel = status === 1 ? "有効" : "無効";
この判断が複数画面に散らばると、表示ゆれや修正漏れの原因になります。
✅ 判断結果を返す(ロジックをバックエンドに寄せる)
{
"status": "active"
}
const STATUS_LABEL: Record<string, string> = {
active: "有効",
inactive: "無効",
};
👉 業務ルールはバックエンドが責任を持つ
視点3:TypeScript の型を意識したスキーマ設計
フロントでは、API レスポンスはそのまま型になります。
type UserListItem = {
id: number;
name: string;
email: string;
status: "active" | "inactive";
};
ここで意識すべきことは、
- null を返さない設計(可能な限り初期値や空配列にする)
- フィールドの意味が型名から伝わること
👉 API = 型の設計でもある
3. 実践:フロントエンドが喜ぶ具体的な設計ポイント
原則を踏まえ、実務ですぐに意識できる具体的なポイントを整理します。
3.1 日時データは ISO 8601 で統一する
❌ フォーマットが曖昧な日付
{
"createdAt": "2025-01-12 10:23:45"
}
- タイムゾーンが不明
- 画面・環境ごとに解釈が変わる
- フロント側で毎回変換処理が必要
✅ ISO 8601 形式で統一
{
"createdAt": "2025-01-12T10:23:45Z"
}
type User = {
id: number;
createdAt: string; // ISO 8601
};
👉 バックエンドは「日時の正解」を提供し、表示はフロントに任せる
3.2 null や undefined の意味を型で明確にする
❌ 意味が曖昧な null
{
"nickname": null
}
- 未設定なのか
- 将来入る予定なのか
- 仕様なのかバグなのか
フロントは毎回悩むことになります。
✅ 意味を型で表現する
{
"nickname": "aki"
}
type User = {
id: number;
nickname?: string; // 未設定の場合は key 自体を返さない、もしくは明示的な null
};
👉 null を返す前に「この値は存在しうるのか?」を設計で決める
3.3 HTTP ステータスコードとレスポンス Body の責務分担
❌ 200 OK でエラー内容を返す
200 OK
{
"error": "Unauthorized"
}
- 成功か失敗かが HTTP レベルで判断できない
- フロントで独自判定が必要
✅ HTTP ステータスと body を分けて設計
401 Unauthorized
{
"code": "UNAUTHORIZED",
"message": "ログインが必要です"
}
👉 ステータスコードは「結果」、body は「理由」を伝える
3.4 UI 制御のためのエラー設計
❌ エラー内容が曖昧な API
{
"message": "error"
}
✅ フロントで分岐できるエラー(Error Code の付与)
{
"code": "USER_NOT_FOUND",
"message": "ユーザーが存在しません"
}
if (error.code === "USER_NOT_FOUND") {
showToast("ユーザーが見つかりません");
}
👉 エラーは UI 制御のための情報
4. まとめ:フロントエンドの実装まで想像できるバックエンドへ
バックエンドがここまで考えられると、単なる実装者ではなく 「設計者」 としての価値が上がります。
バックエンド目線で「フロントにとって使いやすい API」とは、
- フロントが迷わないデータを返す
- 型で仕様を説明できる
- 実装ではなく設計で負担を減らす
API を「実装物」ではなく、フロントとのインターフェースとして設計できると、チーム開発はよりスムーズになります。
バックエンドだからこそ、フロントの実装まで想像した API 設計を意識してみてください。
Discussion
システムが進化するとAPIの利用者は、フロントエンドだけではなくて別のバックエンド(別のマイクロサービス)になりますよ。
その時には、画面仕様に合わせたAPIは後悔しますよ。画面のリニューアルの時もバックエンドの大改修が入って、プロジェクトマネージャーから何でそんなに工数がかかるの?って詰められそう。
画面の為には、Backend For Frontendを挟むとか、REST APIではなくGraphQLにするとかがセオリーかな。