DEV Community

Cover image for gin-rate-limit: Configurable Rate Limiting Middleware for Gin
Zanyar Jalal
Zanyar Jalal

Posted on

gin-rate-limit: Configurable Rate Limiting Middleware for Gin

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:path patterns, 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")
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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 ResponseWriterFunc if you want full control over the body/status instead of the default JSON message.

Install

go get github.com/zanyar3/gin-rate-limit
Enter fullscreen mode Exit fullscreen mode

MIT licensed. Repo, full docs, and config file schema here: https://github.com/zanyar3/gin-rate-limit

Top comments (1)

Collapse
 
arpan_singh_121 profile image
Arpan Singh •

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).