How to Implement Rate Limiting in NestJS

Updated: January 1, 2024 By: Guest Contributor Post a comment

Introduction

Rate limiting is a crucial feature for APIs to control traffic and prevent abuse. In this tutorial, we will explore how to implement rate limiting in a NestJS application using the latest NestJS and TypeScript syntax.

Getting Started

Before we dive into rate limiting, ensure you have a NestJS project set up. If you need to create a new project, run the following command:

nest new project-name

Next, navigate to the project directory:

cd project-name

Installing nestjs-rate-limiter

To implement rate limiting, we’ll need to install the nestjs-rate-limiter package:

npm install nestjs-rate-limiter

Basic Rate Limiting

The simplest form of rate limiting can be applied application-wide. Import the RateLimiterModule in your app.module.ts:

import { RateLimiterModule } from 'nestjs-rate-limiter';

@Module({
  imports: [
    RateLimiterModule.register({
      points: 5, // Number of points
      duration: 60, // Per second(s)
    }),
  ],
  // ... other parts of the module
})
export class AppModule {}

This configuration will limit the entire application to five requests per minute for each IP address.

Controller-Specific Rate Limiting

To apply rate limiting on a specific controller, use the RateLimiterInterceptor:

import { RateLimiterInterceptor } from 'nestjs-rate-limiter';
import { UseInterceptors } from '@nestjs/common';

@Controller('specific')
@UseInterceptors(RateLimiterInterceptor)
export class SpecificController {
  // ... controller methods 
}

Customizing Rate Limiting

Rate limiting can be customized with a variety of options. For instance, changing the key prefix:

RateLimiterModule.register({
  keyPrefix: 'my-prefix', // custom prefix for rate limiting keys
  // ... other options
})

Or configuring rate limiting based on the method type:

@UseInterceptors(new RateLimiterInterceptor({
  points: 3,
  duration: 60,
  method: RequestMethod.POST,
}))
public create() {
  // POST method-specific handling
}

This will limit the POST requests to three per minute.

Async Options

For more dynamic configurations, you may want to use the registerAsync method, which allows you to use factory functions, existing configuration, or async providers:

@Module({
  imports: [
    RateLimiterModule.registerAsync({
      useFactory: () => ({
        points: 10,
        duration: 60,
      }),
      // Alternatively useExisting or useClass
    }),
  ],
})
export class AppModule {}

Rate Limiting in Microservices

If your NestJS application consists of microservices, you might need to consider a distributed rate limiting solution using a storage, like Redis:

RateLimiterModule.register({
  storeType: 'redis',
  redis: new Redis(), // Redis instance
  points: 10,
  duration: 60,
})

This configuration leverages Redis to rate limit your microservices in a distributed manner.

Handling Rate Limiting Responses

In the event that a limit is exceeded, a 429 Too Many Requests HTTP response is typically returned. You can customize the rate limit exceed message using the exceptionFactory option:

RateLimiterModule.register({
  // ... other options
  exceptionFactory: (errors) => new MyCustomException(errors),
})

Monitoring and Logging

Monitoring rate limit status can be essential for debugging. You can access rate limit information and create logs using interceptors or middleware as shown:

@Injectable()
export class RateLimitInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable {
    const rateLimitInfo = context.switchToHttp().getResponse().getHeaders()['retry-after'];
    // log the rateLimitInfo using your preferred logger
    return next.handle();
  }
}

Testing Rate Limiting

When integrating rate limiting, tests play a vital role to ensure functionality. Use libraries like supertest to automate testing of rate limits:

describe('Rate Limited Endpoint', () => {
  it('should return 429 status after rate limit exceeded', async () => {
    const server = app.getHttpServer();
    for (let i = 0; i <= 5; i++) {
      const response = await request(server).get('/rate-limited-endpoint');
      if (i === 5) {
        expect(response.status).toBe(429);
      }
    }
  });
});

Conclusion

In conclusion, NestJS provides an elegant and scalable way to implement rate limiting. By using the nestjs-rate-limiter package, we can easily apply rate limits to our entire application, specific routes, or controllers, and customize our configuration to fit the needs of our project. With proper testing and logging, rate limiting can help maintain API stability and prevent abuse, ensuring a reliable service for users.