📚 1. Introduction
Rate limiting is a crucial security and performance enhancement feature that protects your backend services from malicious or accidental overuse. In NestJS, we can implement rate limiting both on IP-based requests and Device ID-based requests.
This guide provides a step-by-step approach to implementing both methods, along with a clean folder structure and explanations of key components.
⚙️ 2. Prerequisites
Make sure you have the following installed:
- Node.js (>= 14.x)
-
NestJS CLI (
npm install -g @nestjs/cli
) - Basic understanding of NestJS, TypeScript, and Middleware concepts
🛠️ 3. Project Setup
3.1. Create a NestJS Project
nest new rate-limiting-app
cd rate-limiting-app
3.2. Install Required Dependencies
npm install @nestjs/throttler dotenv
📂 4. Folder Structure
Organize your project like this:
src/
│
├── app.module.ts
├── app.controller.ts
├── main.ts
│
├── device/
│ ├── guards/
│ │ └── custom-throttler.guard.ts
│ ├── services/
│ │ └── rate-limiter.service.ts
│ ├── device.controller.ts
│
├── util/
│ └── response.util.ts
│
└── custom-throttler.guard.ts
🌍 5. Environment Variables
Create a .env
file at the root level:
PORT=3000
# Rate Limit Configurations
TTL=60000 # Time-to-live in milliseconds (60 seconds)
LIMIT=3 # Maximum requests allowed in the time window
🚀 6. Application Entry Point
6.1. main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(process.env.PORT);
}
bootstrap();
🌐 7. IP-Based Rate Limiting
7.1. app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { DeviceController } from './device/device.controller';
import { ThrottlerModule } from '@nestjs/throttler';
import { APP_GUARD } from '@nestjs/core';
import { RateLimiterService } from './device/services/rate-limiter.service';
import { CustomThrottlerGuard } from './custom-throttler.guard';
@Module({
imports: [
ThrottlerModule.forRoot([
{
ttl: 60,
limit: 3,
},
]),
],
controllers: [AppController, DeviceController],
providers: [
RateLimiterService,
{
provide: APP_GUARD,
useClass: CustomThrottlerGuard,
},
],
})
export class AppModule {}
7.2. custom-throttler.guard.ts
import {
ThrottlerGuard as BaseThrottlerGuard,
ThrottlerException,
ThrottlerRequest,
} from '@nestjs/throttler';
import { Response } from 'express';
import { ResponseUtil } from './util/response.util';
import { Injectable } from '@nestjs/common';
@Injectable()
export class CustomThrottlerGuard extends BaseThrottlerGuard {
async handleRequest(requestProps: ThrottlerRequest): Promise<boolean> {
try {
return await super.handleRequest(requestProps);
} catch (error) {
if (error instanceof ThrottlerException) {
const response = requestProps.context.switchToHttp().getResponse<Response>();
response.status(429).json(
ResponseUtil.error(429, 'Too many requests from this IP, please try again later.'),
);
}
throw error;
}
}
}
7.3. app.controller.ts
import 'dotenv/config';
import { Controller, Get } from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
import { ResponseUtil } from './util/response.util';
@Controller('/api/ip')
export class AppController {
@Get('/test')
@Throttle({ default: { limit: Number(process.env.LIMIT), ttl: Number(process.env.TTL) } })
testIpEndpoint() {
return ResponseUtil.success(200, 'Request successful!', {
info: 'IP-based rate limiting is working as expected.',
});
}
}
📱 8. Device ID-Based Rate Limiting
8.1. rate-limiter.service.ts
import 'dotenv/config';
import { Injectable } from '@nestjs/common';
@Injectable()
export class RateLimiterService {
private readonly requestCache: Map<string, { count: number; timestamp: number }> = new Map();
private readonly limit = Number(process.env.LIMIT);
private readonly ttl = Number(process.env.TTL);
async isRateLimited(deviceId: string): Promise<boolean> {
const currentTime = Date.now();
const cachedData = this.requestCache.get(deviceId);
if (cachedData) {
const timeDiff = currentTime - cachedData.timestamp;
if (timeDiff < this.ttl) {
if (cachedData.count >= this.limit) {
return true;
} else {
cachedData.count++;
return false;
}
} else {
this.requestCache.set(deviceId, { count: 1, timestamp: currentTime });
return false;
}
} else {
this.requestCache.set(deviceId, { count: 1, timestamp: currentTime });
return false;
}
}
}
8.2. custom-throttler.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { RateLimiterService } from '../services/rate-limiter.service';
import { ResponseUtil } from '../../util/response.util';
@Injectable()
export class RateLimiterGuard implements CanActivate {
constructor(private readonly rateLimiterService: RateLimiterService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const response = context.switchToHttp().getResponse();
const deviceId = request.headers['device-id'];
if (!deviceId) {
response.status(403).json(ResponseUtil.error(403, 'Device ID is required'));
return false;
}
const isRateLimited = await this.rateLimiterService.isRateLimited(deviceId);
if (isRateLimited) {
response.status(429).json(ResponseUtil.error(429, 'Too many requests from this device, please try again later.'));
return false;
}
return true;
}
}
8.3. device.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { RateLimiterGuard } from './guards/custom-throttler.guard';
import { ResponseUtil } from '../util/response.util';
@Controller('/api/device')
export class DeviceController {
@Get('/test')
@UseGuards(RateLimiterGuard)
testDeviceEndpoint() {
return ResponseUtil.success(200, 'Request successful!', {
info: 'Device ID-based rate limiting is working as expected.',
});
}
}
📝 9. Advantages of Rate Limiting
- 🚫 Prevents Abuse
- ⚡ Improved Performance
- 🔒 Enhanced Security
- 📊 Fair Usage Policy
- 💸 Cost Management
✅ 10. Conclusion
This guide outlined both IP-based and Device ID-based rate-limiting strategies in NestJS.
Run the server:
npm run start:dev
Test Endpoints:
-
By IP:
curl http://localhost:3000/api/ip/test
-
By Device ID:
curl http://localhost:3000/api/device/test -H 'device-id: unique-device-id'
Author: Mohin Sheikh
Follow me on GitHub for more insights!
Top comments (1)
❤️🔥✌️