Laravel: There is a RateLimiter and you didn’t know

You have a rate limit, and you have a rate limit, and you have a rate limit!

Italo Baeza Cabrera
4 min readJun 10, 2019

Throttling something in Laravel is not rocket science. You usually want to restrict an action based on a number of attempts inside a time window, like when the user makes a Request. The throttle middleware does that for you, and the default LoginController automatically throttle logins.

This works for throttling requests or logins, but what about a custom action? Do you need to create your own Throttler or download a package that takes care of it? Fortunately, there is a throttler already inside Laravel, called RateLimiter. It’s located in Illuminate\Cache\RateLimiter, but the thing it’s not documented anywhere. Since it’s usage is very limited, it’s understandable.

Why is there? Because this class is used for aforementioned middleware and login throttler, and uses the application Cache to save the limit data. If it’s there, guess what: we can also use it, like we did with the Markdown parser.

How it works?

The class is relatively simple, in part thanks to the InteractsWithTime trait that handles… well, interacting with time.

The RateLimiter uses a cache store to put two things: a key that holds the number attempts, and another key which holds the window of time to register new attempts. The latter just appends :timer to the original key.

How we can call it? This class is not registered in the Service Container, but you can instance it using the app helper anywhere in your code.

$limiter = app(RateLimiter::class);

If in your code you plan to use the RateLimiter constantly you can conveniently type-hint the class, like in Controller handlers.

/**
* Do something tied to rate limiting
*
* @param \Illuminate\Cache\RateLimiter $limiter
* @return \Illuminate\Http\Response
* @throws \Illuminate\Validation\ValidationException
*/
public function notTooManyTimes(RateLimiter $limiter)
{
if ($limiter->tooManyAttempts('my_action')) {
// .. do something
}
// ...
}

It receives the default Cache Store of your application, which in fresh installations is the file one. Since you have control in the instancing, you can swap your Cache for another:

$limiter = app(RateLimiter::class, [
'cache' => Cache::store('memcache')
]);

In the case where you want to use a custom store everywhere, you can tell the Service Container to use another Cache when the RateLimiter is resolved anywhere in your AppServiceProvider:

/**
* Bootstrap any application services.
*
*
@return void
*/

public function boot()
{
// ...

$this->app
->when(\Illuminate\Cache\RateLimiter::class)
->needs(\Illuminate\Contracts\Cache\Repository::class)
->give(function ($app) {
return $app->make('cache')->store('memcached');
});

This will affect all instances of the RateLimiter. For more granular control, you could extend this class and register it in the Service Container with your own Cache Store to pick, or even use tagging. The sky's the limit, boys and girls!

Basic usage

The basic flow of rate-limiting something is this:

  1. You check if the action has attempts left with tooManyAttempts(). If not, do something, like throwing your favorite Exception like Validation Exception, which is something the ThrottlesLogins trait does.
  2. You use hit() each time you want subtract 1 to the attempts left, with the name to identify what rate limiter and the decay seconds of it.
  3. Optionally, reset the counter using resetAttempts(), or additionally reset the timer using clear().

There are other methods like:

  • attempts(): shows you how many attempts the user has been done.
  • retriesLeft(): The number of retries left.
  • available(): How much time must pass to retry.

These are useful to tell the user it has tried X times, has X retries left, or to tell the user he can proceed after X time.

Can we stop reckless Requests?

The RateLimiter is very basic. Most of the time you can stop the user using the throttle middleware, leaving our controller logic clean. But sometimes, we need to use the Request to effectively throttle an action, which you can’t do with the throttle middleware alone.

For example, we can use this to handle how many times a user can send a Podcast Analytics to a reportable email. Since this calculation is costly for the application, we can add a rate limiter to avoid queueing the SendPodcastAnalyticsJob in the next hour or so. I know this example as a better solution, but is just here to prove a point:

In addition to this, we could stop the propagation of the form submission using JavaScript, just to be sure the user don’t click more than one time by accident before seeing the result.

You are free to use the RateLimiter in whatever you want, but it’s often more useful in controllers, before the real logic is executed, like before firing an Event or pushing a Job.

No need to clog up your application with packages or hack into your application, or create your own Rate Limiter from the ground. RateLimiter is just there, ready to be used.

--

--

Italo Baeza Cabrera

Graphic Designer graduate. Full Stack Web Developer. Retired Tech & Gaming Editor. https://italobc.com