Rate limiting is one of the most important security feature that you must add for securing backend APIs from malicious attacks like brute-forcing
/admin and for handling unwanted flood of requests from users. In simple terms, rate-limiting allows us as a developer to control the rate at which user requests are processed by our server.
In this guide, we will learn how to add rate limiting for a small-sized project as quickly as possible and how to scale such a method for a production-ready application.
Simply said, you want to reduce the risk of DOS ↗️ attacks and make sure that your server is never overloaded.
For example, you have to build a Public API and you want to allow unsubscribed users to make only 100 requests per hour. Once the user has exceeded that limit, you just want to simply ignore the request and send them an error indicating that they have exhausted their free limit and consider to subscribe for your API or anything or that sort.
Kindly bear in mind that for implementing a rate-limiting technique, there must be a clearly defined constraint, which could be based on any of the following:
- IP address: The constraint is on the IP address of the device from where the request was initiated.
- Location: The constraint here is based on the geographical region which is implemented based on the location from where the request was made.
- Users: The constraint is made on the particular user and is implemented by using a unique identifier for the particular user like userId, API KEY, etc.
Several algorithms can be used to implemented rate limiting, you can read more about them here ↗️.
Given that, let us start with the practical implementation of API rate limiting.
We will use a third-party npm package called express-rate-limit ↗️ for this purpose. Surely we can build a custom middleware ourselves but there is no need to reinvent the wheel.
I’m assuming that you already have a project set up with express. If not then quickly setup an express project using boilerplate-gen ↗️ package.
and choose express for the project template.
Install the package.
Let’s now quickly try to understand what we are doing here.
We are exporting a function called
rateLimitMiddleware which invokes the rateLimit function which we installed from the package. This middleware enforces rate limiting based on the options we have passed in, which are -
windowMs- The window size in milliseconds, which in our case is 1 hour.
max- Maximum number of requests which can be allowed in the given window size.
message- The error message that the user would get when they exceed their limit.
headers- This option automatically adds the appropriate headers showing that this API resource is rate limited (
X-RateLimit-Limit), current usage (
X-RateLimit-Remaining) and time to wait before retrying (
Now that we have created our middleware, we have simply configure our application to use this middleware when handling the requests.
Let’s do that quickly
Voilà! We are done here. Now all the requests will be rate-limited according to your configurations. Also you can add multiple middlewares with different sets of configurations for certain routes.
For eg, normal routes can be rate-limited to 100 requests per hour and
/admin can be rate-limited to 20 requests per hour to avoid brute force password attacks ↗️
Note: This package identifies users by their IP addresses using
Great! So now you have added a rate limiter for your API with just 4 simple steps.
Now let’s move to the other half section of this blog, which is…
The above-mentioned implementation is really good if you are building a small to medium size application. However, this approach will not scale for large applications.
Why is that?? You must be asking right.
Firstly, if your application size is large, most likely you won’t be having a single node process on a single server. Rather you will have multiple node processes running on a distributed system and the above third party package does not share state with other processes/servers by default.
So using the same configurations you won’t be able to scale.
So what’s the solution there? How can I share the state between multiple server instances?
The answer is quite simple
You use an
External Data Store to store all the information.
express-rate-limit package uses
Memory Store by default which stores
hits in-memory in the Node.js process, hence cannot share the state between processes.
So we can use an external data store to store this information and that way we can have multiple processes/servers using that external store and that way we can scale our application.
Now the question is what should we use as our
Data Store. Well, there are many choices like -
- Redis Store
- Memcached Store
- Mongo Store
- MySQL etc.
I would prefer to choose Redis ↗️ because it is fast and flexible with support for various types of data structures.
Also we will use another third-party package called rate-limiter-flexible ↗️ as it works with Redis, process Memory, Cluster or PM2, Memcached, MongoDB, MySQL, PostgreSQL and allows to control requests rate in a single process or distributed environment.
Let’s now start with the implementation part. Assuming you have an existing project, or use the same method from above to set up a new one quickly.
Install the packages needed.
We will use Redis for the project, if you don’t have Redis installed then first download and install it from here ↗️
Let’s break it down by each section.
We import the packages, both Redis and rate-limiter-flexible, and use the
RateLimiterRedissince we are implementing with Redis.
We create Redis client connecting to the local machine on Redis default port
6379. You can use a remote hosted machine with Redis as well here (which you might do for large systems).
We create the rateLimiter instance with some configuration options
redis- The redisClient instance we created.
keyPrefix- Adding a prefix to all the keys generated from it, we can use it like
appname:rl, rl is for ratelimit. Feel free to choose any other keyPrefix.
points- Maximum number of points can be consumed over the duration.
duration- Number of seconds before consumed points are reset. If it is set to
0then it never resets.
You can view more options here ↗️
Finally, we set up our middleware which uses the rateLimiter instance we created above and we consume based on the
req.ipi.e IP address of the user.
Finally, we will use this middleware in our
And..that’s it. Now you can scale your application with rate limiter. I would highly suggest you to check the docs of the package for more details and configuration options.
We learn how to set up rate limiter for nodejs and expressjs for small and large size applications with implementation with code examples.