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

# なにがしたい？

**APIなのにHTMLエラーが出る**
![image.png](https://qiita-image-store.s3.amazonaws.com/0/129854/0b94af4f-ee48-7a75-3a7a-415f5e3d5fe8.png)

**APIは、ブラウザで開いてもJSONで返して欲しい**
![image.png](https://qiita-image-store.s3.amazonaws.com/0/129854/9b9f1432-a1de-8006-e0ed-c3ef17656355.png)

WEBとしても、APIとしても、さらっと動くLaravelさん。コントローラでは、WEBはHTMLで、APIはJSONで返すように、コードを分けていることが多いと思いますが、例えばエラーが起きたらどうしていますか？ JSONなのに、HTMLのエラー画面が返ってきたりしていませんか？

実は、Laravelは、特に何もしなくてもうまいことやってくれます。
リクエストされているのがHTMLなのか、JSONなのかを自動的に判断して、WEBならHTML、APIならJSONでエラーを返します。

でも、例えば、APIのエンドポイントをWEBブラウザから試験的に開こうとすると、Laravelは **WEBモードだと判断して、エラーハンドリングの挙動が変わります**。

そうじゃなくて、APIは何が起ころうと、JSONで返して欲しい。
それをさらっと実現する方法です。

APIを作る際にはデフォルトで入れておいたほうが良いかなーと思います。


# 結論

```php:app/Http/Middleware/RequireJson.php
<?php namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Response;

/**
 * 強制的にJSONをリクエスト
 * リクエストヘッダに Accept: application/json を付与する
 * 
 * @link https://stackoverflow.com/questions/46035072/enforce-json-middleware
 */
class RequireJson
{
    public function handle($request, Closure $next)
    {
        // リクエストヘッダに Accept:application/json を加える
        $request->headers->set('Accept','application/json');

        $response = $next($request);

        return $response;
    }
}
```

APIの呼び出しには常にこれを適用します。

```php:app/Http/Kernel.php
        //....
        'api' => [
            'throttle:60,1',
            'bindings',
            \App\Http\Middleware\RequireJson::class, // 追加
        ],
        //....
```

# 結論２

ミドルウェアじゃいやだ！という人は、Routeに書く方法があります。

```php:app/Providers/RouteServiceProvider.php
    //....
    protected function mapApiRoutes()
    {
        Route::prefix('api')
             ->middleware('api')
             ->header('Accept', 'application/json') // 追加
             ->namespace($this->namespace)
             ->group(base_path('routes/api.php'));
    }
    //....
```

Routeのほうがカンタンですね。ただ、Routeは `route/api.php` だとグループを追加する必要があったり、となると直感に反して `RouteServiceProvider.php` に書く必要があったり、と不確定要素が多いのでどちらかというとミドルウェアをおすすめします。このあたりは好みの問題かもしれません。


# 解説

仕組みはコチラ。

```php:Illuminate\Http\Request.php

    /**
     * Jsonをリクエストされているか？
     */
    public function expectsJson()
    {
        return ($this->ajax() && ! $this->pjax()) || $this->wantsJson();
    }

    public function wantsJson()
    {
        $acceptable = $this->getAcceptableContentTypes();

        return isset($acceptable[0]) && Str::contains($acceptable[0], ['/json', '+json']);
    }

    public function ajax()
    {
        return $this->isXmlHttpRequest();
    }

    public function pjax()
    {
        return $this->headers->get('X-PJAX') == true;
    }

    // これはベースクラス Symphony のコード
    public function isXmlHttpRequest()
    {
        return 'XMLHttpRequest' == $this->headers->get('X-Requested-With');
    }
```

つまり、XMLHttpRequest であるか、リクエストヘッダに、`Accept:...../json' とか `Accept:.....+json' と書いてあるかどうか、といったところです。

ふつうのAjaxリクエストは XMLHttpRequest がセットされるので、自動的にすべてJSONで返してくれますが、ブラウザからのリクエストにはこれが含まれません。そこで、`Accept:...../json` というヘッダを書き込むことで、常にJSONとして可動させることができます。

# 感想

APIを作ろう！というと、まずエラーの返し方を考えると思いますが、そこもフレームワークの標準にどかっと乗っかると、余計なことを考えることもなく、余計な手を動かすこともないので早いです。ちょっとイメージと違う動きをするぞ？と思っても、無理に各個撃破するんじゃなくて、めんどくさくてもフレームワークの仕組みをたどると、あっさりとした解決策が見つかったりして、またLaravelと仲良くなれた気がしました。

できるだけ、フレームワークには依存したいお年頃です。