Laravel API uses Redis + Lua

This is a continuation of Laravel: rate limiter for different API endpoints

Centralizing your rate limits in a config file keeps your Laravel app clean and easy to maintain.


1. Create a Config File

Make a new file: config/rate_limits.php

php

<?php

return [

    // Key = endpoint name
    // Value = [limit, window_in_seconds]

    'login' => [
        'limit' => 3,
        'window' => 60,   // 3 requests per minute
    ],

    'profile' => [
        'limit' => 20,
        'window' => 60,   // 20 requests per minute
    ],

    'default' => [
        'limit' => 100,
        'window' => 60,   // 100 requests per minute
    ],

];

2. Update the Service

Modify app/Services/RateLimiter.php to load limits from the config:

php

<?php

namespace App\Services;

use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Config;

class RateLimiter
{
    public static function attempt(string $userId, string $endpoint): bool
    {
        $script = file_get_contents(app_path('Services/Redis/rate_limit.lua'));

        // Load limit & window from config
        $settings = Config::get("rate_limits.$endpoint") ?? Config::get("rate_limits.default");

        $limit  = $settings['limit'];
        $window = $settings['window'];

        // Key = endpoint + user
        $key = "rate_limit:{$endpoint}:{$userId}";

        $result = Redis::eval($script, 1, $key, $limit, $window);

        return $result === 1;
    }
}

3. Simplify Controllers

Now controllers don’t need hardcoded values — just pass the endpoint name:

php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Services\RateLimiter;

class ApiController extends Controller
{
    public function login(Request $request)
    {
        $userId = $request->ip(); // limit login by IP

        if (!RateLimiter::attempt($userId, 'login')) {
            return response()->json(['error' => 'Too Many Login Attempts'], 429);
        }

        return response()->json(['message' => 'Login successful']);
    }

    public function profile(Request $request)
    {
        $userId = $request->user()->id ?? $request->ip();

        if (!RateLimiter::attempt($userId, 'profile')) {
            return response()->json(['error' => 'Too Many Profile Requests'], 429);
        }

        return response()->json(['profile' => ['name' => 'ice', 'age' => 26]]);
    }
}

Benefits:

  • Single place for limits → change behavior without touching controllers.
  • Default rule applies automatically if endpoint not configured.
  • Easy to add new endpoints → just update config/rate_limits.php.
  • Can be tuned per environment (production vs. local).

You want to make this middleware-based, so you can attach rate limits to routes directly (Route::middleware('throttle:login'))? That way, you won’t even touch controllers. See Laravel: a middleware-based Rate limiter.

By ice

Leave a Reply

Your email address will not be published. Required fields are marked *