公開日:2021.09.02

【Laravel 8.x】API / Ajax のエラーレスポンスをJSONで返す ※カテゴリ確認

テクログ

どうも!先日恐竜科学博へ行き、少年の心を取り戻したわいです!

いや~、良いですね。恐竜は。図鑑の隅々まで(※1)読み込んでいた幼少期を思い出しました。

ずっと忘れていた恐竜への気持ちを蘇らせてくれたのは、バイリンガルニュースの恐竜くんの回でした。

恐竜科学博もバイリンガルニュースも両方おもしろかったです。

(※1)・・・ティラノサウルスのページのみ

恐竜は化石というかたちで現代人に当時の情報を提供しますが、WebAPIでは一般的にJSONという形式でクライアントに情報を提供します。

しかし、LaravelのデフォルトではエラーレスポンスをJSONで返してくれません。

このままだと、クライアントがデバッグするのは、化石なしで恐竜の研究をするくらい難しいです。

そういうことで、今回はLaravelでAPI / Ajax のエラーレスポンスをJSONで返す方法について書きたいと思います。(※2)

(※2)・・・無理やり話をつなげた

一つ目の方法は、

$request->headers->set('Accept','application/json');

を追加して、APIのいかなるリクエストに対しても Accept:application/json を加えるという方法です。

こうすることで、$request->expectsJson() が true になり、

Illuminate\Foundation\Exceptions\Handler 内でエラーレスポンスをJSON形式で返してくれるようになります。

しかし、この方法の場合、abort(404) をしたときなどに投げられる HttpException や内部エラーを返すときに、

ファイル名などの膨大な情報がJSONに含まれてしまい、とてもキレイなエラーレスポンスとは言えません。

(そもそも、ファイル名などはクライアントに教えたくないです)

参考:Laravel APIで常にJSONをリクエストするミドルウェア

というわけで、下記方法がオススメです。

Illuminate\Foundation\Exceptions\Handler を継承している app\Exceptions\Handler.php(※3)を実装します。

(※3)・・・このクラスは、Laravelのバージョンによって中身が微妙に違います。本記事はLaravel 8.x系です。

<?php

namespace App\Exceptions;

use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpFoundation\Response as HttpResponse;
use Throwable;

class Handler extends ExceptionHandler
{
	/**
	 * A list of the exception types that are not reported.
	 *
	 * @var array
	 */
	protected $dontReport = [
		//
	];

	/**
	 * A list of the inputs that are never flashed for validation exceptions.
	 *
	 * @var array
	 */
	protected $dontFlash = [
		'current_password',
		'password',
		'password_confirmation',
	];

	/**
	 * Register the exception handling callbacks for the application.
	 *
	 * @return void
	 */
	public function register()
	{
		$this->reportable(function (Throwable $e) {
			//
		});

		$this->renderable(function (Throwable $e, Request $request) {

			if ($request->is('api/*') || $request->ajax())
			{
				Log::error('[API Error] '.$request->method().': '.$request->fullUrl());

				if ($this->isHttpException($e))
				{
					$message = $e->getMessage() ?: HttpResponse::$statusTexts[$e->getStatusCode()];
					Log::error($message);

					return response()->json([
						'message' => $message
					], $e->getStatusCode());
				}
				elseif ($e instanceof ValidationException)
				{
					Log::error($e->errors());

					return $this->invalidJson($request, $e);
				}
				else
				{
					return response()->json([
						'message' => 'Internal Server Error'
					], 500);
				}
			}
		});
	}
}

投げられたすべての例外がこのクラスを通ります。

やっていることは下記です。

・API / AJax リクエストの時だけ、例外処理のレンダーを変える

・abort(401), abort(404, ‘Record not found.’) などの HttpException のエラーメッセージをJSONで返す

・$request->validated()実行時のバリデーションエラーをJSONで返す(ここは、Illuminate\Foundation\Exceptions\Handlerの処理をそのまま使う)

・内部エラーの場合、’Internal Server Error’というエラーメッセージをJSONで返す

・HttpException, ValidationException はデフォルトでログを吐かないようになっているので、API / Ajaxのときはログを吐くようにする

これで、無事クライアントに理解しやすいエラーレスポンスを返すことができるようになりました。

こんな感じで、恐竜も意図的に化石になる部分を決めていたらおもしろいですね。(※4)

(※4)・・・おもしろくない

以上、わいでした。

健闘を祈る!!

この記事を書いた人

わい

入社年2019年

出身地大阪

業務内容システム開発

特技または趣味芸人のラジオを聴く、ダイビング

わいの記事一覧へ

テクログに関する記事一覧