Skip to content

Rate Limiting

RateLimiterMiddleware provides request rate limiting based on client IP address with configurable windows, burst allowance, and rate limit headers.

// Default: 60 requests per minute
server.use(RateLimiterMiddleware())
import scala.concurrent.duration._
server.use(RateLimiterMiddleware(RateLimiterMiddleware.Options(
limit = RateLimiterMiddleware.RateLimit(
maxRequests = 100,
window = 1.minute,
burst = 20,
skipFailedRequests = false,
skipSuccessfulRequests = false,
statusCode = 429,
errorMessage = "Too many requests. Please try again later.",
headers = true,
),
keyGenerator = request => Future.successful(request.ip),
skip = request => request.path.startsWith("/health"),
)))
OptionTypeDefaultDescription
maxRequestsInt60Max requests per window
windowDuration1.minuteTime window
burstInt0Extra requests allowed above max
skipFailedRequestsBooleanfalseDon’t count failed requests
skipSuccessfulRequestsBooleanfalseDon’t count successful requests
statusCodeInt429Status code when limited
errorMessageString"Too many requests..."Error message
headersBooleantrueSend rate limit headers
OptionTypeDefaultDescription
storeRateLimitStoreInMemoryStoreStorage backend
keyGeneratorRequest => Future[String]By IPKey generation function
skipRequest => Boolean_ => falseSkip rate limiting
onRateLimitedFunctionDefault handlerCustom response on limit
ipSourcesSeq[IpSource.Value]Forward, Real, DirectIP detection order
import scala.concurrent.duration._
// Moderate: specified max, with burst allowance
server.use(RateLimiterMiddleware.moderate(100, 1.minute))
// Strict: specified max, no burst
server.use(RateLimiterMiddleware.strict(50, 1.minute))
// Flexible: specified max, with generous burst
server.use(RateLimiterMiddleware.flexible(200, 1.minute))

When headers = true, responses include:

HeaderDescription
X-RateLimit-LimitMaximum requests allowed
X-RateLimit-RemainingRequests remaining in window
X-RateLimit-ResetSeconds until window resets
Retry-AfterSeconds to wait (only on 429)

The ipSources option controls how client IPs are detected:

SourceHeader/Property
IpSource.ForwardX-Forwarded-For
IpSource.RealX-Real-IP
IpSource.CloudflareCF-Connecting-IP
IpSource.DirectSocket remote address

Rate limit by something other than IP:

// Rate limit by API key
val options = RateLimiterMiddleware.Options(
limit = RateLimiterMiddleware.RateLimit(100, 1.minute),
keyGenerator = request =>
Future.successful(request.header("x-api-key").getOrElse(request.ip)),
)

Implement RateLimitStore for a persistent backend (e.g., Redis):

trait RateLimitStore {
def increment(key: String, window: Duration): Future[(Int, Int)]
def reset(key: String): Future[Unit]
def cleanup(): Future[Unit]
}

The built-in InMemoryStore runs cleanup every 24 hours.