🫱

フロント目線で考えるAPI設計のポイント|TypeScriptで“語れる”設計判断

に公開

はじめに

フロントエンド開発をしていると、

  • API を叩いて画面に表示できる
  • React / Next.js で一通りの実装ができる

という段階までは比較的スムーズに到達できます。

一方で、チーム開発・設計フェーズで評価される人は、
そこからもう一歩踏み込んだ視点を持っています。

それは

「フロント目線で API を評価・説明できるか」

という視点です。

この記事は

  • フロントエンドエンジニアが
    「API をどう見ると評価が上がるのか」
  • バックエンドエンジニアが
    「フロントにとって使いやすい API とは何か」

TypeScript を軸に整理したものです。

この記事で伝えたいこと

  • フロントエンドは「API を使うだけの立場」ではない
  • API 設計はフロント・バックエンド共通の設計テーマ
  • TypeScript は設計判断を言語化するための道具

① フロント目線で考える API 設計・評価とは

❌ 実装できるだけの API 利用

fetch("/api/users")
  .then((res) => res.json())
  .then((data) => setUsers(data));

これは動きます。
ただし、このコードからは設計の話ができません

  • API の形は妥当か?
  • フロントで余計な責務を持っていないか?
  • 将来の変更に耐えられるか?

といった視点が抜けています。

✅ 一歩踏み込んで API を評価する人の視点

1. 「画面単位」で API を見る

例:ユーザー一覧画面

GET /api/users
{
  "users": [{ "id": 1, "name": "Alice", "email": "a@example.com" }]
}

ここで一歩踏み込む人はこう考えます。

  • 一覧と詳細で同じレスポンス構造でよいか?
  • ページング・ソートはどこで責務を持つべきか?
  • 画面に不要な項目まで返していないか?

👉 API を「画面のためのインターフェース」として評価している

これは、バックエンド側で API を設計する際にも非常に重要な視点です。

2. フロントで加工しなくて済む API か?

❌ フロントに加工を押し付けた API

users.map((u) => ({
  ...u,
  displayName: `${u.lastName} ${u.firstName}`,
}));

全てバックエンドで行うべきだと言っているのではないが、
この状態が増えるほど、

  • 表示ロジックが各所に散らばる
  • 修正漏れ・不整合が起きやすくなる

✅ 一歩踏み込む人の考え方

「この加工、API 側の責務として返せないか?」

type User = {
  id: number;
  displayName: string;
  email: string;
};

👉 API 設計 = 責務分担の設計

フロントの実装を見て API を評価できる人はバックエンド設計の議論にも参加できるようになります。

3. TypeScript で API 仕様を「コードで説明できる形」にする

API を使うとき 「どんなデータが返ってくるか」 が曖昧なまま実装してしまうケースは多いです。

❌ 実装できるだけの状態
const res = await fetch("/api/users");
const data: any = await res.json();
  • 何が返ってくるかは実行時まで分からない
  • API の変更に気づけない
  • レビューでも設計の話ができない

👉 「動くけど、考えた形が残らない」コード

✅ 一歩踏み込んだ考え方
type User = {
  id: number;
  name: string;
  email: string;
};

type GetUsersResponse = {
  users: User[];
};

この型があるだけで、

  • この API は「users 配列を返す」と一目で分かる
  • フロント側の前提条件がコードに残る
  • API 仕様が変わると 型エラーで気づける

👉 TypeScript は「API の説明書をコードで残す道具」

バックエンド側が API を変更したときも、フロント側で「どこに影響が出るか」がすぐ分かります。

これは、フロントとバックエンドの共通言語として非常に強力です。

4. エラーも含めて API を設計・評価する

API を使うとき、つい「成功する前提」でコードを書いてしまいがちです。

しかし実務では、

  • バリデーションエラー
  • 認証・認可エラー
  • サーバエラー

必ず起きます

❌ 成功系しか考えていない例
const users = await fetchUsers();
setUsers(users);
  • エラー時に何が起きるか分からない
  • UI 側での分岐が後付けになる
✅ エラーを前提にした API 利用

まず、API の返り値の形を型で決めます。

type ApiError = {
  code: string;
  message: string;
};

type GetUsersResult =
  | { success: true; data: User[] }
  | { success: false; error: ApiError };

次に、この型を 返す API 関数を用意します。

async function fetchUsers(): Promise<GetUsersResult> {
  const res = await fetch("/api/users");

  if (!res.ok) {
    return {
      success: false,
      error: {
        code: "FETCH_FAILED",
        message: "ユーザー一覧の取得に失敗しました",
      },
    };
  }

  const data = (await res.json()) as GetUsersResponse;

  return {
    success: true,
    data: data.users,
  };
}

そして UI 側で使うときはこうなります。

const result = await fetchUsers();

if (result.success) {
  setUsers(result.data);
} else {
  setErrorMessage(result.error.message);
}

こうすることで、

  • エラー時の UI を最初から考えられる
  • 「どんな失敗が起きるか」を設計として説明できる
  • フロント・バックエンド間で認識が揃う

👉 エラー設計も API 設計の一部

② TypeScript で語れる状態設計とは

❌ 状態を「ただ持っているだけ」の状態

const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(false);

この書き方はシンプルですが、

  • 初期表示なのか
  • 再読み込み中なのか
  • エラーが起きているのか

が分かりません。

結果として、

  • if (loading) が増える
  • UI の条件分岐が散らかる
  • 状態の意味を説明できない

という問題が起きます。

✅ 一歩踏み込んだ状態設計

1. 状態を「意味」で定義する

type LoadState = "idle" | "loading" | "success" | "error";

type UsersState = {
  data: User[];
  state: LoadState;
  errorMessage?: string;
};

これにより、

  • loading 中はローディングを表示
  • error ならエラーメッセージを表示
  • success なら一覧を表示

と、UI と状態が自然に対応します。

👉 状態は「UI の設計図」

2. サーバ状態と UI 状態を分けて考える

// APIデータ
const { data: users } = useQuery({
  queryKey: ["users"],
  queryFn: fetchUsers,
});
// 画面操作に閉じた状態(API とは無関係)
const [selectedUserId, setSelectedUserId] = useState<number | null>(null);
  • users:API 由来のデータ(サーバ状態)
  • selectedUserId:画面固有の状態(UI 状態)

これを分けることで、

  • API 再取得の影響範囲が分かりやすい
  • 画面が複雑になっても整理できる

👉 責務を分けることが、状態設計の第一歩

3. 状態管理ライブラリを「理由付き」で選ぶ

useQuery({
  queryKey: ["users"],
  queryFn: fetchUsers,
});

ここで語れる人は、こう説明できます。

  • サーバ状態を React の state から切り離したい
  • 再取得・キャッシュを UI とは別の責務にしたい

👉 「便利だから」ではなく「設計上そうあるべきだから」

まとめ

「フロント目線で考える API 設計・評価」とは

  • API を画面単位で評価できる
  • フロント・バックエンドの責務を意識できる
  • TypeScript で仕様と意図を表現できる
  • なぜその設計なのか説明できる

ということです。

この視点を持てるようになると、

  • フロントエンドエンジニアは
    「書ける人」から「任せられる人」へ
  • バックエンドエンジニアは
    「作れる API」から「使われる API」へ

一段階レベルアップできます。

Discussion