フロントエンド・API連携

Laravel APIとTypeScriptの型同期:スキーマ駆動開発で破綻を防ぐ要点

LaravelのAPIレスポンスを変更した後、TypeScript側の型定義だけが古いまま残る。結果として、ビルドは通るが画面で undefined が発生する。あるいは、バックエンドの変更直後にフロントエンドのビルドが失敗する。…

Laravel APIとTypeScriptの型同期:スキーマ駆動開発で破綻を防ぐ要点

この問題は、TypeScriptの記述力では解決しない。原因は、Laravelとフロントエンドが別々の型定義を持っていることである。APIの仕様をPHP側とTypeScript側で二重管理している限り、修正漏れは構造的に発生する。

Laravel APIとTypeScriptの型同期を自動化するには、API仕様を単一の情報源として扱う必要がある。LaravelからOpenAPI仕様を生成し、その仕様からTypeScriptの型を生成する。PHPのDTOやEnumを直接TypeScriptへ変換する方法もある。

本質はツールの選択ではない。どの層を正規の仕様とするかを決め、変更時に同期処理を強制する設計である。

手動で管理する型定義は、変更に弱い

Laravelで次のようなレスポンスを返すとする。

ユーザー情報として、識別子、表示名、メールアドレス、登録日時を返している。フロントエンドには、それに対応するTypeScriptの型定義を記述する。初期段階では問題がない。

しかし、実際の開発ではレスポンスは変化する。

  • namedisplay_name に変更する
  • email を非公開にする
  • created_at の形式を変更する
  • ネストされた profile を追加する
  • 一覧APIにページネーションを導入する
  • 権限によって返却項目を変える
  • バリデーションエラーの構造を統一する

バックエンドの実装を変更しても、TypeScriptの型定義は自動では変わらない。開発者が関連ファイルを把握し、手作業で更新する必要がある。

このとき、型定義の更新漏れが発生する。

コンパイルが検出できる問題と、検出できない問題

型の不整合には、少なくとも二種類ある。

一つは、TypeScriptのコンパイルで検出できる不整合である。たとえば、型定義上は user.name が存在するが、実際のレスポンスでは display_name に変更されている場合、正しい型が生成されていればビルドエラーになる。

もう一つは、型定義そのものが古いために検出できない不整合である。フロントエンドが次のような型を保持しているとする。

User 型には name: string が存在する。しかし、Laravel APIはすでに display_name: string を返している。この場合、TypeScriptは手元の型定義を正しいものとして扱う。コード上の user.name にエラーは出ない。実行時に値が存在しないだけである。

つまり、型チェックの精度は型定義の正確性に依存する。古い型を厳密にチェックしても、現在のAPI仕様との不整合は検出できない。

TypeScriptの型安全性は、型定義がAPIの現実を反映している場合にだけ成立する。

手動同期で見落とされやすい箇所

単純なレスポンスだけなら、手動修正でも対応できる。しかし、Laravel APIではデータ構造が複数の層に分かれる。

リソースクラス

LaravelのAPIリソースを使っている場合、モデルの属性と実際のレスポンスは一致しないことがある。User モデルに多数の属性が存在していても、UserResource が返す項目は限定できる。

モデルの型をそのままフロントエンドへ渡す設計では、非公開項目まで型に含まれる可能性がある。これは単なる型の問題ではない。データ公開範囲の設計ミスである。

ページネーション

Laravelの LengthAwarePaginatorCursorPaginator を使うと、レスポンスにはデータ本体だけでなく、ページ情報やリンク、メタ情報が含まれる。

一覧画面側で、単純な配列として扱っていると処理が破綻する。

ページ番号方式とカーソル方式でも構造や利用方法が異なる。型定義が User[] だけでは、ページネーションの実装を表現できない。

日付と時刻

PHP側の CarbonCarbonInterface は、JSONレスポンスでは通常文字列になる。TypeScript側で Date と定義すると、実際の値との差異が生じる。

APIから受け取った時点では文字列であり、日時オブジェクトではない。日時ライブラリで変換するなら、その変換処理を境界に置く必要がある。

Enum

PHPのEnumを導入しても、フロントエンドが許容値を把握していなければ、文字列として処理される。状態値や権限値の追加時に、画面の分岐が不足する。

Enumは値の一覧を型に反映しやすい領域である。手動定義を残す理由は少ない。

API仕様を中心に置くスキーマ駆動開発

LaravelとTypeScriptを接続する場合、代表的な設計は三つある。

方式正規の仕様型生成の方向適したケース
OpenAPI中心APIのOpenAPI仕様OpenAPIからTypeScriptREST APIを複数クライアントで利用する
PHPクラス中心DTO、Enum、PHPクラスPHPからTypeScriptLaravel内部のデータ構造を共有する
モデル中心EloquentモデルモデルからTypeScriptモデル属性やリレーションを参照したい

OpenAPI中心の方式では、APIの外部契約を明示する。ReactやVueのフロントエンドだけでなく、モバイルアプリや外部サービスとの連携にも向いている。

PHPクラス中心の方式では、Laravel側のDTOやEnumを型の起点にできる。API仕様を別途手書きする量を減らせる。

モデル中心の方式は導入しやすい。ただし、Eloquentモデルの構造とAPIレスポンスの構造が同一であるとは限らない。APIリソースや変換処理を多用する場合は、モデル型をそのまま公開契約として扱うべきではない。

OpenAPIはフロントエンドのためだけの形式ではない

OpenAPIをTypeScriptの型生成用ファイルとしてだけ扱うと、設計上の効果を狭めることになる。

OpenAPIには、次の情報を表現できる。

  • エンドポイントのパス
  • HTTPメソッド
  • パラメータ
  • リクエストボディ
  • レスポンス構造
  • 必須項目
  • Enumの値
  • エラー応答
  • 認証方式
  • ページネーションの構造

この仕様を正規の契約として管理すれば、フロントエンドの型だけでなく、APIドキュメントやクライアント生成にも利用できる。

APIの変更を実装ファイルだけで完結させず、契約の変更として扱える点が重要である。

LaravelからOpenAPIを生成する

Laravel向けのOpenAPI生成ツールとして、dedoc/scramble がある。導入後は、routes/api.php に定義されたルートなどをもとにOpenAPI仕様ドキュメントを自動生成できる。

[Composerでの導入コマンドは composer require](/articles/aishi-dainoenjinianoyi-ge/) dedoc/scramble である。

生成された仕様は、たとえばOpenAPI 3.0.3形式のドキュメントとして扱える。実際の生成結果は、コントローラーの戻り値、リクエストクラス、PHPDoc、ルート定義などの書き方に左右される。

ここで注意すべきなのは、自動生成が実装の曖昧さを消してくれるわけではない点である。

戻り値の構造がコードから読み取りにくい場合、生成されたスキーマも不十分になる。APIリソースの構造やレスポンスの例外を適切に表現するには、実装側の型情報やドキュメントが必要である。

自動生成は、設計の代替ではない。設計された実装から契約を抽出する仕組みである。

dedoc/scramble と openapi-typescript の接続

OpenAPI仕様からTypeScript型を生成するには、openapi-typescript を利用できる。

代表的な実行コマンドは次の形式である。コマンドはインラインで示す。

npx openapi-typescript --output resources/ts/types/schema.d.ts

この処理により、Laravelから生成したOpenAPI仕様をTypeScriptの宣言ファイルへ変換できる。生成先はプロジェクトの構成に合わせてよい。resources/ts/types/schema.d.ts のように、アプリケーションコードと生成ファイルを分離しておくと管理しやすい。

生成された型を直接書き換えない

生成ファイルは、手動編集の対象にしない。変更しても次回の生成で消えるためである。

たとえば、APIのレスポンス型を利用する場合は、生成された pathscomponents をアプリケーション側で参照する。

OpenAPIの構造では、再利用するデータモデルを components.schemas に定義する。TypeScript側では、そのスキーマからレスポンス型を取り出す形になる。

実装上は、次のような役割分担になる。

  • Laravelのリクエストクラスが入力仕様を表す
  • APIリソースが出力仕様を表す
  • dedoc/scramble がOpenAPIへ変換する
  • openapi-typescript がTypeScript型へ変換する
  • VueやReactのデータ取得処理が生成型を参照する

この流れを一度作ると、フロントエンド側でレスポンス型を新規作成する場面が減る。

APIクライアント層で型を固定する

生成型をコンポーネント内で直接使うと、型の参照方法が各画面に分散する。APIクライアント層を設け、通信処理と型の対応を集約する方がよい。

Axiosを利用する場合でも、通信部分を画面から分離する。

たとえば、ユーザー一覧APIについて、次の責務を分ける。

1. APIクライアントがHTTP通信を担当する。

2. 生成型がレスポンス構造を表す。

3. 状態管理層が取得結果を保持する。

4. UIコンポーネントが表示だけを担当する。

この分離により、APIのレスポンス形式が変更された際に修正箇所を限定できる。

Axiosの型引数に生成型を指定する方法もある。ただし、型引数を付けただけでは実際のレスポンスを検証したことにはならない。TypeScriptの型は実行時に消えるためである。

外部入力の検証まで必要な場合は、OpenAPIから実行時スキーマを生成する構成や、別途検証ライブラリを導入する構成を検討する必要がある。現時点で、すべてのバリデーションルールを自動生成だけで完全にカバーできるとは限らない。

生成型はAPIの境界で使う

生成型をドメイン全体へそのまま流すと、APIの都合がUIや業務ロジックへ侵入する。

たとえば、APIレスポンスの日付が文字列である場合、画面内部でも常に文字列のまま扱うとは限らない。日時表示や並べ替えを行うなら、アプリケーション内部のモデルへ変換する設計が必要になる。

境界ではAPI型を使う。内部では内部用の型へ変換する。

この変換を省略すれば実装量は減る。しかし、API仕様と画面仕様が密結合になる。APIのレスポンス形式を変更できなくなり、長期的には依存関係が増える。

PHPのDTOとEnumをTypeScriptへ変換する

OpenAPIを使わず、LaravelのPHPクラスを型の起点にする方法もある。

spatie/laravel-typescript-transformer を使うと、PHPクラス、Enum、DTOなどをTypeScript型へ変換できる。導入コマンドは composer require spatie/laravel-typescript-transformer である。

変換処理は php artisan typescript:transform で実行できる。

この方式の特徴は、API全体の仕様ではなく、PHP側で定義したデータ構造を明示的に共有できることである。

DTOを契約として使う

Laravelのコントローラーやサービスクラスが配列を直接返している場合、レスポンス構造はコードの複数箇所に分散しやすい。

DTOを導入すると、データ構造をクラスとして表現できる。

  • プロパティ名が固定される
  • 型がPHP側で明示される
  • データ変換の責務を集約できる
  • TypeScriptへの変換対象を限定できる
  • コントローラーの戻り値が読みやすくなる

DTOは、EloquentモデルとAPIレスポンスの間に置く変換層として機能する。

モデルの全属性をAPIへ出すのではなく、公開する項目をDTOで定義する。これにより、内部データ構造と外部契約を分離できる。

ただし、DTOからTypeScript型を生成しただけで、実際のJSONレスポンスと完全に一致するとは限らない。シリアライズの設定、属性名の変換、Nullableの扱いを確認する必要がある。

CarbonはTypeScript上で文字列になる

spatie/laravel-typescript-transformer では、Laravelで使われる CarbonInterfaceCarbon が、TypeScript上では文字列として扱われる。

これはJSON通信の実態に合っている。PHPの日時オブジェクトが、そのままブラウザへ送られるわけではないためである。

TypeScript側で次の二つを混同してはならない。

  • APIから受信した日時文字列
  • 日時ライブラリで変換した日時オブジェクト

API境界の型が string であることは、日時を扱えないという意味ではない。受信後に変換処理を明示すればよい。

ページネーション構造を型へ反映する

Laravelの LengthAwarePaginatorCursorPaginator を利用している場合、変換された型には datalinksmeta などの構造が反映される。

これにより、フロントエンド側でページネーションのレスポンスを配列と誤認しにくくなる。

特に一覧画面では、次の情報が分離されていることが多い。

  • 表示対象のデータ
  • 現在ページ
  • 最終ページ
  • 次ページのリンク
  • 前ページのリンク
  • 件数
  • カーソル情報

型定義にこれらが含まれていれば、ページング処理の引数や状態管理も明確になる。

一方で、ページネーション形式を途中で変更すると、画面側の通信処理にも影響する。ページ番号方式からカーソル方式へ変更する場合、型だけでなく取得処理のアルゴリズムも変わる。

型同期は、仕様変更の影響範囲を隠すための仕組みではない。影響範囲をビルド時に表面化するための仕組みである。

Huskyとlint-stagedで同期処理を強制する

型生成を手動コマンドにすると、実行忘れが発生する。

開発者がLaravel側だけを変更し、生成処理を実行せずにコミットする。別の開発者がそのブランチを取得すると、実装と生成型が一致していない状態になる。

この問題には、Gitフックを利用する。

Huskyとlint-stagedを設定すると、コミット前に特定ファイルの変更を検出し、OpenAPI仕様やTypeScript型の生成処理を自動実行できる。

変更ファイルと生成処理を対応させる

すべてのコミットでAPIスキーマを生成する設計は単純である。しかし、フロントエンドだけの変更でも毎回Laravel側の生成処理を実行すると、処理時間が増える。

そこで、バックエンドの変更をトリガーにする。

対象になりやすいファイルは次の通りである。

  • routes/*.php
  • APIコントローラー
  • Form Request
  • API Resource
  • DTO
  • Enum
  • 認証や権限に関係するクラス
  • OpenAPI生成設定
  • APIレスポンスに関係するサービスクラス

ただし、サービスクラスの変更がレスポンスへ影響する場合は、単純なパス判定だけでは不十分である。変更対象の選定は、プロジェクトの依存関係に合わせて設計する。

コミット前フローの役割

Huskyとlint-stagedを利用した場合、コミット前に次の処理を実行できる。

1. Laravel側のAPI関連ファイルの変更を検出する。

2. OpenAPI仕様を生成する。

3. openapi-typescriptschema.d.ts を生成する。

4. TypeScriptの型チェックを実行する。

5. 生成されたファイルをコミット対象へ追加する。

型チェックには tsc --strict を利用できる。厳格モードを有効にすることで、暗黙的な any やNull許容の曖昧さを検出しやすくなる。

ただし、コミット前処理だけに依存するのは危険である。ローカル環境を経由しないマージ、CI上の生成差分、フック無効化などがあるためである。

CIでも同じ検証を実行する必要がある。

生成ファイルをコミットするか

生成された schema.d.ts をGit管理するかどうかは、チームの運用によって異なる。

コミットする場合は、レビューでAPI仕様と型変更を確認できる。フロントエンドだけを取得する開発者も、すぐに型を利用できる。

コミットしない場合は、ビルド時に生成する必要がある。生成環境にPHP、Composer、Laravel、Node.jsの依存関係が必要になる。フロントエンド専用のビルド環境からバックエンドの生成処理を呼び出す構成は、依存関係が複雑になりやすい。

個人開発や小規模開発では、生成ファイルをコミットする方が運用コストを抑えやすい。重要なのは、生成物が正規ファイルではなく、元のAPI仕様から再生成できる成果物であると明確にすることである。

型生成の実行方法を人の記憶に依存させると、同期の仕組みは運用で破綻する。

Eloquentモデルから型を生成する方法

Eloquentモデルの属性やリレーションをフロントエンドで参照したい場合は、fumeapp/modeltyper のようなツールを利用できる。

導入コマンドは composer require --dev fumeapp/modeltyper である。

この種のツールでは、特別な設定を増やさずに、Laravelモデルの属性やリレーションをもとにTypeScript型を生成できる。countexists など、Eloquentに関連する特殊なメソッドにも対応する構成がある。

モデル中心の型生成は、管理画面や内部向けAPIでは有効である。Laravel側のモデルとフロントエンド側の表示項目が近い場合、導入効果が高い。

しかし、公開APIの契約をモデルだけで表現するのは難しい。

モデル型とAPIレスポンス型は別物である

Eloquentモデルには、次のような情報が含まれる。

  • データベースの属性
  • キャスト後の値
  • 非表示属性
  • リレーション
  • アクセサ
  • 集計結果
  • 遅延ロードされた関係
  • 内部処理用の状態

APIレスポンスには、その一部だけを出すことが多い。

たとえば、モデルには password のハッシュが存在していても、APIレスポンスへ含めてはならない。モデルの属性から自動生成した型にその項目が含まれていると、フロントエンドが利用可能な公開データだと誤認する。

また、リレーションのロード有無によって、JSONの構造が変わることもある。常に返る項目なのか、条件付きで返る項目なのかを、モデル型だけでは判断しにくい。

そのため、次のように用途を分けるのが安全である。

用途推奨する型の起点理由
外部公開APIOpenAPI、API Resource、DTO公開契約を明示できる
管理画面DTOまたはAPIスキーマ画面に必要な項目だけを表現できる
Laravel内部処理Eloquentモデルリレーションや属性を直接扱える
内部ツールモデル型生成導入コストが低く、参照用途に向く
複数クライアント連携OpenAPI言語や実行環境をまたいで利用できる

モデルからの型生成は便利である。ただし、APIの正式な契約を自動的に定義する機能ではない。用途を限定して使う必要がある。

型同期を導入する前に固定すべき設計

ツールを導入する前に、APIの設計ルールを決める必要がある。ここが曖昧だと、生成された型も一貫しない。

Nullableと未定義を区別する

TypeScriptでは、null とプロパティ自体の未定義は異なる。

Laravelのレスポンスで、値が存在しない場合に null を返すのか、項目そのものを省略するのかを決める必要がある。

  • avatar_url: string | null
  • avatar_url?: string

この二つは同じ意味ではない。

null を返す設計なら、フロントエンドは項目の存在を前提にできる。値がない場合だけを処理すればよい。

項目を省略する設計なら、フロントエンドはプロパティの存在そのものを確認する必要がある。

認証状態や権限によって項目が変わる場合、単純なNullableでは足りないこともある。レスポンスのバリアントを分ける設計が必要になる。

APIの命名規則を統一する

PHP側でスネークケース、TypeScript側でキャメルケースを使う構成は可能である。しかし、変換の責務を明確にしなければならない。

次のような変換を複数箇所で行うと、依存関係が増える。

  • Laravelのシリアライザで変換する
  • Axiosのインターセプターで変換する
  • 状態管理層で変換する
  • コンポーネント内で変換する

変換するなら、APIクライアント層か専用のアダプターに集約する。生成型は変換前のJSONに合わせるのか、変換後のオブジェクトに合わせるのかも決める必要がある。

エラー応答を型に含める

成功時のレスポンスだけ型同期しても、通信処理全体の安全性は上がらない。

Laravelのバリデーションエラー、認証エラー、権限エラー、リソース未検出など、失敗時の形式も契約として扱う必要がある。

特にForm Requestのバリデーションエラーは、項目ごとのメッセージを持つことが多い。フロントエンドのフォーム処理では、エラーのキーと入力名が一致している必要がある。

成功レスポンスとエラーレスポンスを同じ型で扱わず、HTTPステータスやAPIクライアントの例外処理と組み合わせて設計する方がよい。

自動生成してもテストは不要にならない

型同期は、APIの形状を確認する仕組みである。値の妥当性や業務ルールまで保証するものではない。

たとえば、型が status: string で一致していても、実際の値が業務上許可されているとは限らない。

  • 期限切れのステータスが返る
  • 権限のないデータが返る
  • 金額の単位が誤っている
  • 日付のタイムゾーンが異なる
  • ページ番号と総件数の計算が合わない

これらは型検査の対象外である。

契約テストと統合テストの境界

OpenAPI仕様から型を生成する場合、仕様と実装が一致していることが前提になる。仕様生成がコントローラーやリソースのコードから行われるなら、実装変更は仕様へ反映される。

しかし、実際のデータベース状態や認証条件によってレスポンスが変化する場合、生成された型だけでは確認できない。

次のテストを組み合わせる必要がある。

  • APIのHTTPステータスを確認するテスト
  • 必須項目の存在を確認するテスト
  • 認証と権限によるレスポンス差分のテスト
  • ページネーションの構造を確認するテスト
  • バリデーションエラーの形式を確認するテスト
  • Enumの値が想定範囲に収まることを確認するテスト

型生成は静的検査であり、APIを実行した結果の検査ではない。両者を混同してはならない。

実際の導入手順を分けて考える

一度にすべてを自動化すると、どこで不整合が起きているのか分からなくなる。導入は段階的に進める方がよい。

第1段階:手動生成で仕様を確認する

最初に、LaravelのAPIルートとレスポンスを整理する。型生成ツールを導入する前に、次の点を確認する。

  • APIのパスとHTTPメソッドが統一されているか
  • Form Requestの入力項目が明確か
  • API Resourceの出力項目が明確か
  • 日時やEnumの表現が決まっているか
  • ページネーション形式が決まっているか
  • エラー応答がエンドポイントごとに異なっていないか

その後、dedoc/scramble でOpenAPI仕様を生成する。生成結果を確認し、実際のレスポンスと一致しているかを比較する。

ここで差異がある場合、TypeScript生成へ進んではならない。誤った仕様を自動生成しても、誤りが高速に拡散するだけである。

第2段階:TypeScript型を生成する

OpenAPI仕様が安定したら、openapi-typescript でTypeScript型を生成する。

npx openapi-typescript --output resources/ts/types/schema.d.ts

生成されたファイルの構造を確認し、フロントエンドのAPIクライアントから参照する。

この段階では、既存の手動型定義をすべて削除する必要はない。まず一つのエンドポイントを選び、生成型へ置き換える。変更前後の差分を確認する。

第3段階:変更検知を追加する

手動生成で運用できることを確認したら、Huskyとlint-stagedを導入する。

バックエンドのAPI関連ファイルが変更された場合だけ、型生成を実行する。生成後に tsc --strict を走らせる。

ここでビルドが失敗するなら、型同期が機能している。失敗を抑制するのではなく、API変更に伴うフロントエンド修正を行う。

第4段階:CIで再生成と差分を検証する

ローカルのGitフックは補助機能である。CIでは、生成処理を必ず再実行する。

推奨する確認内容は次の通りである。

1. Composer依存関係をインストールする。

2. Node.js依存関係をインストールする。

3. LaravelからOpenAPI仕様を生成する。

4. openapi-typescript で型を生成する。

5. 生成ファイルに差分がないか確認する。

6. tsc --strict を実行する。

7. APIのテストを実行する。

生成ファイルをコミットする運用なら、CI上で再生成した結果とリポジトリ上のファイルを比較する。差分が出た場合は、生成漏れと判断して失敗させる。

型の粒度を上げすぎると、依存関係が逆流する

型を自動生成すると、できるだけ詳細な型を使いたくなる。モデルのリレーション、集計値、条件付き属性まで一つの巨大な型へまとめる設計である。

これは短期的には便利である。しかし、型の依存関係が広がる。

一つのモデル変更が、複数の画面やAPIクライアントへ波及する。リレーションを追加しただけで、無関係なコンポーネントの型チェックが失敗することもある。

APIレスポンスは、用途ごとに分ける方が安定する。

  • 一覧表示用の簡略型
  • 詳細表示用の完全型
  • 管理者向けの拡張型
  • 編集画面用の入力型
  • 検索条件用のクエリ型

入力と出力も分離する。ユーザー作成APIの入力型と、作成後に返すユーザー情報の型は同じではない。

入力にはパスワードや確認項目が含まれる。一方、レスポンスには識別子や登録日時が含まれる。これらを一つの型で表すと、不要な項目を送信する事故につながる。

REST APIとGraphQLで型同期の考え方は異なる

LaravelのREST APIでは、OpenAPIを契約として扱う構成が自然である。エンドポイント単位でリクエストとレスポンスを定義できるためである。

一方、GraphQLではスキーマそのものが契約になる。クエリに応じて取得項目が変わるため、RESTのレスポンス型をそのまま適用することはできない。

今回のようなLaravel REST APIとTypeScriptフロントエンドの連携では、まずOpenAPIによるスキーマ駆動開発を検討する方が導入しやすい。

GraphQLを導入する理由が、単に型生成を行いたいというだけなら、システム全体の依存関係を増やす可能性がある。データ取得の柔軟性、N+1問題、認可、キャッシュ戦略まで含めて判断する必要がある。

型同期は技術選定の目的ではない。API契約を一元化するための手段である。

Laravel APIとTypeScriptの型同期で見るべきトレードオフ

自動生成には明確な利点がある。一方で、導入すればすべてが解決するわけではない。

観点自動生成の利点残る課題
型の更新API変更をTypeScriptへ反映できる生成処理を実行する運用が必要
開発速度手動の型定義作業を減らせる初期設定と既存コードの移行が必要
API設計仕様を明示しやすい実装が曖昧だと仕様も曖昧になる
レビュー型の差分で変更を確認できる生成ファイルの差分量が増える
安全性型の不整合をビルドで検出できる実行時の値や業務ルールは別途検証が必要
保守性型定義の二重管理を抑えられるAPI型と内部型の境界設計が必要

導入効果が最も高いのは、LaravelとTypeScriptの両方を同じリポジトリで管理し、API変更が頻繁に発生するプロジェクトである。

逆に、APIがほぼ固定されていてフロントエンドも小規模なら、全面的な自動化は過剰になる可能性がある。まず主要エンドポイントだけを対象にする方が合理的である。

まとめ

Laravel APIとTypeScriptの型同期自動化では、次の設計が基本になる。

  • APIレスポンスの型をフロントエンドで手動管理し続けない
  • 外部契約にはOpenAPIを使い、dedoc/scramble で仕様を生成する
  • openapi-typescript でTypeScriptの型定義を生成する
  • DTOやEnumの共有には spatie/laravel-typescript-transformer を使う
  • Eloquentモデルの型生成は、内部利用や参照用途に限定する
  • Huskyとlint-stagedで生成処理をコミット前に実行する
  • CIでも再生成し、生成物の差分を検証する
  • Nullable、Enum、日時、ページネーション、エラー応答を契約として設計する
  • 生成型とアプリケーション内部の型を必要に応じて分離する
  • 型検査だけでなく、APIテストと実行時検証を組み合わせる

改善の中心は、型定義を書く速度ではない。仕様変更が発生したとき、どの処理を失敗させ、どの差分を開発者へ知らせるかである。

自動生成の導入で、API設計やテストが不要になるわけではない。手動同期による見落としを減らし、変更の影響を早い段階で検出できるようになる。それがLaravel APIとTypeScriptを接続するスキーマ駆動開発の実質的な価値である。

関連記事: LaravelとTypeScriptの型連携:自動生成ツールの選定基準と導入の現実解.

よくある質問

Laravel APIとTypeScriptの型を同期するにはどうすればよいですか?
API仕様を単一の情報源として扱い、LaravelからOpenAPI仕様を生成し、その仕様からTypeScript型を生成します。代表的な構成ではdedoc/scrambleとopenapi-typescriptを利用します。
dedoc/scrambleで何を自動生成できますか?
dedoc/scrambleは、Laravelのルートやコントローラーの戻り値、リクエストクラス、PHPDocなどをもとにOpenAPI仕様ドキュメントを生成できます。生成結果は実装の書き方や型情報に左右されます。
生成したTypeScript型ファイルを直接編集してもよいですか?
生成ファイルは次回の生成で変更内容が消えるため、直接編集しない方がよいです。アプリケーション側から生成されたpathsやcomponentsを参照して利用します。
LaravelのCarbonはTypeScriptでどの型になりますか?
LaravelのCarbonやCarbonInterfaceは、JSON通信の実態に合わせてTypeScript上では通常文字列として扱われます。日時オブジェクトとして使う場合は、API境界の後で明示的に変換します。
EloquentモデルからTypeScript型を生成しても公開APIの契約に使えますか?
Eloquentモデルにはデータベース属性や非表示属性、リレーションなどが含まれるため、モデル型をそのまま公開APIの契約にするのは難しいです。外部公開APIでは、OpenAPI、API Resource、DTOを型の起点にする方が安全です。

参考情報