Skip to main content
Glama
rate-limiting.service.ts4.09 kB
import { Injectable, Inject } from '@nestjs/common'; import Redis from 'ioredis'; export interface RateLimitResult { allowed: boolean; remaining: number; resetAt: Date; limit: number; } @Injectable() export class RateLimitingService { constructor(@Inject('REDIS_CLIENT') private readonly redis: Redis) {} /** * Check and increment rate limit counters for a given key * Uses Redis MULTI transaction for atomic operation */ async checkRateLimit( key: string, limitRpm: number, limitRpd: number, ): Promise<RateLimitResult> { const now = new Date(); const minuteKey = this.getMinuteKey(key, now); const dayKey = this.getDayKey(key, now); // Use Redis pipeline for atomic operations const pipeline = this.redis.pipeline(); // Check and increment minute counter (60 second TTL) pipeline.incr(minuteKey); pipeline.expire(minuteKey, 60); // Check and increment day counter (86400 second TTL) pipeline.incr(dayKey); pipeline.expire(dayKey, 86400); const results = await pipeline.exec(); if (!results) { return { allowed: true, remaining: limitRpm, resetAt: new Date(), limit: limitRpm, }; } const minuteCount = (results[0]?.[1] as number) || 0; const dayCount = (results[2]?.[1] as number) || 0; // Determine which limit was hit (if any) const minuteExceeded = minuteCount > limitRpm; const dayExceeded = dayCount > limitRpd; const allowed = !minuteExceeded && !dayExceeded; // Calculate reset times const minuteResetAt = new Date(now.getTime() + 60000); const dayResetAt = new Date(now.getTime() + 86400000); // Use the stricter limit for remaining calculation const remainingMinute = Math.max(0, limitRpm - minuteCount); const remainingDay = Math.max(0, limitRpd - dayCount); const remaining = Math.min(remainingMinute, remainingDay); const resetAt = minuteExceeded ? minuteResetAt : dayResetAt; return { allowed, remaining, resetAt, limit: minuteExceeded ? limitRpm : limitRpd, }; } /** * Get current rate limit status without incrementing */ async getRateLimitStatus( key: string, limitRpm: number, limitRpd: number, ): Promise<RateLimitResult> { const now = new Date(); const minuteKey = this.getMinuteKey(key, now); const dayKey = this.getDayKey(key, now); const [minuteCount, dayCount] = await Promise.all([ this.redis.get(minuteKey), this.redis.get(dayKey), ]); const minuteVal = parseInt(minuteCount || '0', 10); const dayVal = parseInt(dayCount || '0', 10); const minuteExceeded = minuteVal > limitRpm; const dayExceeded = dayVal > limitRpd; const allowed = !minuteExceeded && !dayExceeded; const minuteResetAt = new Date(now.getTime() + 60000); const dayResetAt = new Date(now.getTime() + 86400000); const remainingMinute = Math.max(0, limitRpm - minuteVal); const remainingDay = Math.max(0, limitRpd - dayVal); const remaining = Math.min(remainingMinute, remainingDay); const resetAt = minuteExceeded ? minuteResetAt : dayResetAt; return { allowed, remaining, resetAt, limit: minuteExceeded ? limitRpm : limitRpd, }; } /** * Reset rate limit counters for a given key */ async resetRateLimit(key: string): Promise<void> { const now = new Date(); const minuteKey = this.getMinuteKey(key, now); const dayKey = this.getDayKey(key, now); await Promise.all([this.redis.del(minuteKey), this.redis.del(dayKey)]); } /** * Generate minute-based key with current minute timestamp */ private getMinuteKey(key: string, date: Date): string { const minute = Math.floor(date.getTime() / 60000); return `ratelimit:minute:${key}:${minute}`; } /** * Generate day-based key with current day timestamp */ private getDayKey(key: string, date: Date): string { const day = Math.floor(date.getTime() / 86400000); return `ratelimit:day:${key}:${day}`; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aiatamai/atamai-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server