Rate limiting in Gin usually means hand-rolling counters per middleware, with no clean way to define general quotas, per-endpoint overrides, multiple time windows, or whitelists declaratively.
I built gin-rate-limit to solve that.
What it does
- IP or client-ID based limiting — count by client IP (with trusted-proxy support) or by an API-key/header
-
General + per-endpoint rules — set a baseline quota, then override it for specific
verb:pathpatterns, including wildcards (get:/api/orders/*) - Multiple time windows per rule — e.g. 10 req/sec and 1000 req/day on the same endpoint
- Whitelisting — bypass by IP, CIDR, client ID, or endpoint
- Pluggable storage — in-memory by default (zero deps), or Redis with atomic Lua-based increment+expiry for multi-instance deployments
-
Standard headers —
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset,Retry-After - Config via code or file — functional options, or load rules from YAML/JSON
Quick example
limiter, err := ginratelimit.New(
ginratelimit.WithGeneralRules(
ginratelimit.Rule{Endpoint: "*", Period: "1s", Limit: 5},
),
)
if err != nil {
panic(err)
}
defer limiter.Close()
r := gin.Default()
r.Use(limiter.Middleware())
r.GET("/ping", func(c *gin.Context) { c.String(200, "pong") })
r.Run(":8080")
A more realistic case: OTP throttling
One pattern that doesn't map cleanly to simple IP limiting is throttling "send OTP" or "send verification email" endpoints — you want to limit by the recipient (email/phone in the body), not the caller's IP, otherwise one account can be hammered from many IPs. A small middleware lifts the recipient into a header that a dedicated limiter keys on:
otpLimiter, _ := ginratelimit.New(
ginratelimit.WithStrategy(ginratelimit.StrategyClientID),
ginratelimit.WithClientIDHeader("X-OTP-Recipient"),
ginratelimit.WithGeneralRules(
ginratelimit.Rule{Endpoint: "*", Period: "10m", Limit: 3},
ginratelimit.Rule{Endpoint: "*", Period: "1d", Limit: 10},
),
)
otp := router.Group("/api/otp")
otp.Use(otpKey(), otpLimiter.Middleware())
otp.POST("/send", sendOTPHandler)
The full example, plus login brute-force protection and tiered public-API limits, is in the README.
Design notes
- Counters use a fixed window per period, concurrency-safe via mutex (memory) or a Lua script (Redis).
- The middleware fails open — if the store errors (e.g. Redis is down), requests are allowed rather than blocking your API on a storage outage.
- Custom 429 responses are supported via a
ResponseWriterFuncif you want full control over the body/status instead of the default JSON message.
Install
go get github.com/zanyar3/gin-rate-limit
MIT licensed. Repo, full docs, and config file schema here: https://github.com/zanyar3/gin-rate-limit