LaravelでAPI用のJSONエラーレスポンスを整形する方法!初心者向け完全ガイド
生徒
「LaravelでAPIを作っているのですが、エラーが起きたときのレスポンスをJSON形式で返すにはどうすればいいですか?」
先生
「APIのエラー処理は、Webページとは少し違う考え方が必要です。Handler.phpを使ってJSONで整形されたエラーレスポンスを返す方法を一緒に学びましょう。」
生徒
「JSONってよく聞くんですが、そもそも何ですか?」
先生
「JSONはデータをやり取りするときに使う書き方のルールです。基本から順番に説明していきますよ!」
1. APIとJSONエラー処理の基本を理解しよう
まずAPIという言葉から説明します。APIとは「Application Programming Interface」の略で、異なるシステム同士がデータをやり取りするための窓口のようなものです。たとえばスマートフォンのアプリが天気情報を表示するとき、天気データを提供するサーバーにAPIを通じてデータを取りに行っています。
そしてJSONとは「JavaScript Object Notation」の略で、データを整理して書くためのフォーマット(書き方のルール)です。人間にも読みやすく、機械にも処理しやすい形式として、現在のWeb開発では非常に広く使われています。たとえばユーザー情報をJSONで表すと、{"name": "田中太郎", "age": 25}のような形になります。
Laravelを使ってAPIを作る場合、エラーが発生したときにHTMLのエラーページを返してしまうと、アプリ側でそのデータを受け取って処理することができません。そのためエラーのときもJSON形式でレスポンス(返答)を返す必要があります。この仕組みを正しく整えることが、使いやすいAPIを作るための重要なポイントです。
2. LaravelがAPIリクエストを判定する仕組み
Laravelには、届いたリクエスト(アクセス)がAPIからのものかどうかを自動で判定する仕組みが備わっています。リクエストのURLが/api/から始まっている場合や、リクエストヘッダーにAccept: application/jsonが含まれている場合、LaravelはそのリクエストをAPI向けと判断してJSONでレスポンスを返そうとします。
リクエストヘッダーとは、アクセスするときに一緒に送られる付属情報のようなものです。手紙でいえば封筒に書かれた「宛先」や「差出人」にあたる部分です。Accept: application/jsonは「JSON形式のデータを受け取りたい」という意味の指示です。
この判定はLaravelが自動でやってくれるため、開発者は意識せずにAPIとWebページの両方に対応したエラー処理を書くことができます。ただし、より細かいカスタマイズをしたい場合はapp/Exceptions/Handler.phpを編集する必要があります。
3. Handler.phpでJSONエラーレスポンスを返す基本的な書き方
それでは実際にapp/Exceptions/Handler.phpを使って、APIにエラーが起きたときにJSON形式でレスポンスを返す方法を見ていきましょう。
register()メソッドの中にrenderable()を使って処理を書いていきます。以下は、データが見つからなかったときに発生するModelNotFoundExceptionをJSONで返す例です。
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class Handler extends ExceptionHandler
{
public function register(): void
{
$this->renderable(function (ModelNotFoundException $e, $request) {
// リクエストがAPIからかどうかを確認する
if ($request->expectsJson()) {
return response()->json([
'status' => 'error',
'message' => 'データが見つかりませんでした。',
], 404);
}
});
}
}
$request->expectsJson()は、アクセスしてきた相手がJSONを期待しているかどうかを調べるメソッドです。これがtrueのときだけJSONで返すようにすることで、通常のWebページのアクセスには影響を与えずにAPIだけに対応した処理を書けます。
このコードを実行してAPIにアクセスすると、以下のようなJSONが返ってきます。
{
"status": "error",
"message": "データが見つかりませんでした。"
}
4. バリデーションエラーをJSONで返す方法
APIでよく扱うエラーのひとつがバリデーションエラーです。バリデーションとは「入力チェック」のことで、たとえばメールアドレスの形式が正しくない、必須項目が空欄のままになっているなど、受け取ったデータが正しい形かどうかを確認する処理です。
Laravelのバリデーションが失敗するとValidationExceptionという例外が発生します。通常のWebページではフォームの画面にエラーメッセージが表示されますが、APIの場合はJSONで返す必要があります。
以下のようにHandler.phpに記述することで、バリデーションエラーの内容をJSON形式にして返すことができます。
<?php
use Illuminate\Validation\ValidationException;
public function register(): void
{
$this->renderable(function (ValidationException $e, $request) {
if ($request->expectsJson()) {
return response()->json([
'status' => 'error',
'message' => '入力内容に誤りがあります。',
'errors' => $e->errors(), // 各項目のエラー内容
], 422);
}
});
}
$e->errors()はバリデーションで失敗した項目と理由を配列として返すメソッドです。ステータスコードの422は「入力内容が処理できない形式だった」ことを示す番号です。このようにすることで、どの項目でどんなエラーが起きたかをAPIの利用者に正確に伝えることができます。
5. 認証エラーをJSONで返す方法
APIを使ったシステムでは、ログインしていない状態でアクセスしてきたユーザーに対して「認証が必要です」というエラーを返す場面が多くあります。Laravelでは認証が必要なページに未認証のままアクセスするとAuthenticationExceptionが発生します。
認証とは、「あなたは本当に誰ですか?」を確認することです。ウェブサービスでいえばログイン状態のチェックにあたります。
以下のようにHandler.phpへ書くことで、未認証のAPIアクセスに対してJSONエラーを返せます。
<?php
use Illuminate\Auth\AuthenticationException;
public function register(): void
{
$this->renderable(function (AuthenticationException $e, $request) {
if ($request->expectsJson()) {
return response()->json([
'status' => 'error',
'message' => '認証が必要です。ログインしてください。',
], 401);
}
});
}
ステータスコード401は「認証が必要」を意味します。APIの利用者はこのコードを受け取ることで「ログインしていないのでアクセスできなかった」と判断できます。このようにステータスコードとメッセージをセットで返すことが、わかりやすいAPIエラーレスポンスを設計するうえで大切なポイントです。
6. すべての例外をJSONで一括対応する方法
個別の例外ごとに処理を書く方法もありますが、想定外の例外が起きたときにも必ずJSON形式で返したいという場合は、すべての例外をまとめて処理する方法が便利です。
LaravelではThrowableという型を使うと、どんな種類の例外でもキャッチすることができます。Throwableとは、PHPにおいてすべての例外やエラーの親にあたる型のことです。
<?php
use Throwable;
public function register(): void
{
$this->renderable(function (Throwable $e, $request) {
if ($request->expectsJson()) {
// HTTPステータスコードを取得する(取得できない場合は500)
$status = method_exists($e, 'getStatusCode')
? $e->getStatusCode()
: 500;
return response()->json([
'status' => 'error',
'message' => $e->getMessage() ?: 'サーバーエラーが発生しました。',
], $status);
}
});
}
method_exists()は、そのオブジェクトに指定したメソッドが存在するかどうかを調べる関数です。getStatusCode()メソッドが存在する例外ならそのコードを使い、存在しない場合は500(サーバー内部エラー)をセットしています。このようにしておくと、どんな予期せぬエラーが起きてもJSONで統一されたレスポンスを返せるので安心です。
7. JSONエラーレスポンスの設計で意識すること
APIのエラーレスポンスを設計するときは、返すデータの形を統一しておくことがとても重要です。形がバラバラだと、APIを利用する側(スマホアプリや他のシステム)が受け取ったデータを処理するのが難しくなります。
よく使われるJSONエラーレスポンスの形式としては、status(成功か失敗か)、message(人間向けのメッセージ)、errors(詳細なエラー情報)、code(エラーの種類を示す独自コード)などをセットにするパターンがあります。
また、HTTPステータスコードも適切なものを選ぶことが大切です。よく使うものをまとめると次のとおりです。400番台はクライアント(アクセスしてきた側)のミス、500番台はサーバー側の問題を示します。400は「リクエストの形式が正しくない」、401は「認証が必要」、403は「アクセス権限がない」、404は「リソースが見つからない」、422は「バリデーションエラー」、500は「サーバー内部エラー」をそれぞれ意味します。
このようにエラーの種類とステータスコードを正しく対応させることで、APIの利用者がエラーの原因を素早く把握できる、使いやすいAPIが完成します。LaravelのHandler.phpを活用することでこれらの設定をひとつのファイルにまとめて管理できるため、メンテナンスもしやすくなります。