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
Top comments (1)
Your walkthrough of building a configurable rate‑limiting middleware for Gin makes it so easy to avoid hand‑rolled counters and plug it straight into any project. If you ever want to reach an even broader dev audience, consider syndicating the post on ZyVOP (zyvop.com).