Rate limiting is a critical security mechanism that protects your APIs from brute-force attacks, DDoS (Distributed Denial of Service) attempts, and abusive traffic. When working with a REST API in NestJS, standard Express middlewares like express-rate-limit work beautifully out of the box.
However, when you use GraphQL, all your requests (whether it's a login mutation or a data query) go through a single endpoint (e.g., /graphql or /cms-api). Because of this, standard HTTP route-based middleware can't differentiate between your operations.
In this post, we'll walk through how to implement granular, resolver-level rate limiting in a NestJS GraphQL application using the official @nestjs/throttler package.
Step 1: Install the Required Packages
To get started, install the @nestjs/throttler package:
npm install @nestjs/throttler
Note: Depending on your NestJS version and peer dependencies, you might need to append --legacy-peer-deps.
Step 2: Create a Custom GraphQL Throttler Guard
By default, @nestjs/throttler is built for standard HTTP controllers. To make it understand the GraphQL context, we need to create a custom guard that overrides the getRequestResponse method.
Create a new file called gql-throttler.guard.ts (e.g., in src/guards/):
import { ExecutionContext, Injectable } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ThrottlerGuard } from '@nestjs/throttler';
@Injectable()
export class GqlThrottlerGuard extends ThrottlerGuard {
protected getRequestResponse(context: ExecutionContext) {
const gqlCtx = GqlExecutionContext.create(context);
const ctx = gqlCtx.getContext();
// Map the GraphQL context to the standard req/res objects
return { req: ctx.req, res: ctx.req.res || ctx.res };
}
}
Step 3: Register ThrottlerModule and Global Guard
Next, we need to import the ThrottlerModule into our main application module (app.module.ts) and register our custom guard globally so it protects all resolvers by default.
In this configuration, we'll define a default limit for all endpoints (e.g., 1000 requests per 15 minutes).
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { ThrottlerModule } from '@nestjs/throttler';
import { GqlThrottlerGuard } from './guards/gql-throttler.guard';
// ... other imports
@Module({
imports: [
ThrottlerModule.forRoot([
{
name: 'default',
ttl: 15 * 60000, // 15 minutes in milliseconds
limit: 1000, // 1000 requests per 15 mins globally
},
]),
// ... other modules
],
providers: [
{
provide: APP_GUARD,
useClass: GqlThrottlerGuard, // Applies our custom guard globally
},
// ... other providers
],
})
export class AppModule {}
Step 4: Override Limits on Specific Resolvers
Now that the entire app is protected by a generous rate limit, what about sensitive endpoints like Authentication? We don't want to allow 1000 login attempts per 15 minutes!
We can use the @Throttle() decorator to easily override the default limits on specific resolvers (or individual queries/mutations).
Open your auth.resolver.ts and apply the decorator:
import { Resolver } from '@nestjs/graphql';
import { Throttle } from '@nestjs/throttler';
@Resolver()
// Override the 'default' throttler rule for this specific resolver
@Throttle({ default: { limit: 100, ttl: 15 * 60000 } })
export class AuthResolver {
// All mutations and queries inside this resolver will now be restricted
// to 100 requests per 15 minutes per IP address.
// ... your login/register methods
}
Summary
With this setup, you have achieved:
- Global Protection: Every GraphQL query and mutation is automatically protected against abuse.
-
Context Awareness: The custom
GqlThrottlerGuardsafely parses GraphQL requests. - Granular Control: You can easily tighten limits on sensitive operations like authentication using a single decorator.
Happy secure coding!
Top comments (0)