Rate limiting
Optional per-IP rate limiting for the BFF in the SHOPin storefront accelerator. It is off unless you set BFF_RATE_LIMIT_ENABLED=true; it does not run in the presentation app.
For how 429 fits the global error shape and filters, see Error handling.
Configuration
Only the literal string true for BFF_RATE_LIMIT_ENABLED enables throttling. Any other value or an unset variable leaves limits disabled (the guard does not apply caps even if TTL/limit variables are set).
When enabled, set:
| Variable | Meaning |
|---|---|
BFF_RATE_LIMIT_TTL | Default window (milliseconds) |
BFF_RATE_LIMIT_LIMIT | Max requests per IP in that window |
BFF_RATE_LIMIT_SECURE_TTL | Secure window (milliseconds) |
BFF_RATE_LIMIT_SECURE_LIMIT | Max requests per IP in that window on @SecureRateLimit() routes |
Default throttler — Applies to all routes while limiting is on (first two variables).
Secure throttler — Applies only on handlers with @SecureRateLimit() (last two variables). On those routes the client must stay under both limits.
In the reference repo, @SecureRateLimit() is on: the whole auth.controller.ts, POST create order, PUT change customer password, and mutating routes in payment.controller.ts (PUT set method, POST initiate — not GET methods).
OptionalThrottlerGuard is the app guard in app.module.ts; options are built in throttler.config.ts.
Starting points (not prescriptions): e.g. 60000 ms / 100–200 per IP for default, 60000 ms / ~5 per IP for secure. Tune for traffic and how client IP is seen behind proxies.
Multiple BFF replicas
The throttler stores counts per BFF process. With more than one replica, the env limits are not a single global budget per client unless traffic is pinned to one instance—turn BFF_RATE_LIMIT_ENABLED off and enforce limits outside the BFF (gateway, proxy, CDN/WAF, or another layer with fleet-wide or shared state). Using the built-in throttler alone in that setup is misleading unless you add shared storage yourself (outside the default accelerator).
429 in the presentation app
The BFF returns 429 with the usual JSON error body; logging follows other client errors (see Error handling).
In the presentation app, use HttpError.getStatusCode(error) === 429 or HttpError.isTooManyRequestsError(error) from error-utils.ts, or handle RateLimitError where the BFF client throws it. Prefer a clear “try again later” message and avoid tight retries or repeated submits that amplify load.
import { HttpError } from '@/lib/error-utils'
export function rateLimitMessage(error: unknown): string | null {
if (HttpError.isTooManyRequestsError(error)) {
return 'Too many attempts. Try again in a moment.'
}
return null
}
For a full form flow (loading state, field errors, and 429 copy), follow existing auth forms (e.g. auth-login-form.tsx) rather than only this stub.
Related
- Error handling
- BFF
- Security measures (auth)
- Deploy project apps — deploy-time notes for
BFF_RATE_LIMIT_*variables
Back to Validation & resilience · Back to How to work with SHOPin