Skip to content

Per-Endpoint Rate Limiting

DaraMex uses a custom Redis-backed sliding window algorithm for per-endpoint rate limiting. This replaces the previous @nestjs/throttler approach with a more granular, Redis-native solution.

@RateLimit Decorator

typescript
@RateLimit({
  windowMs: '60s',           // 60 seconds (see time formats below)
  limit: 5,                  // max 5 requests
  identifierType: 'ip',      // or 'userId'
})
OptionTypeDescription
windowMsstringSliding window duration. Supported formats: 500ms, 30s, 5m.
limitnumberMaximum requests allowed within the window.
identifierType'userId' | 'ip'How to identify the requester.

Time Format for windowMs

Use human-readable time strings instead of calculating milliseconds:

FormatExamplesEquivalence
Milliseconds500ms, 1500msRaw milliseconds
Seconds30s, 60sMultiply by 1000
Minutes5m, 15mMultiply by 60,000

Algorithm

The guard executes a single atomic Lua script on each request:

  1. Prune: ZREMRANGEBYSCORE removes expired entries from the sorted set.
  2. Count: ZCARD checks the number of requests in the current window.
  3. Allow: If under the limit, ZADD records the request and PEXPIRE sets TTL.
  4. Deny: If at/over the limit, computes retryAfterMs from the oldest entry.

Redis key format: rate_limit:{Controller}:{handler}:{identifier}

Identifier Resolution

  • 'userId': Uses request.user.sub or request.user.id, falls back to request.ip if no user context.
  • 'ip': Always uses request.ip.

Reverse Proxy Trust (Traefik/Dokploy)

  • The API enables Express proxy trust with one hop (trust proxy = 1) in bootstrap.
  • This ensures request.ip is resolved from the trusted Traefik hop instead of client-supplied X-Forwarded-For headers.
  • If proxy topology changes, update this trust level to match the real hop count.

Guard Execution Order

The RateLimitGuard is registered as an APP_GUARD in CoreModule and runs before authentication:

  1. RateLimitGuard — Redis-backed per-endpoint rate limiting
  2. JwtAuthGuard — JWT authentication
  3. AuthTypeGuard — Authorization (user/client type check)

Endpoints without @RateLimit() are not rate-limited (the guard skips them).

Response on Denial

json
{
  "statusCode": 429,
  "errorCode": "CORE.RATE_LIMIT_EXCEEDED",
  "message": {
    "en": "Too many requests, please try again later",
    "es": "Demasiadas solicitudes, por favor intenta de nuevo mas tarde"
  },
  "metadata": {
    "retryAfterSeconds": 42
  }
}

The Retry-After HTTP header is also set (in seconds) and mirrors metadata.retryAfterSeconds.

Rate Limits by Endpoint

User Auth (/auth/user)

EndpointLimitWindowIdentifier
POST /api/identity/auth/user/check-email31 minIP
POST /api/identity/auth/user/register515 minIP
POST /api/identity/auth/user/verify-email51 minIP
POST /api/identity/auth/user/login1015 minIP
GET /api/identity/auth/user/google/callback101 minIP
POST /api/identity/auth/user/change-password315 minuserId

Client Auth (/auth/client)

EndpointLimitWindowIdentifier
POST /api/identity/auth/client/register515 minIP
POST /api/identity/auth/client/verify-email51 minIP
POST /api/identity/auth/client/login1015 minIP
GET /api/identity/auth/client/google/callback101 minIP
POST /api/identity/auth/client/change-password315 minuserId

Shared Auth (/auth)

EndpointLimitWindowIdentifier
POST /api/identity/auth/refresh101 minIP

Notifications SMS Test

EndpointLimitWindowIdentifier
POST /api/notifications/sms/test35 minIP

The SMS test endpoint only exists in development; in any other environment it returns 404 Not Found even before Twilio delivery is attempted.