🙄

LaravelでURLに api/ を付けずにAPIルーティングを実現

2024/12/25に公開

結論

  • routes に専用のルーティング定義ファイルを作成する
  • RouteServiceProvider に新設したルーティング定義ファイルの挙動を定義する

大体これで何とかなるみたいです。

発端

知らないというのは恐ろしいことで。

他サービスからコールさせてシステム連携するためのAPI群を構築するにあたり、設計時点でLaravelの仕様をロクに知らないまま、すべてのエンドポイントの定義を取り決めてしまいました。
当然、そのURLの頭に api/ は付いていません。

APIなので routes/api.php にルーティングを書けばいいだろうと思ったものの、ここに書いたルーティングには必ずURLの頭に api/ が付くと知り、急遽 routes/web.php に記述。
しかし、これだとXSRF(CSRF)対策などの理由でCookie周りのHTTPヘッダが勝手に付与されてしまうので、コール元の他サービスの実装によってはこれが悪さをしかねない状態になっており、実際に悪さをしてました。

…やっちまった…。

という事で routes/api.php に既存ルーティングを移設したいんだけど、URLは据え置き】 という要件が発生しました。

調査

※実際にはもう少し迷走してたんですが端折ります

RouteServiceProvider を確認する

どうやらルーティング定義を読み込むのに使っているのは RouteServiceProvider というクラスらしいので、app/Providers/RouteServiceProvider.php を確認。

class RouteServiceProvider extends ServiceProvider
{
    // 省略

    public function boot(): void
    {
        RateLimiter::for('api', function (Request $request) {
            return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
        });

        $this->routes(function () {
            Route::middleware('api')
                ->prefix('api')
                ->group(base_path('routes/api.php'));

            Route::middleware('web')
                ->group(base_path('routes/web.php'));
        });
    }
}

なるほど、routes/api.php に書いてあるルーティング定義に対して prefix('api') でURLに api/ が付き、middleware('api') の処理が行われる。
ということは、API用の定義をコピーして、新しいルーティング定義ファイルを設けたらいいんじゃないか。

新しいルーティング定義ファイルを追加

新しく routes/newapi.php を設け、今回問題となっている定義を routes/web.php からこちらに移動しました。

RouteServiceProvider に定義を追加

app/Providers/RouteServiceProvider.php に以下を追加しました。

Route::middleware('api')
    ->group(base_path('routes/newapi.php'));

動作確認

以下の通りキャッシュクリアを実施。

# php artisan cache:clear
# php artisan route:clear

試しにPostmanでAPIを叩いてみたところ、HTTPヘッダからセッション周りのものが消えました。

対応による弊害

これで、api/を接頭句にしないAPIが実現できるようになりましたが、ひとつ弊害が。
app/Providers/RouteServiceProvider.php の冒頭にこういう処理があります。

RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});

これ、「1分間に同一ユーザー or 同一IPからアクセスを受けたら429エラーにする」という仕組みで、おそらくmiddleware('api')の処理で行っており、routes/web.php に記述している間はそういった制約はありません。

ここについての話はまた別の記事で。

Discussion