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

go dev.to

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 headersX-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

Source: dev.to

arrow_back Back to Tutorials