<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Indira Mythili</title>
    <description>The latest articles on Forem by Indira Mythili (@mythili007).</description>
    <link>https://forem.com/mythili007</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F663512%2Fdc3df4b0-35ec-4658-9755-5a2366db0294.jpeg</url>
      <title>Forem: Indira Mythili</title>
      <link>https://forem.com/mythili007</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mythili007"/>
    <language>en</language>
    <item>
      <title>Implementing Rate Limiting in NestJS with Custom Redis Storage</title>
      <dc:creator>Indira Mythili</dc:creator>
      <pubDate>Mon, 09 Dec 2024 12:40:12 +0000</pubDate>
      <link>https://forem.com/mythili007/implementing-rate-limiting-in-nestjs-with-custom-redis-storage-4fnd</link>
      <guid>https://forem.com/mythili007/implementing-rate-limiting-in-nestjs-with-custom-redis-storage-4fnd</guid>
      <description>&lt;p&gt;In modern APIs, rate-limiting is essential to prevent abuse, ensure fair usage, and protect backend resources. This implementation uses Redis for storing rate-limit data, offering both efficiency and scalability. We use the built-in NestJS throttler module in this implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration ----
&lt;/h2&gt;

&lt;p&gt;The rate limit configuration is defined in &lt;code&gt;ratelimit.config.ts&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const apiRateLimit = {
  ttl: 60, // Time to live in seconds
  limit: 5, // Maximum number of requests
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Throttler Module ----
&lt;/h2&gt;

&lt;p&gt;The ThrottlerModule is configured in &lt;code&gt;throttler.module.ts&lt;/code&gt; using the &lt;code&gt;ThrottlerModule.forRootAsync&lt;/code&gt; method to load configuration dynamically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Module({
  imports: [
    CacheModule,
    ThrottlerModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (
        customStorage: CustomRedisThrottlerStorage,
        configService: ConfigService,
      ) =&amp;gt; ({
        storage: customStorage,
        throttlers: [
          {
            ttl: configService.get&amp;lt;number&amp;gt;('apiRateLimit.ttl'),
            limit: configService.get&amp;lt;number&amp;gt;('apiRateLimit.limit'),
          },
        ],
      }),
      inject: [CustomRedisThrottlerStorage, ConfigService],
    }),
  ],
  providers: [
    CustomRedisThrottlerStorage,
    ConfigService,
    ThrottlerInterceptor,
  ],
  exports: [
    ThrottlerInterceptor,
    CustomRedisThrottlerStorage,
    ConfigService,
  ],
})
export class CustomThrottlerModule {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Custom Redis Throttler Storage ----
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;CustomRedisThrottlerStorage&lt;/code&gt; class implements the &lt;code&gt;ThrottlerStorage&lt;/code&gt; interface to store rate limit data in Redis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Injectable()
export class CustomRedisThrottlerStorage implements ThrottlerStorage {
  constructor(private cacheService: CacheService) {}

  * We can write our own methods to fetch records from cache using the key we used to store. For example,

  async getRecord(key: string): Promise&amp;lt;ThrottlerStorageRecord | undefined&amp;gt; {
    const value = await this.cacheService.get(key);
    if (value) {
      // Fetch the TTL from redis cache
      const ttl = await this.cacheService.ttl(key);
      const record: ThrottlerStorageRecord = {
        totalHits: parseInt(value, 10),
        timeToExpire: ttl,
        isBlocked: false,
        timeToBlockExpire: 0,
      };
      return record;
    }
    return undefined;
  }

- Increment the value when the API gets hit.
  async increment(key: string, ttl: number): Promise&amp;lt;ThrottlerStorageRecord&amp;gt; {
    let oldValue = 0;
    // Retrieve the current request count for the API key from Redis.
    const val = await this.cacheService.get(key);
    if (val) oldValue = parseInt(val, 10);

    const newValue = oldValue + 1;
    // set the new updated value in cache
    await this.cacheService.setex(key, newValue.toString(), ttl);
    const record: ThrottlerStorageRecord = {
      totalHits: newValue,
      timeToExpire: ttl,
      isBlocked: false,
      timeToBlockExpire: 0,
    };
    return record;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Throttler Guard ----
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;ThrottlerGuard&lt;/code&gt; class extends ThrottlerGuard to customize the tracking of requests:&lt;/p&gt;

&lt;p&gt;We can use any unique identifier as a key, Here I have used "IP address + user-related info" as an unique parameter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Injectable()
export class CustomThrottlerGuard extends ThrottlerGuard {
  protected async getTracker(req: Record&amp;lt;string, string&amp;gt;): Promise&amp;lt;string&amp;gt; {
    return fetchIpAddress(req);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Throttler Interceptor
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;CustomThrottlerInterceptor&lt;/code&gt; class implements &lt;code&gt;NestInterceptor&lt;/code&gt; to handle rate limiting logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Injectable()
export class CustomThrottlerInterceptor implements NestInterceptor {
  async isRateLimited(key: string) {
    const limit = this.configService.get&amp;lt;number&amp;gt;('apiRateLimit.limit');
    const record = await this.customRedisThrottlerStorage.getRecord(key);
    return record.totalHits &amp;gt; limit;
  }

  intercept(context: ExecutionContext, next: CallHandler): Observable&amp;lt;any&amp;gt; {
    const request: Record&amp;lt;string, string&amp;gt; = context.switchToHttp().getRequest();
    const requestIP = fetchIpAddress(request);
    const key = `login:${requestIP}:${request.url}`;// Example redis key
    const ttl = this.configService.get&amp;lt;number&amp;gt;('apiRateLimit.ttl');

    return next.handle().pipe(
      catchError(async (err: Error) =&amp;gt; {        
        await this.customRedisThrottlerStorage.increment(key, ttl);
          if (await this.isRateLimited(key)) {
            throw new ThrottlerException();
          }
      }),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Testing Tips:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We can use Postman to send multiple requests and observe Redis entries using redis-cli.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test edge cases like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bursts of requests within the TTL.&lt;/li&gt;
&lt;li&gt;Requests exceeding the limit exactly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I used a simple flowchart to illustrate the entire process, showing how the throttler mechanism works step by step. If you have any suggestions, edits, or ideas for improvement, feel free to share them—I’d love to hear your feedback!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxbpwkhzd2nkjuzsq3rgb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxbpwkhzd2nkjuzsq3rgb.png" alt="Image description" width="716" height="1460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you for your time! I hope you found this article helpful!&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>typescript</category>
      <category>ratelimiting</category>
      <category>customredisstorage</category>
    </item>
  </channel>
</rss>
