外部境界から考える
API型設計をTypeScriptで考えるとき、レスポンス型をそのまま画面で使っていないでしょうか。
小さな画面なら、それでも動きます。しかし、実務ではAPI仕様の変更、nullableな値、表示用の加工が増えます。その結果、画面側に条件分岐が散らばりやすくなります。
この記事では、API型設計でレスポンス型、DTO、画面用モデルを分ける理由を整理します。目的は、API変更に強く、レビューしやすい型設計にすることです。
API型設計でレスポンス型をそのまま使わない理由
まず、APIレスポンス型は外部から来るデータです。つまり、フロントエンド側で完全には制御できません。
例えば、APIから次のようなユーザー情報が返るとします。
type UserResponse = {
id: string;
first_name: string;
last_name: string;
status: "active" | "inactive";
last_login_at: string | null;
};
この型をそのまま画面に渡すと、画面側がAPIの命名に依存します。また、日付表示やステータス表示の加工も画面に散らばります。
そのため、実務ではAPIレスポンス型と画面用モデルを分ける方が保守しやすくなります。
APIレスポンス型と画面用モデルを分ける
次に、APIレスポンス型を画面用モデルへ変換します。ここで命名、nullable、表示用の値を整理します。
type UserViewModel = {
id: string;
fullName: string;
statusLabel: string;
lastLoginText: string;
};
function toUserViewModel(user: UserResponse): UserViewModel {
return {
id: user.id,
fullName: `${user.last_name} ${user.first_name}`,
statusLabel: user.status === "active" ? "有効" : "停止中",
lastLoginText: user.last_login_at ?? "未ログイン",
};
}
この変換を入れると、画面コンポーネントは表示に集中できます。また、API側の命名やnullの扱いを変換層に閉じ込められます。
一方で、すべての小さなAPIで過剰にモデルを分ける必要はありません。複数画面で使うデータや、加工が多いデータから分けると現実的です。
API型設計でDTOをどう扱うか
DTOという言葉は、現場によって意味が揺れやすいです。フロントエンドでは、APIとやり取りするためのデータ型として使うことが多いです。
実務では、次のように分けると整理しやすくなります。
| 型 | 役割 | 例 |
|---|---|---|
| Response型 | APIから返る値 | UserResponse |
| Request型 | APIへ送る値 | UpdateUserRequest |
| ViewModel | 画面で使う値 | UserViewModel |
| FormValues | フォーム入力値 | UserFormValues |
このように名前を分けると、レビュー時に責務を確認しやすくなります。また、APIへ送る値と画面で持つ値を混同しにくくなります。
unknownと実行時検証でAPI境界を守る
TypeScriptの型は、実行時のデータを保証しません。fetchで受け取ったJSONは、型注釈を書いても本当にその形とは限りません。
そのため、外部から来る値はunknownとして受ける考え方が役に立ちます。必要に応じて、型ガードやスキーマ検証で確認します。
async function fetchJson(url: string): Promise<unknown> {
const response = await fetch(url);
return response.json();
}
型の絞り込みについては、TypeScript公式のNarrowingも参考になります。
また、APIレスポンスを実行時に検証したい場合は、Zodのようなスキーマ検証ライブラリを使う選択肢もあります。
API型設計でレビューされやすいポイント
API型設計では、型があるかどうかだけでなく、型の置き場所がレビューされます。特に次の観点は見られやすいです。
- APIレスポンス型を画面に直接渡していないか
- Request型とResponse型を混同していないか
- nullやundefinedの扱いが画面に散らばっていないか
- 表示用の文字列変換がコンポーネントに入りすぎていないか
- 外部データをanyで受け取っていないか
なお、Utility Typesを使う場合は、意図が読みやすいかも確認しましょう。公式のUtility Typesも参考になります。
よくある質問
TypeScriptでAPIレスポンス型は必ず分けるべきですか?
必ずではありません。ただし、複数画面で使うデータや加工が多いデータは分けた方が保守しやすいです。
DTOとViewModelの違いは何ですか?
DTOはAPIとの入出力に近い型です。ViewModelは画面表示に近い型です。責務を分けると、API変更の影響を抑えやすくなります。
APIレスポンスはanyで受けてもよいですか?
一時的には使えます。ただし、anyのまま画面へ広げると型安全が崩れます。unknownや実行時検証を使う方が安全です。
zodは必ず必要ですか?
必須ではありません。ただし、外部APIや仕様変更が多いAPIでは、実行時検証を入れる価値があります。
まとめ:API型設計は境界を分けると保守しやすい
API型設計では、APIレスポンス型をそのまま画面で使わないことが重要です。
- APIレスポンス型は外部境界として扱う
- 画面用モデルへ変換する層を作る
- Request型とResponse型を分ける
- unknownや実行時検証で外部データを守る
- コンポーネントは表示に集中させる
API型設計を整理すると、Reactコンポーネントも読みやすくなります。また、仕様変更時の影響範囲も追いやすくなります。
ReactやTypeScriptの経験を活かせる案件について整理したい方は、カジュアル面談で相談することもできます。また、案件内容や支援体制を知りたい方は、bluenaの採用情報も参考にしてください。
「TypeScriptの経験を次の案件でどう活かすか」に迷っているなら、まずbluenaに話してみてください。
一度カジュアル面談をしませんか?
株式会社bluenaは「高還元」と「伴走支援」を両立したSES企業です。単価の81〜86%を還元する報酬体系と、専任サポーターによる隔週1on1で、エンジニアが納得できるキャリアを実現します。
まとまっていなくてもOK——まずは現在地を聞かせてください。
カジュアル面談ですので、お気軽にお聞かせください。





