Skip to content
Advertisement

Laravel Spark – Can I limit plan API calls per month and API calls per minute/hour?

I want to use Laravel Spark to create different licenses for an API. I want to limit the API calls per month and also per hour/minute. For example the Basic License will allow 10 000 monthly api calls but I also want to limit the API calls per hour/minute for the same account. Can this be done with laravel spark?

There is: throttle and rate_limit, but can I limit total number of API calls per month and also per hour/minute? For example: 10 000 total api calls per month and max 60 api calls per hour/minute?

According to the docs, I can limit access per minute/hour: https://laravel.com/docs/6.x/routing#rate-limiting

Something like this combines throttle with rate limit:

Route::middleware('auth:api', 'throttle:10|rate_limit,1')->group(function () {     
Route::get('/user', function () { // }); 
});

But my main question is how do I combine rate limiting per minute/hour with the monthly limit?

Advertisement

Answer

From the same documentation page:

You may also combine this functionality with dynamic rate limits. For example, if your User model contains a rate_limit attribute, you may pass the name of the attribute to the throttle middleware so that it is used to calculate the maximum request count for authenticated users

So, given the above, you could add an accessor on your User model that gets the rate limit value based on their current subscription plan:

class User extends Authenticatable
{
    public function getRateLimitAttribute()
    {
        // Get license if it's a model

        if ($license === 'basic') {
            return 10000;
        }

        // Return some default here
    }
}

You can then use your dynamic rate limit value like this:

Route::middleware('auth:api', 'throttle:rate_limit,1')->group(function () {
    // Rate-limited routes
});

As for the monthly limit, you’ll need to keep a counter somewhere and check that on each request. You could log each API request in another middleware:

class LogRequest
{
    public function handle($request, Closure $next)
    {
        return $next($request);
    }

    public function terminate($request, $response)
    {
        LogRequestJob::dispatch($request);
    }
}
class LogRequestJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    public function handle()
    {
        // Insert row for user to log request
        RequestLog::create([
            'user_id' => $this->request->user()->getKey(),
            'path' => $this->request->path(),
            // Store any other request data you're interested in
        ]);
    }
}

You’ll then need to check the count before handling a request, again using middleware:

use SymfonyComponentHttpKernelExceptionTooManyRequestsHttpException;

class CheckRequestInLimit
{
    public function handle($request, Closure $next)
    {
        $year = Carbon::now()->year;
        $month = Carbon::now()->month;
        $count = RequestLog::user($request->user())->year($year)->month($month)->count();
        $limit = $request->user()->monthly_limit; // another accessor

        if ($count < $limit) {
            return $next($request);
        }

        // Count is equal to (or greater than) limit; throw exception
        $retryAfter = Carbon::today()->addMonth()->startOfMonth();
        $message = 'You have exceeded your monthly limit';

        throw new TooManyRequestsHttpException($retryAfter, $message);
    }
}

Hopefully this gives you food for thought!

Edit on November 1, 2021: This answer was written against an old version of Laravel. Laravel later introduced rate limiting that would provide a more succinct solution to the problem above:

RateLimiter::for('api', function (Request $request) {
    return [
        Limit::perHour(60)->by($request->user()->getKey()),
        Limit::perDay(10000, 30)->by($request->user()->getKey()),
    ];
});
User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement