API rate limiting in Laravel 5.2

Posted on December 19, 2015 | By Matt Stauffer


Warning: This post is over a year old. I don't always update old posts with new information, so some of this information may be out of date.

More and more of my work in Laravel lately has been creating APIs. I have a manual rate limiter class I've been using, but I've had a sense that there's a cleaner way to do it. Unsurprisingly, when Taylor set out to write a rate limiter middleware for Laravel, he did it cleaner and better than I had.

Brief introduction to rate limiting

If you're not familiar with it, rate limiting is a tool—most often used in APIs—that limits the rate at which any individual requester can make requests.

That means, for example, if some bot is hitting a particularly expensive API route a thousand times a minute, your application won't crash, because after the nth try, they will instead get a 429: Too Many Attempts. response back from the server.

Usually a well-written application that implements rate limiting will also pass back three headers that might not be on another application: X-RateLimit-Limit, X-RateLimit-Remaining, and Retry-After (you'll only get Retry-After if you've hit the limit). X-RateLimit-Limit tells you the max number of requests you're allowed to make within this application's time period, X-RateLimit-Remaining tells you how many requests you have left within this current time period, and Retry-After tells you how many seconds to wait until you try again. (Retry-After could also be a date instead of a number of seconds).

Note: Each API chooses the time span it's rate limiting for. GitHub is per hour, Twitter is per 15-minute segment. This Laravel middleware is per minute.

How to use Laravel's rate-limiting middleware

So, on to the new feature in Laravel 5.2. There's a new throttle middleware that you can use. Let's take a look at our API group:

Route::group(['prefix' => 'api'], function () {
    Route::get('people', function () {
        return Person::all();
    });
});

Let's apply a throttle to it. The default throttle limits it to 60 attempts per minute, and disables their access for a single minute if they hit the limit.

Route::group(['prefix' => 'api', 'middleware' => 'throttle'], function () {
    Route::get('people', function () {
        return Person::all();
    });
});

If you make a request to this api/people route, you'll now see the following lines in the response headers:

HTTP/1.1 200 OK
... other headers here ...
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59

Remember, this response means:

A) This request succeeded (the status is 200)
B) You can try this route 60 times per minute
C) You have 59 requests left for this minute

What response would we get if we went over the rate limit?

HTTP/1.1 429 Too Many Requests
... other headers here ...
Retry-After: 60
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0

And the actual content of the response would be a string: "Too Many Attempts."

What if we tried again after 30 seconds?

HTTP/1.1 429 Too Many Requests
... other headers here ...
Retry-After: 30
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0

Same response, except the Retry-After timer that's telling us how long to wait has ticked down by 30 seconds.

Customizing the throttle middleware

Let's do a bit of customization. We want to limit it to 5 attempts per minute.

Route::group(['prefix' => 'api', 'middleware' => 'throttle:5'], function () {
    Route::get('people', function () {
        return Person::all();
    });
});

And if we want to change it so that, if someone hits the limit, they can't try again for another 10 minutes?

Route::group(['prefix' => 'api', 'middleware' => 'throttle:5,10'], function () {
    Route::get('people', function () {
        return Person::all();
    });
});

That's all there is to it!

You can see the code that's supporting this here: ThrottlesRequests.php


Comments? I'm @stauffermatt on Twitter


Tags: laravel  •  laravel 5.2  •  API