Appearance
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'
})| Option | Type | Description |
|---|---|---|
windowMs | string | Sliding window duration. Supported formats: 500ms, 30s, 5m. |
limit | number | Maximum 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:
| Format | Examples | Equivalence |
|---|---|---|
| Milliseconds | 500ms, 1500ms | Raw milliseconds |
| Seconds | 30s, 60s | Multiply by 1000 |
| Minutes | 5m, 15m | Multiply by 60,000 |
Algorithm
The guard executes a single atomic Lua script on each request:
- Prune:
ZREMRANGEBYSCOREremoves expired entries from the sorted set. - Count:
ZCARDchecks the number of requests in the current window. - Allow: If under the limit,
ZADDrecords the request andPEXPIREsets TTL. - Deny: If at/over the limit, computes
retryAfterMsfrom the oldest entry.
Redis key format: rate_limit:{Controller}:{handler}:{identifier}
Identifier Resolution
'userId': Usesrequest.user.suborrequest.user.id, falls back torequest.ipif no user context.'ip': Always usesrequest.ip.
Reverse Proxy Trust (Traefik/Dokploy)
- The API enables Express proxy trust with one hop (
trust proxy = 1) in bootstrap. - This ensures
request.ipis resolved from the trusted Traefik hop instead of client-suppliedX-Forwarded-Forheaders. - 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:
RateLimitGuard— Redis-backed per-endpoint rate limitingJwtAuthGuard— JWT authenticationAuthTypeGuard— 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)
| Endpoint | Limit | Window | Identifier |
|---|---|---|---|
POST /api/identity/auth/user/check-email | 3 | 1 min | IP |
POST /api/identity/auth/user/register | 5 | 15 min | IP |
POST /api/identity/auth/user/verify-email | 5 | 1 min | IP |
POST /api/identity/auth/user/login | 10 | 15 min | IP |
GET /api/identity/auth/user/google/callback | 10 | 1 min | IP |
POST /api/identity/auth/user/change-password | 3 | 15 min | userId |
Client Auth (/auth/client)
| Endpoint | Limit | Window | Identifier |
|---|---|---|---|
POST /api/identity/auth/client/register | 5 | 15 min | IP |
POST /api/identity/auth/client/verify-email | 5 | 1 min | IP |
POST /api/identity/auth/client/login | 10 | 15 min | IP |
GET /api/identity/auth/client/google/callback | 10 | 1 min | IP |
POST /api/identity/auth/client/change-password | 3 | 15 min | userId |
Shared Auth (/auth)
| Endpoint | Limit | Window | Identifier |
|---|---|---|---|
POST /api/identity/auth/refresh | 10 | 1 min | IP |
Notifications SMS Test
| Endpoint | Limit | Window | Identifier |
|---|---|---|---|
POST /api/notifications/sms/test | 3 | 5 min | IP |
The SMS test endpoint only exists in development; in any other environment it returns 404 Not Found even before Twilio delivery is attempted.