Documentation Index
Fetch the complete documentation index at: https://docs.go-mizu.dev/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The ratelimit middleware implements token bucket rate limiting to control request rates. It protects your application from abuse, ensures fair usage, and prevents resource exhaustion.
Use it when you need:
- API rate limiting
- Protection against brute force attacks
- Fair usage enforcement
- Resource protection
Installation
import "github.com/go-mizu/mizu/middlewares/ratelimit"
Quick Start
app := mizu.New()
// 100 requests per minute
app.Use(ratelimit.PerMinute(100))
Configuration
Options
| Option | Type | Default | Description |
|---|
Rate | int | 100 | Requests allowed per interval |
Interval | time.Duration | 1m | Time window |
Burst | int | Same as Rate | Maximum burst capacity |
KeyFunc | func(*mizu.Ctx) string | Client IP | Rate limit key extractor |
Headers | bool | true | Include rate limit headers |
ErrorHandler | func(*mizu.Ctx) error | - | Custom error handler |
Skip | func(*mizu.Ctx) bool | - | Skip rate limiting |
Examples
Basic Rate Limiting
// 100 requests per minute per IP
app.Use(ratelimit.PerMinute(100))
// 10 requests per second
app.Use(ratelimit.PerSecond(10))
// 1000 requests per hour
app.Use(ratelimit.PerHour(1000))
Custom Rate and Interval
// 50 requests per 30 seconds
app.Use(ratelimit.New(50, 30*time.Second))
With Burst
// Allow burst of 200, refill at 100/minute
app.Use(ratelimit.WithOptions(ratelimit.Options{
Rate: 100,
Interval: time.Minute,
Burst: 200,
}))
Rate Limit by API Key
app.Use(ratelimit.WithOptions(ratelimit.Options{
Rate: 1000,
Interval: time.Hour,
KeyFunc: func(c *mizu.Ctx) string {
// Use API key instead of IP
return c.Request().Header.Get("X-API-Key")
},
}))
Rate Limit by User
app.Use(ratelimit.WithOptions(ratelimit.Options{
Rate: 100,
Interval: time.Minute,
KeyFunc: func(c *mizu.Ctx) string {
if user := GetUser(c); user != nil {
return user.ID
}
return c.ClientIP() // Fallback to IP
},
}))
Skip Certain Requests
app.Use(ratelimit.WithOptions(ratelimit.Options{
Rate: 100,
Interval: time.Minute,
Skip: func(c *mizu.Ctx) bool {
// Skip health checks
if c.Request().URL.Path == "/health" {
return true
}
// Skip authenticated admins
if user := GetUser(c); user != nil && user.IsAdmin {
return true
}
return false
},
}))
Custom Error Handler
app.Use(ratelimit.WithOptions(ratelimit.Options{
Rate: 100,
Interval: time.Minute,
ErrorHandler: func(c *mizu.Ctx) error {
return c.JSON(429, map[string]any{
"error": "Rate limit exceeded",
"retry_after": c.Header().Get("Retry-After"),
})
},
}))
app.Use(ratelimit.WithOptions(ratelimit.Options{
Rate: 100,
Interval: time.Minute,
Headers: false, // Don't include rate limit headers
}))
Different Limits for Different Routes
// Global: 100 requests/minute
app.Use(ratelimit.PerMinute(100))
// API: More restrictive
api := app.Group("/api")
api.Use(ratelimit.PerMinute(60))
// Expensive operations: Very restrictive
app.Post("/api/export", exportHandler, ratelimit.PerMinute(5))
Tiered Rate Limits
func tierRateLimit() mizu.Middleware {
free := ratelimit.WithOptions(ratelimit.Options{
Rate: 100, Interval: time.Hour,
KeyFunc: func(c *mizu.Ctx) string { return "free:" + c.ClientIP() },
})
pro := ratelimit.WithOptions(ratelimit.Options{
Rate: 10000, Interval: time.Hour,
KeyFunc: func(c *mizu.Ctx) string { return "pro:" + GetUser(c).ID },
})
return func(next mizu.Handler) mizu.Handler {
return func(c *mizu.Ctx) error {
user := GetUser(c)
if user != nil && user.Plan == "pro" {
return pro(next)(c)
}
return free(next)(c)
}
}
}
Custom Store
// Implement Store interface for Redis, etc.
type redisStore struct {
client *redis.Client
}
func (s *redisStore) Allow(key string, rate int, interval time.Duration, burst int) (bool, ratelimit.RateLimitInfo) {
// Redis implementation
}
app.Use(ratelimit.WithStore(&redisStore{client}, ratelimit.Options{
Rate: 100,
Interval: time.Minute,
}))
When Headers is enabled (default), these headers are included:
| Header | Description |
|---|
X-RateLimit-Limit | Maximum requests allowed |
X-RateLimit-Remaining | Remaining requests in window |
X-RateLimit-Reset | Unix timestamp when limit resets |
Retry-After | Seconds until retry (when limited) |
API Reference
Functions
// Convenience constructors
func PerSecond(n int) mizu.Middleware
func PerMinute(n int) mizu.Middleware
func PerHour(n int) mizu.Middleware
// Custom rate and interval
func New(rate int, interval time.Duration) mizu.Middleware
// Full configuration
func WithOptions(opts Options) mizu.Middleware
// Custom store
func WithStore(store Store, opts Options) mizu.Middleware
Store Interface
type Store interface {
Allow(key string, rate int, interval time.Duration, burst int) (bool, RateLimitInfo)
}
RateLimitInfo
type RateLimitInfo struct {
Limit int
Remaining int
Reset time.Time
}
Token Bucket Algorithm
The middleware uses the token bucket algorithm:
- Bucket starts full with
Burst tokens
- Requests consume one token
- Tokens refill at
Rate per Interval
- Burst allows temporary spikes
Bucket: [ββββββββββββ] 100 tokens
Request β [βββββββββββ ] 99 tokens
Request β [ββββββββββ ] 98 tokens
...
Refill β [ββββββββββββ] 100 tokens (after interval)
Technical Details
The rate limit middleware implements the token bucket algorithm, a sophisticated approach for managing request rates with support for burst traffic. The implementation details are as follows:
Token Bucket Implementation
The algorithm maintains a bucket of tokens for each rate limit key:
- Initialization: Each bucket starts with
Burst tokens (default equals Rate)
- Token Consumption: Each request consumes exactly one token from the bucket
- Token Refill: Tokens are added continuously based on elapsed time:
- Refill rate:
Rate / Interval tokens per second
- Formula:
tokensToAdd = (Rate * elapsedSeconds) / intervalSeconds
- Capacity Limit: Bucket capacity is capped at
Burst tokens to prevent unlimited accumulation
- Request Decision: Request is allowed if bucket has at least 1 token available
In-Memory Store
The default MemoryStore provides:
- Thread Safety: Uses
sync.Mutex for concurrent access protection
- Per-Key Tracking: Maintains separate buckets for each key (IP, user ID, API key, etc.)
- Automatic Cleanup: Background goroutine removes stale buckets after 10 minutes of inactivity
- Fractional Tokens: Uses
float64 for precise token calculations during refills
For each request, the middleware calculates and returns:
- Limit: Maximum requests allowed per interval (from
Rate option)
- Remaining: Current token count (floored to nearest integer)
- Reset: Timestamp when bucket will be full again (current time +
Interval)
Best Practices
- Set reasonable limits based on expected usage
- Use different limits for different endpoints
- Include rate limit headers for client awareness
- Monitor rate limit hits for tuning
- Consider user tiers for fair access
Testing
The rate limit middleware includes comprehensive test coverage for all functionality:
| Test Case | Description | Expected Behavior |
|---|
TestNew | Basic rate limiting with IP-based keys | First N requests succeed, subsequent requests return 429; different IPs tracked separately |
TestWithOptions_Headers | Rate limit headers in response | Headers include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset |
TestWithOptions_ErrorHandler | Custom error handler for rate limit exceeded | Custom error handler called instead of default when limit exceeded |
TestWithOptions_KeyFunc | Custom key extraction function | Rate limits applied per custom key (e.g., API key) instead of IP |
TestWithOptions_Skip | Skip rate limiting for certain requests | Requests matching skip condition bypass rate limiting |
TestWithOptions_RetryAfter | Retry-After header on rate limit | Retry-After header included when request is rate limited |
TestPerSecond | PerSecond convenience function | Middleware created with per-second interval |
TestPerMinute | PerMinute convenience function | Middleware created with per-minute interval |
TestPerHour | PerHour convenience function | Middleware created with per-hour interval |
TestMemoryStore_Allow | Token bucket allows/denies requests | Allows up to limit, denies when bucket empty |
TestMemoryStore_TokenRefill | Token bucket refills over time | Tokens refill after interval allowing new requests |
TestMemoryStore_DifferentKeys | Different keys have independent buckets | Exhausting one key doesnβt affect other keys |