Skip to content

nearform/fastify-slow-down

Repository files navigation

CI

A slow down plugin for fastify

Installation

npm i fastify-slow-down

Usage

Register SlowDown as a Fastify plugin. This plugin will add an onRequest hook to slow down replies if a client (based on their IP address by default) has made too many multiple requests in the given timeWindow and it will add slowDown request decorator, which is an object with the following properties:

Name Type Description
limit number the maximum limit until the server starts to delay the response
current number the index of the current request
remaining number how many requests are left until the server starts to delay the response
delay number | undefined value of the delay (in milliseconds) applied to this request
import Fastify from 'fastify'
import slowDownPlugin from 'fastify-slow-down'

const fastify = Fastify()

// register the plugin
fastify.register(slowDownPlugin)

// create a route
fastify.get('/', async () => 'Hello from fastify-slow-down!')

// start server
await fastify.listen({ port: 3000 })

The response will have some additional headers:

Header Description
x-slow-down-limit how many requests in total the client can make until the server starts to slow down the response
x-slow-down-remaining how many requests remain to the client in the timeWindow
x-slow-down-delay how much delay (in milliseconds) has been applied to this request

Configuration

Name Type Default Value Description
delay string | number 1 second Base unit of time delay applied to requests. It can be expressed in milliseconds or as string in ms format. Set to 0 to disable delaying.
delayAfter number 5 number of requests received during timeWindow before starting to delay responses. Set to 0 to disable delaying.
maxDelay string, number Infinity the maximum value of delay that a request has after many consecutive attempts. It is an important option for the server when it is running behind a load balancer or reverse proxy, and has a request timeout. Set to 0 to disable delaying.
timeWindow string |, number 30 seconds The duration of the time window during which request counts are kept in memory. It can be expressed in milliseconds or as a string in ms format. Set to 0 to disable delaying.
inMemoryCacheSize number 5000 The maximum number of items that will be stored in the in-memory cache (this plugin internally uses a lru cache to handle the clients, you can change the size of the cache with this option)
redis Redis client instance from the ioredis package undefined by default this plugin uses an in-memory store, but if your application works on more than one server it is useless, since the data is stored locally. You can pass a Redis client here and magically the issue is solved. To achieve the maximum speed, this plugin requires the use of ioredis. Note: the default parameters of a redis connection are not the fastest to provide a slow-down. We suggest to customize the connectTimeout and maxRetriesPerRequest as in the example.
headers boolean true flag to add custom headers x-slow-down-limit, x-slow-down-remaining, x-slow-down-delay for all server responses.
keyGenerator function (req) => req.ip Function used to generate keys to uniquely identify requests coming from the same user
onLimitReached function undefined Function that gets called the first time the limit is reached within timeWindow.
skipFailedRequests boolean false When true, failed requests (status >= 400) won't be counted.
skipSuccessfulRequests boolean false When true, successful requests (status < 400) won't be counted.
skip function undefined Function used to skip requests. Returning true from the function will skip limiting for that request.

Example with configuration

Registering the plugin with these options:

fastify.register(slowDownPlugin, {
  delay: '10 seconds',
  delayAfter: 10,
  timeWindow: '10 minutes',
  maxDelay: '100 seconds'
})

A delay specified via the delay option will be applied to requests coming from the same IP address (by default) when more than delayAfter requests are received within the time specified in the timeWindow option.

In 10 minutes the result of hitting the API will be:

  • 1st request - no delay
  • 2nd request - no delay
  • 3rd request - no delay
  • ...
  • 10th request - no delay
  • 11th request - 10 seconds delay
  • 12th request - 20 seconds delay
  • 13th request - 30 seconds delay
  • ...
  • 20th request - 100 seconds delay
  • 21st request - 100 seconds delay*

After 10 minutes without hitting the API the results will be:

  • 21th request - no delay
  • 21th request - no delay
  • ...
  • 30th request - no delay
  • 31th request - 10 seconds delay

*Delay remains the same because the value of maxDelay option is 100 seconds

Integration with fastify-rate-limit

The plugin slows down the response without stopping it at a certain limit. This can put the server in a difficult situation because the client can make many requests without waiting for a response. To limit the number of requests, it should be used together with the fastify-rate-limit plugin.

See in this example which is the implementation using fastify-slow-down and fastify-rate-limit plugins.

If the delayAfter value of the fastify-slow-down package is set to be less than the max value of the fastify-rate-limit package, then you will see the responses being delayed once the delayAfter value has been exceeded, by the number of ms as specified in the delay value.

However, once the max value from the fastify-rate-limit package has been exceeded, then as expected you will still have rate limit errors returned but they will also be delayed according to the delay value from the fastify-slow-down package assuming that the delayAfter value has also been exceeded.