Image description

What is Rate Limiting?

Rate limiting is a technique used to control the number of requests a user or system can make to a server in a given time period.
Think of it like this:

You can’t drink an entire bottle of water in one gulp (at least you shouldn’t 😅). You sip it slowly. Similarly, APIs and servers also want users to “sip” data—request by request, not flood the server with 1000 requests per second.

It’s basically saying:

“Hey, you can only make 100 requests per 15 minutes. Chill.”

In this blog, I’ll walk you through how to implement rate limiting in a NestJS application using the official @nestjs/throttler package.

We’ll cover:

  1. Why rate limiting is important

  2. How to configure global and route-specific limits

  3. How to override limits per route

  4. How to skip limits entirely

  5. How to apply rate limiting based on users (like email)

Let’s get started! 🚀

Why Do We Need Rate Limiting?

Without rate limiting, bots or even regular users can spam your API with a flood of requests. This can lead to:

  • Increased server load

  • Slower response times

  • Downtime or crashes

Rate limiting solves this by capping how many requests are allowed in a given time period.

Installing Throttler

First, install the throttler package:
npm install @nestjs/throttler

Setting Up Global Rate Limiting

In your AppModule, you can define multiple throttling rules using different names.

import { Module } from "@nestjs/common";
import { APP_GUARD } from "@nestjs/core";
import { ThrottlerGuard, ThrottlerModule } from "@nestjs/throttler";

@Module({
  imports: [
    ThrottlerModule.forRoot({
      throttlers: [
        {
          name: "short",
          ttl: 10000, // 10 seconds
          limit: 3, // 3 requests per 10 sec
        },
        {
          name: "long",
          ttl: 86400000, // 24 hours
          limit: 9, // 9 requests per day
        },
      ],
    }),
  ],
  providers: [
    {
      provide: APP_GUARD,
      useClass: ThrottlerGuard,
    },
  ],
})
export class AppModule {}

How It Works

If a user exceeds these limits, they get a 429 Too Many Requests error:

{
  "statusCode": 429,
  "message": "Too Many Requests"
}

Override Global Limits per Route

Use the @Throttle() decorator to set different limits for individual routes:

import { Controller, Get } from "@nestjs/common";
import { Throttle } from "@nestjs/throttler";

@Controller("products")
export class ProductController {
  @Throttle({ default: { limit: 5, ttl: 30000 } }) // 5 requests per 30 seconds
  @Get()
  getProducts() {
    return "products list";
  }
}

This overrides the global limit for this route only.

Skip Rate Limiting for Specific Controllers or Routes

You can exclude an entire controller or just a specific route from throttling using @SkipThrottle().
Skip for Entire Controller

import { Controller, Get } from "@nestjs/common";
import { SkipThrottle } from "@nestjs/throttler";

@SkipThrottle()
@Controller("users")
export class UsersController {
  @Get()
  findAll() {
    return "user list";
  }
}

//Apply Skip Inside the Same Controller

@Controller("products")
export class ProductController {
  @SkipThrottle({ default: false }) // Explicitly apply rate limit
  @Get("image")
  getImage() {
    return "product image";
  }

  @SkipThrottle() // Skip rate limit for this route
  @Get("title")
  getTitle() {
    return "product title";
  }
}

Custom Rate Limiting Based on Email Instead of IP

By default, throttler uses the IP address to track requests. You can customize this by creating a custom guard that tracks based on the user’s email (if authenticated):

  1. Create a Custom Guard
import {Injectable,CanActivate,ExecutionContext} from '@nestjs/common';
import {ThrottlerGuard,ThrottlerStorage,ThrottlerModuleOptions,} from '@nestjs/throttler';
import { JwtService } from '@nestjs/jwt';
import { AuthJwtConfig } from 'src/configs/auth-jwt.config';
import { Reflector, ModuleRef } from '@nestjs/core';
import { UsersApiService } from 'src/users/users-api.service';
import { UserSelectTypeQuery } from 'src/users/filters/user.filter';

@Injectable()
export class EmailThrottlerGuard extends ThrottlerGuard implements CanActivate{
private jwtService: JwtService;
private authJwtConfig: AuthJwtConfig;
private userData:UsersApiService;

constructor(
options: ThrottlerModuleOptions,
storageService: ThrottlerStorage,
reflector: Reflector,
moduleRef: ModuleRef,
) {
super(options, storageService, reflector);

 this.jwtService = moduleRef.get(JwtService, { strict: false });
 this.authJwtConfig = moduleRef.get(AuthJwtConfig, { strict: false });
 this.userData=moduleRef.get(UsersApiService,{strict:false})

}

protected async getTracker(req: Record<string, any>): Promise<string> {

// Check if it is login req and the request body contains a `username`.
// If available, use it to generate a unique key for rate limiting.
// This ensures that rate limiting is applied per user rather than per IP.
// if not login req then take token from req headers and decrypt token and find user id from token after that
// find user email from database and set as a key for rate limiting


 let key = 'anonymous';
 if (req.url === '/auth/login') {
   const username = req.body?.username;
   if (username) {
     key = `rate-limit-${username}`;
   }
   return key;
 }
 try {
   const authHeader = req.headers?.authorization;
   const token = authHeader?.split(' ')[1];
   if (token) {
     const decoded = this.jwtService.verify(token, {
       secret: this.authJwtConfig.secret,
     });
     if (decoded?.sub) {
       const parmaData=req.params
       parmaData.selectType="detail"
       const user = await this.userData.findOne(decoded.sub,new UserSelectTypeQuery())
       const userEmail = user?.email
       key = `rate-limit-${userEmail}`;
     }
   }
   else{
     if (req.body?.username) {
       key = `rate-limit-${req.body.username}`;
     }
   }
 } catch (err) {
   console.warn('JWT verification failed:', err.message);
 }
 return key;

}
}
  1. Use the Custom Guard in AppModule
providers: [
    {
    provide: APP_GUARD,
    useClass: EmailThrottlerGuard,
    },
    ],

This allows you to limit each logged-in user separately, instead of by IP address.

Multiple Throttle Profiles: When & Why?

You can define multiple throttle profiles like "short" for spam-sensitive routes and "long" for daily caps. This allows more fine-grained control across different routes and use cases.
For example:

  • /login → use short burst limits

  • /daily-report → use long-term limits

Conclusion

By using @nestjs/throttler, you can:

  • Easily apply global rate limits

  • Override them per route with @Throttle()

  • Skip rate limits for certain routes using @SkipThrottle()

  • Customize user tracking via email instead of IP

  • Define multiple rate limit strategies (short vs long windows)

Rate limiting helps you protect your API, scale responsibly, and ensure fair use. Whether you're building a small app or a large-scale platform, adding this layer is a smart move.
If you found this helpful and want more like this, drop a comment below or reach out to me!