Skip to content

Rate Limit

Contract

@modularityjs/rate-limit defines the abstract RateLimiterService:

typescript
interface RateLimitResult {
  readonly allowed: boolean;
  readonly remaining: number;
  readonly total: number;
  readonly resetAt: number; // Unix timestamp ms
}

abstract class RateLimiterService {
  abstract consume(key: string, points?: number): Promise<RateLimitResult>;
  abstract get(key: string): Promise<RateLimitResult>;
  abstract reset(key: string): Promise<void>;
}
  • consume(key, points) — attempt to consume points (default 1). Returns allowed: false if limit exceeded.
  • get(key) — check current state without consuming.
  • reset(key) — clear the counter for a key.

Drivers

Memory (@modularityjs/rate-limit-memory)

In-memory fixed-window rate limiter. For development and single-instance deployments.

typescript
import { RateLimitModule } from '@modularityjs/rate-limit';
import { RateLimitMemoryModule } from '@modularityjs/rate-limit-memory';

const modules = [
  RateLimitModule,
  RateLimitMemoryModule.forRoot({ limit: 100, windowMs: 60_000 }),
];
OptionDefaultDescription
limit100Maximum points per window
windowMs60000Window duration in milliseconds
maxEntries10000Cap on tracked keys; once reached, consume returns allowed: false for new keys until older entries expire or evict
evictionIntervalMsOptional periodic sweep that removes expired entries; unset means entries are only purged lazily on access

Redis (@modularityjs/rate-limit-redis)

Redis-backed rate limiter. consume runs a Lua script combining INCRBY + PEXPIRE + PTTL (PEXPIRE only on the first hit of a window; PTTL computes resetAt); get runs a separate Lua script that combines GET + PTTL. For distributed systems where multiple instances share limits.

typescript
import { RateLimitModule } from '@modularityjs/rate-limit';
import { RedisModule } from '@modularityjs/redis';
import { RateLimitRedisModule } from '@modularityjs/rate-limit-redis';

const modules = [
  RedisModule.forRoot({ host: 'localhost', port: 6379 }),
  RateLimitModule,
  RateLimitRedisModule.forRoot({ limit: 100, windowMs: 60_000 }),
];
OptionDefaultDescription
limit100Maximum points per window
windowMs60000Window duration in milliseconds
keyNamespace'rl:'Prefix for Redis keys

HTTP Bridge (@modularityjs/http-rate-limit)

Automatically rate-limits all HTTP requests via HttpServer.onRequest() hook. Throws RateLimitExceededException (HTTP 429) when the limit is exceeded.

The extension is fail-open: if the underlying RateLimiterService.consume() throws (e.g. Redis is unreachable), the error is reported via process.emitWarning and the request proceeds without being rate-limited.

typescript
import { HttpRateLimitModule } from '@modularityjs/http-rate-limit';

const modules = [
  RateLimitModule,
  RateLimitMemoryModule.forRoot({ limit: 100, windowMs: 60_000 }),
  HttpRateLimitModule,
];

By default, the rate limit key is extracted from the x-forwarded-for header (falling back to 'unknown'). Customize with forRoot():

typescript
import { getRequestAuth } from '@modularityjs/http-auth';

HttpRateLimitModule.forRoot({
  keyExtractor: (request) => {
    const identity = getRequestAuth(request);
    return identity
      ? `user:${identity.id}`
      : ((request.headers['x-forwarded-for'] as string) ?? 'anon');
  },
});

Programmatic Usage

Inject RateLimiterService directly for non-HTTP rate limiting:

typescript
@Injectable()
class LoginService {
  constructor(
    @Inject(RateLimiterService) private readonly limiter: RateLimiterService,
  ) {}

  async login(username: string, password: string) {
    const result = await this.limiter.consume(`login:${username}`);
    if (!result.allowed) {
      throw new RateLimitExceededException(result.resetAt);
    }
    // proceed with authentication
  }
}

Use cases beyond HTTP:

  • Login attempt throttling (per username)
  • Queue message processing rate control
  • External API call budgeting
  • Scheduled job frequency limiting