同じ“Middleware”でも別物?RailsとLaravelで見えた5つの違い

RailsからLaravelを眺める

※本記事は「RailsからLaravelを眺める」シリーズの第11回です。Rails出身の私がLaravelを触りながら、Railsと比較して違いを整理していく連載になります。


はじめに

Rails出身のエンジニアがLaravelを触ると、最初に違和感を覚えるのは「同じ用語なのに指しているものが全然違う」という場面です。
その代表例が「ミドルウェア」。

Railsにおけるミドルウェアは「Rack Middleware」であり、RubyのWebフレームワーク共通の基盤です。
一方、Laravelのミドルウェアはフレームワークに密接に統合された仕組みで、ルート単位での認証やCSRF保護、後処理までを担います。

この記事ではまず「結論=抽象化レベルが違う」という点を提示し、その上で特徴の比較、コード例、そして5つの主要な違いを整理していきます。


結論から言うと:抽象化レベルが違う

最初に核心を言ってしまうと、LaravelのミドルウェアとRackは抽象化のレベルが根本的に異なります。

  • Rack: Rubyのウェブサーバーとフレームワークをつなぐインターフェース仕様
  • Laravelミドルウェア: Laravelフレームワーク内部のHTTPリクエスト処理機構

Rackは「RailsでもSinatraでもHanamiでも使える」共通基盤。
LaravelミドルウェアはLaravel専用で、フレームワークの一部として強く統合されています。


特徴の比較

Rackの特徴

  • env という生のハッシュでHTTP情報を扱う
  • 戻り値は [status, headers, body] の配列
  • フレームワークに依存しない汎用的な設計

Laravelミドルウェアの特徴

  • Request / Response というリッチなオブジェクトを扱う
  • Closure $next でパイプライン的に処理を繋ぐ
  • Laravel専用(他のPHPフレームワークでは利用できない)

コード例(Rack)

# app/middleware/hello_middleware.rb
class HelloMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    status, headers, body = @app.call(env)
    headers['X-Hello'] = 'World'
    [status, headers, body]
  end
end

# config/application.rb
Rails.application.config.middleware.use HelloMiddleware

コード例(Laravel)

// app/Http/Middleware/CheckToken.php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class CheckToken
{
    public function handle(Request $request, Closure $next)
    {
        if ($request->header('X-TOKEN') !== 'secret') {
            return response('Unauthorized', 401);
        }
        return $next($request);
    }
}

登録と適用例:

// app/Http/Kernel.php
protected $routeMiddleware = [
    'check.token' => \App\Http\Middleware\CheckToken::class,
];

// routes/web.php
Route::get('/admin', fn () => 'Admin Page')->middleware('check.token');

Terminable Middleware

Laravelには「レスポンス送信後に処理を続行できる」Terminable Middlewareという仕組みがあります。

// app/Http/Middleware/LogAfterResponse.php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class LogAfterResponse
{
    public function handle(Request $request, Closure $next): Response
    {
        return $next($request);
    }

    public function terminate(Request $request, Response $response): void
    {
        \Log::info('Request finished at: ' . now());
    }
}

RailsのRack Middlewareには標準で「レスポンス後フック」が存在せず、この点はLaravel独自の強みです。


5つの主な違い

1. インターフェースの違い

  • Rackは call(env) を受け取り、[status, headers, body] を返すシンプルな仕様。
  • Laravelは Request/Response のオブジェクトを使い、Closure $next でパイプラインを構築する。

2. 適用範囲の柔軟性

  • Rackは基本的に全体に適用される。
  • Laravelは全体・グループ・ルート単位で柔軟に適用できる。

3. Railsでの実際の使い分け

Railsではミドルウェア的な処理は二層構造になっています。

  • Rackミドルウェア(低レベル)
    • セッション管理
    • 静的ファイル配信
    • ロギング
    • セキュリティヘッダー
  • コントローラのbefore_action(アプリレベル)
    • 認証チェック
    • 権限確認
    • パラメータ検証

Laravelではこれら両方を「Middleware」に寄せられるのが特徴です。

4. 処理の流れの違い

  • Rackは「積み上げた処理を順番に実行 → レスポンスを返す」ストレートな流れ。
  • Laravelは「パイプライン」として構築され、$next() で処理が前後にまたがる。

5. 終了可能性(Terminable Middleware)

  • Rackにはレスポンス後フックがない。
  • LaravelはTerminable Middlewareによって、レスポンス後の処理を公式にサポート。

Rails脳から見た感想

Rails脳でLaravelのMiddlewareを見ると、「Rackに書くのは違うな」と思っていた処理がMiddlewareに自然に収まるのが印象的です。
認証や認可といった処理をbefore_actionに寄せがちなRailsに対して、Laravelはミドルウェアとして統合的に扱える。
結果として、コントローラの責務がスッキリしやすい設計になっています。

特にTerminable MiddlewareはRailsにない概念で、「レスポンスを返した後に何かする」処理をフレームワーク標準でサポートしているのは新鮮でした。
開発者体験を意識するLaravelらしい仕組みだと感じます。


まとめ

RailsとLaravelのミドルウェアは、同じ「リクエスト処理にフックを挟む仕組み」でも、その立ち位置が大きく異なります。

  • RackはWebサーバーとフレームワークをつなぐ共通仕様。低レベルな処理を担当する。
  • LaravelのMiddlewareはLaravel専用。ルート単位で柔軟に適用でき、Terminable Middlewareによるレスポンス後処理まで担う。

言い換えれば、Rackは「抽象化された共通基盤」、Laravel Middlewareは「フレームワークに密着した便利ツール」。

Rails出身者にとっては「Rack = インフラ層、Laravel Middleware = アプリ層+後処理」という理解が最もしっくりきます。
フレームワーク哲学の違いを最も端的に感じられるテーマの一つだと思います。

コメント

タイトルとURLをコピーしました