npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kyrasource/rate-limit

v1.0.1

Published

Storage-agnostic rate limiting utilities for NestJS applications with in-memory store by default and pluggable backend support

Downloads

16

Readme

@kyrasource/rate-limit

Storage-agnostic rate limiting utilities for NestJS applications. Ships with a fast in-memory store by default and a clean store interface so you can plug in Redis or any other backend without coupling.

Features

  • Flexible Storage: In-memory by default, swap to Redis or any custom backend
  • Global Guard: Automatically rate limits all endpoints (configurable)
  • Per-Route Configuration: Override limits on specific handlers
  • IP-Based Tracking: Extracts client IP from headers and socket
  • Whitelist Support: Bypass rate limits for trusted IPs
  • Standard Headers: Returns X-RateLimit-* and Retry-After headers
  • TypeScript Support: Full type safety with interfaces
  • Error Handling: Returns HTTP 429 when limit exceeded
  • Production Ready: Used in distributed deployments with Redis

Installation

npm install @kyrasource/rate-limit

Quick Start

1. Setup in Your Module

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { RateLimitModule } from '@kyrasource/rate-limit';
import rateLimitConfig from './config/rate-limit.config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [rateLimitConfig],
    }),
    RateLimitModule, // Registers as APP_GUARD automatically
  ],
})
export class AppModule {}

2. Configure Rate Limits in .env

RATE_LIMIT_ENABLED=true
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100
RATE_LIMIT_PREFIX=rate-limit
RATE_LIMIT_MESSAGE=Too many requests, please try again later.
RATE_LIMIT_WHITELIST=127.0.0.1,::1

3. Apply Decorators to Routes

import { Controller, Get } from '@nestjs/common';
import { RateLimit } from '@kyrasource/rate-limit';

@Controller('api')
export class ApiController {
  // Uses global rate limit (100 requests per 15 minutes)
  @Get('public')
  public() {
    return { message: 'public endpoint' };
  }

  // Custom: 30 requests per minute
  @Get('search')
  @RateLimit({ windowMs: 60_000, maxRequests: 30 })
  search() {
    return { results: [] };
  }

  // Custom: 5 requests per hour
  @Get('expensive')
  @RateLimit({ windowMs: 3_600_000, maxRequests: 5 })
  expensiveOperation() {
    return { data: 'heavy computation' };
  }

  // No rate limiting
  @Get('health')
  @RateLimit({ enabled: false })
  health() {
    return { status: 'ok' };
  }
}

Configuration

Create src/config/rate-limit.config.ts:

import { registerAs } from '@nestjs/config';
import { RateLimitConfig } from '@kyrasource/rate-limit';

export default registerAs('rateLimitConfig', (): RateLimitConfig => ({
  enabled: process.env.RATE_LIMIT_ENABLED !== 'false',
  windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS ?? '900000', 10), // 15 min
  maxRequests: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS ?? '100', 10),
  keyPrefix: process.env.RATE_LIMIT_PREFIX ?? 'rate-limit',
  message: process.env.RATE_LIMIT_MESSAGE ?? 'Too many requests',
  whitelist: (process.env.RATE_LIMIT_WHITELIST ?? '')
    .split(',')
    .map(ip => ip.trim())
    .filter(Boolean),
}));

API Reference

RateLimit Decorator

Override global settings per route:

@RateLimit({
  enabled: boolean;        // Enable/disable limiting for this route
  windowMs: number;        // Time window in milliseconds
  maxRequests: number;     // Max requests allowed in window
  keyPrefix?: string;      // Redis key prefix (optional)
  message?: string;        // Custom error message (optional)
  whitelist?: string[];    // IPs to bypass limits (optional)
})

HTTP Headers

Every response includes:

X-RateLimit-Limit: 100          // Max requests in window
X-RateLimit-Remaining: 95       // Requests left
X-RateLimit-Reset: 1705718200   // UNIX timestamp when window resets
Retry-After: 30                 // (Only when 429) Seconds to retry

Error Response (HTTP 429)

When rate limit is exceeded:

{
  "statusCode": 429,
  "message": "Too many requests, please try again later",
  "retryAfter": 42
}

Advanced Usage

Using Redis Store (Distributed)

For multi-instance deployments, use Redis:

// src/shared/rate-limit/redis-rate-limit.store.ts
import { Injectable } from '@nestjs/common';
import { RateLimitStore } from '@kyrasource/rate-limit';
import { RedisService } from '@kyrasource/redis';

@Injectable()
export class RedisRateLimitStore implements RateLimitStore {
  constructor(private readonly redis: RedisService) {}

  async get<T = any>(key: string): Promise<T | null> {
    return this.redis.get<T>(key);
  }

  async set(key: string, value: any, ttlSeconds?: number): Promise<void> {
    await this.redis.set(key, value, ttlSeconds);
  }
}

// In your module:
import { RateLimitModule, RATE_LIMIT_STORE } from '@kyrasource/rate-limit';

@Module({
  imports: [RateLimitModule],
  providers: [
    RedisService,
    RedisRateLimitStore,
    {
      provide: RATE_LIMIT_STORE,
      useClass: RedisRateLimitStore,
    },
  ],
})
export class ApiModule {}

Whitelisting Trusted IPs

@Controller('admin')
export class AdminController {
  // Admin endpoints - stricter limits, but whitelist internal IPs
  @Get('users')
  @RateLimit({
    windowMs: 60_000,
    maxRequests: 10,
    whitelist: ['127.0.0.1', '192.168.1.0/24', '::1'],
  })
  listUsers() {
    return { users: [] };
  }
}

Disabling for Specific Routes

@Controller('health')
export class HealthController {
  // No rate limiting for health checks
  @Get()
  @RateLimit({ enabled: false })
  check() {
    return { status: 'healthy' };
  }
}

Custom Key Strategy

By default, keys are built as: ${keyPrefix}:${method}:${path}:${clientIp}

This means:

  • Each HTTP method is tracked separately
  • Each route is tracked separately
  • Each client IP is tracked separately

Example: rate-limit:POST:/api/search:203.0.113.45

IP Detection

The guard extracts client IP in this order:

  1. x-forwarded-for header (supports multiple IPs, takes first)
  2. x-real-ip header
  3. request.ip from Express
  4. request.socket.remoteAddress
  5. Falls back to 'unknown'

Exports

From @kyrasource/rate-limit you can import:

export { RateLimitModule } from './rate-limit.module';
export { RateLimitGuard } from './rate-limit.guard';
export { RateLimitService } from './rate-limit.service';
export { RateLimit } from './rate-limit.decorator';
export { RATE_LIMIT_STORE } from './rate-limit.constants';
export { RateLimitStore } from './rate-limit.store';
export { RateLimitConfig, RateLimitOptions, RateLimitResult } from './rate-limit.types';

Default Configuration

If not configured via environment or ConfigService:

{
  enabled: true,
  windowMs: 15 * 60 * 1000,  // 15 minutes
  maxRequests: 100,
  keyPrefix: 'ratelimit',
  message: 'Too many requests, please try again later',
  whitelist: [],
}

Troubleshooting

"Rate limit exceeded" immediately

  • Check RATE_LIMIT_MAX_REQUESTS value (too low?)
  • Check RATE_LIMIT_WINDOW_MS value (too short?)
  • Verify IP detection is working (check logs for client IP)

Whitelist not working

  • Ensure IPs are comma-separated in .env
  • Check that client IP matches whitelist entry
  • Note: X-Forwarded-For may have multiple IPs

Store errors

  • If using Redis store, verify Redis is running
  • Check RATE_LIMIT_STORE provider is registered
  • Monitor logs for connection errors

License

MIT - See LICENSE file

Support

For issues, feature requests, or documentation, visit:

  • GitHub: kyrasource/rate-limit
  • npm: @kyrasource/rate-limit enabled: true, windowMs: 15 * 60 * 1000, // 15 minutes maxRequests: 100, keyPrefix: 'ratelimit', message: 'Too many requests, please try again later', whitelist: [] as string[], };

Provide your own in `AppModule` (optional):

```ts
import { Module } from '@nestjs/common';
import { RateLimitModule } from '@kushi/rate-limit-lib';

@Module({
  imports: [RateLimitModule],
  providers: [
    { provide: 'RATE_LIMIT_CONFIG', useValue: {
        enabled: true,
        windowMs: 10_000,
        maxRequests: 20,
        keyPrefix: 'api',
        message: 'Rate limit exceeded',
        whitelist: ['127.0.0.1'],
    } },
  ],
})
export class AppModule {}

Storage Abstraction (Replaceable Store)

This package is storage-agnostic. It relies on a minimal interface and a DI token so you can swap implementations.

  • Token: RATE_LIMIT_STORE
  • Interface:
export interface RateLimitStore {
  get<T = any>(key: string): Promise<T | null>;
  set(key: string, value: any, ttlSeconds?: number): Promise<void>;
}

It ships with a default InMemoryRateLimitStore. For multi-instance or distributed environments, override the token with your own store (e.g., Redis).

Example: Redis-backed store (adapter in your app)

// src/shared/rate-limit/redis-rate-limit.store.ts
import { Injectable } from '@nestjs/common';
import { RateLimitStore } from '@kyrasource/rate-limit';
import { RedisService } from '@kyrasource/redis'; // your Redis client wrapper

@Injectable()
export class RedisRateLimitStore implements RateLimitStore {
  constructor(private readonly redis: RedisService) {}

  async get<T = any>(key: string): Promise<T | null> {
    return this.redis.get<T>(key);
  }

  async set(key: string, value: any, ttlSeconds?: number): Promise<void> {
    await this.redis.set(key, value, ttlSeconds);
  }
}

Register it to override the default store:

import { Module } from '@nestjs/common';
import { RateLimitModule, RATE_LIMIT_STORE } from '@kyrasource/rate-limit';
import { RedisService } from '@kyrasource/redis';
import { RedisRateLimitStore } from './shared/rate-limit/redis-rate-limit.store';

@Module({
  imports: [RateLimitModule],
  providers: [
    RedisService,
    { provide: RATE_LIMIT_STORE, useClass: RedisRateLimitStore },
  ],
})
export class AppModule {}

Per-Route Options

Use the RateLimit decorator to override any of the following per handler/class:

export interface RateLimitOptions {
  enabled: boolean;
  windowMs: number;     // duration of the window in ms
  maxRequests: number;  // max requests allowed in window
  keyPrefix: string;    // prefix for storage keys
  message: string;      // error message when blocked
  whitelist?: string[]; // list of IPs to bypass limiting
}

Headers and Errors

On every request, the guard sets the following headers:

  • X-RateLimit-Limit: the configured limit for the window
  • X-RateLimit-Remaining: remaining requests in the current window
  • X-RateLimit-Reset: UNIX seconds when the window resets
  • Retry-After: seconds until reset (only when blocked)

When blocked, the guard throws HTTP 429 with a body like:

{
  "message": "Too many requests, please try again later",
  "retryAfter": 42
}

Exports

From @kyrasource/rate-limit you can import:

  • RateLimitModule
  • RateLimitGuard
  • RateLimitService
  • RateLimit (decorator)
  • RATE_LIMIT_STORE (DI token)
  • InMemoryRateLimitStore and RateLimitStore (for custom adapters)
  • Types: RateLimitOptions, RateLimitResult, RateLimitState

Notes

  • The in-memory store is great for single-instance setups and tests. For distributed/clustered deployments, use a shared store (e.g., Redis) by overriding RATE_LIMIT_STORE.
  • Keys are built as: ${keyPrefix}:${method}:${normalizedPath}:${ip} where normalizedPath strips query strings.

Build

cd packages/rate-limit-lib
npm run build

This compiles to dist/ with type declarations.