Understanding Rate Limiting in Node.js


Firstly, let us understand what Rate Limiting is. Rate limiting is a powerful feature that secures the backend API from malicious attacks and is also used for handling unwanted traffic(requests) from the user.

Now, let us understand what rate limiting is and how it actually works.

Introduction

The incoming and outgoing traffic of a network (communication between client and a server) is controlled by Rate Limiting. Clients can be Web browser and server can be APIs. Therefore, rate limiting allows us to handle user requests based on following constraints ->

  • There is better flow of data between client and server.
  • Improved Security
  • Server must never be overloaded
  • User can only perform that tasks which are allowed by the developer.

For Rate Limiting to be implemented, the limit could be based on ->

  • User - Limit must be specific to the user.
  • Location - Limit is implemented from the location from where the request was made.
  • IP Address - Limit is based on the IP address of the device from which the request was made.

It is used against the API that uses leaky bucket(a queue which takes requests in First In First Out(FIFO) way) for incoming requests.

Pros and Cons of Rate Limiting

Pros:

  • Easy to use
  • Works in an efficient way.

Cons:

  • Managing of requests by the user is hard.
  • It cannot be configured individually.

It can be installed by the following command ->

npm i request-rate-limiter --save

Note - Install it after initializing the package.json file. For initializing package.json file type the following command in the terminal ->

npm init

API

Constructor

It accepts four parameters that is used to configure the behaviour of the limiter. The four parameters are as follows ->

  1. backoffTime - It indicated how much time(in seconds) to backoff when there is a signal to backoff from the remote end.
  2. requestRate - It indicates how many requests can be sent within the interval.
  3. interval - It is the interval within which all requests of requestRate should be executed.
  4. timeout - It indicated the time after which no request must be present in the queue. Any request will be rejected if the queue is full.

For example,

import RequestRateLimiter from 'request-rate-limiter';
 
const limiter = new RequestRateLimiter({
    backoffTime: 20,
    requestRate: 80,
    interval: 80,
    timeout: 700,
});

setRequestHandler

It is used to pass a request handler to the limiter. Let us see it from an example,

import RequestRateLimiter, { RequestsRequestHandler } from 'request-rate-limiter';
 
const limiter = new RequestRateLimiter();
 
limiter.setRequestHandler(new RequestsRequestHandler({
    backoffHTTPCode: 429,
}));

The HTTP 429 response status code indicates that the user has sent too many requests in a given amount of time.

Request

This is the method that is used for sending rate limiting requests. Firstly, we need to pass the configuration of the request to it, which will later be passed to the request handler. Let us see an example for it ->

import RequestRateLimiter, { RequestsRequestHandler } from 'request-rate-limiter';
 
const limiter = new RequestRateLimiter();
limiter.setRequestHandler(new RequestsRequestHandler());

const response = await limiter.request('https://iq.opengenus.org/');

for (const requestConfig of requests) {
    const response = await limiter.request(requestConfig);
}

await Promise.all(requests.map(async(requestConfig) => {
    const response = await limiter.request(requestConfig);
}));

Here, the "await" expression causes the execution of "async" function to pause until the "Promise" is fulfilled or rejected. It will resume the execution of "async" function after fulfillment of "Promise".

If the "Promise" is rejected, the "await" expression will throw the rejected value.

If the value of the expression following "await" expression is not a Promise, then it will be converted to Promise.resolve(value). Here, The Promise.resolve() method returns a Promise object that is resolved with a given value. If the value is a promise, that promise is returned. If the value passed as an argument is a thenable (i.e. has a "then" method), the returned promise will "follow" that thenable, adopting its eventual state. Otherwise the returned promise will be fulfilled with the value.

Idle

This method calls the "Promise" when the limiter becomes idle. When the Promise is resolved, one must call the idle method again to wait for the next bunch of requests to complete.
Let us see an example,

import RequestRateLimiter, { RequestsRequestHandler } from 'request-rate-limiter';
 
const limiter = new RequestRateLimiter();
limiter.setRequestHandler(new RequestsRequestHandler());

Promise.all(requests.map(async(requestConfig) => {
    const response = await limiter.request(requestConfig);
})).catch((err) => {
    console.log("Requests Overflowing");
});

await limiter.idle();

Here, "await limiter.idle()" is used to wait until the limiter becomed idle.

Rate Limiting Working

Rate limiting defines the rate and level of particular resource that can be accessed by the client. Throttling process is used to monitor and control the given traffic (requests).

Therefore, whenever the request is incoming, we check the throttle limit. If throttle limit is reached, we return HTTP status code 429 with the message "too many requests".