Custom Throttler Guard in NestJS with Redis

In this guide, we will explain why and how to create a custom throttler guard in NestJS, particularly when Redis is used for storing rate-limiting data.

By the end of this guide, you'll understand:

  1. The benefits of Redis for rate-limiting.

  2. The need for a Custom Throttler Guard.

  3. How to create and implement your own custom guard.

This will be a beginner-friendly approach, so let's break it down step by step!


Why Use Redis for Throttling?

Before diving into custom guards, let's recap why Redis is used for throttling.

  1. Persistence Across Restarts: Redis stores the rate-limiting data outside your application. If your server restarts, Redis will still hold the rate-limit counts, ensuring that clients can't avoid limits by forcing server restarts.

  2. Fast and Efficient: Redis is an in-memory data store that offers quick read/write operations, making it ideal for tasks like throttling, where you need to track a large number of requests in real-time.

  3. Distributed Environment: If your API runs across multiple servers (e.g., in a Kubernetes cluster), using Redis ensures that rate-limiting is consistent across all instances of your API.


What is a Custom Throttler Guard?

NestJS provides a ThrottlerGuard out of the box, which applies the rate-limiting logic globally to your application. But sometimes, your application may need custom throttling rules, such as:

  • Different limits for admin vs regular users.

  • Throttling requests based on user tokens or IP addresses.

  • Applying custom logic based on the request content.

To handle these use cases, we extend the default ThrottlerGuard and create a custom guard. This allows us to change the behavior of how throttling works, apply different limits to specific users, or even change how throttling is tracked.


Steps to Create a Custom Throttler Guard

Step 1: Install Dependencies

Make sure you have the required Redis packages installed:

npm install @nestjs/throttler ioredis @nest-lab/throttler-storage-redis

Step 2: Set up Throttling with Redis

In your app.module.ts, configure the ThrottlerModule to use Redis for storing the throttling data.

import { Module } from '@nestjs/common';
import { ThrottlerModule } from '@nestjs/throttler';
import { ThrottlerStorageRedisService } from '@nest-lab/throttler-storage-redis';
import { Redis } from 'ioredis';

@Module({
  imports: [
    ThrottlerModule.forRoot({
      ttl: 60, // Time window in seconds
      limit: 5, // Maximum 5 requests in 60 seconds
      storage: new ThrottlerStorageRedisService(
        new Redis({
          host: 'localhost',
          port: 6379,
        }),
      ),
    }),
  ],
})
export class AppModule {}

Here, we are specifying a time-to-live (TTL) of 60 seconds and a request limit of 5 requests in that window.


Step 3: Create the Custom Throttler Guard

Now, create a new file custom-throttler.guard.ts where you extend the default ThrottlerGuard to add custom logic.

import { Injectable, ExecutionContext } from '@nestjs/common';
import { ThrottlerGuard } from '@nestjs/throttler';

@Injectable()
export class CustomThrottlerGuard extends ThrottlerGuard {
  // Override the default tracking logic
  protected getTracker(req: Record<string, any>): string {
    // Use the user's ID for tracking if authenticated, else use the IP
    return req.user?.id || req.ip;
  }

  // Customize the rate-limit based on user roles
  protected getLimit(context: ExecutionContext): number {
    const request = context.switchToHttp().getRequest();
    const user = request.user;

    if (user?.role === 'admin') {
      return 10; // Admins can make 10 requests in the TTL window
    } else {
      return 5; // Regular users get 5 requests
    }
  }

  // Set a global time-to-live of 60 seconds for all requests
  protected getTTL(context: ExecutionContext): number {
    return 60; 
  }
}

Explanation of Methods in Custom Guard:

  • getTracker: This method controls how throttling is tracked. Instead of using the default (IP address), we use the user ID if the user is authenticated. If the user is not logged in, we fall back to using their IP address.

    • Why? Because authenticated users might share the same IP (e.g., in a corporate network), but each user has a unique ID.
  • getLimit: This method defines how many requests can be made in the specified ttl. We are applying different limits:

    • Admins get a higher limit (10 requests in 60 seconds).

    • Regular users get a lower limit (5 requests in 60 seconds).

  • getTTL: This method defines the time window for tracking requests. In this case, we set it to 60 seconds, meaning the request limit resets every 60 seconds.


Step 4: Apply the Custom Throttler Guard Globally

Once you've defined your custom guard, you need to apply it globally in app.module.ts.

import { Module } from '@nestjs/common';
import { ThrottlerModule } from '@nestjs/throttler';
import { APP_GUARD } from '@nestjs/core';
import { CustomThrottlerGuard } from './custom-throttler.guard';
import { ThrottlerStorageRedisService } from '@nest-lab/throttler-storage-redis';
import { Redis } from 'ioredis';

@Module({
  imports: [
    ThrottlerModule.forRoot({
      ttl: 60,
      limit: 5,
      storage: new ThrottlerStorageRedisService(
        new Redis({
          host: 'localhost',
          port: 6379,
        }),
      ),
    }),
  ],
  providers: [
    {
      provide: APP_GUARD,
      useClass: CustomThrottlerGuard,
    },
  ],
})
export class AppModule {}

This will ensure that your custom throttling logic is applied across your entire application.


Testing Your Custom Throttler Guard

You can now test the throttling by making multiple requests to your API. Try making more than 5 requests (if logged in as a regular user) or more than 10 requests (if logged in as an admin). Redis will store and track these requests across different servers and application restarts.

If you exceed the limit, you should see an error similar to:

{
  "statusCode": 429,
  "message": "Too many requests",
  "error": "Too Many Requests"
}

Benefits of Using a Custom Throttler Guard

  1. Fine-Grained Control: Apply different rate limits to different users or endpoints.

  2. Custom Tracking: Track requests based on custom identifiers (user ID, IP, etc.).

  3. Advanced Rate Limiting: Implement dynamic rate limiting based on roles, regions, or request types.

  4. Custom Error Messages: Provide more meaningful feedback to users when they hit rate limits.


Conclusion

Using Redis with a custom throttler guard in NestJS gives you the flexibility to tailor throttling to your application's needs. It ensures persistent, scalable, and customized rate limiting for different users and environments. Whether you're working on a small project or a distributed system, this approach allows you to optimize how your API handles traffic, keeping it secure and scalable.