# Ratelimiting using Redis

## What is Rate limiting and why do we need it?

Rate limiting is a technique used to control the number of requests a client can make to a server within a specified time frame. It helps prevent abuse, ensures fair resource distribution, and maintains system stability.

![](https://media3.giphy.com/media/v1.Y2lkPTc5MGI3NjExb29qNDBjcGx5d3RrMzVscDRvNjlyaXBuNmlpaHQ3azBkczAyZWs3YyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/UF64wV0ebjs2Y/giphy.gif align="center")

1. **Prevents API abuse:** Stops users and bots to from overwhelming the server with excessive requests.
    
2. **Protects from attacks:** Enhances security to protect from brute force attacks and Denial of Service (DoS) attacks.
    
3. **Controls costs:** Helps in avoiding excessive costs on resource consumption, reducing operational costs.
    

## Pre-requisites

This is a very simple implementation of rate limiting, which can be improved upon a lot. But this can save your side projects to be heavily bombarded from excessive API calls.

Things you need to implement this:

* Redis up and running (locally using docker or using any cloud service like [upstash](https://upstash.com/))
    
* Golang and Gin (because Go’s default `net/http` is like building IKEA furniture without instructions)
    

## Let’s implement

1. ### Install dependencies
    
    These are the dependencies we need for our ratelimiting middleware to work.
    

```bash
go get github.com/gin-gonic/gin
go get github.com/go-redis/redis/v8
```

2. ### Connect to Redis 🏓
    
    Let’s code to connect to redis.
    
    This code is to connect with redis when you have running it locally using docker or have a redis server on your PC.
    

```go
func ConnectRedis() error {
    redisClient = redis.NewClient(&redis.Options{
        Addr:     "localhost:6379", // Redis' home address
        Password: "",               // No password? You can set your own password
        DB:       0,                // The VIP database
    })

    // Send a "PING" to check if Redis is awake
    pong, err := redisClient.Ping(ctx).Result()
    if err != nil {
        log.Fatalf("Redis connection failed harder than my diet on Thanksgiving: %v", err)
    }
    fmt.Println("Redis says:", pong) // "PONG!" means you're in business!
    return nil
}
```

But what if you have it up in the cloud using some service provider like upstash. Then change the fields `Addr` and `Password` to the ones you have from your provider, and add [TLS](https://www.cloudflare.com/learning/ssl/transport-layer-security-tls/) config (it’s necessary)  
Then the function for connecting to redis will look like this.  

```go
func ConnectRedis() error {
    redisClient = redis.NewClient(&redis.Options{
        Addr:     "your-upstash-endpoint:6379", // Replace with your Upstash Redis endpoint
        Password: "your-upstash-password",     // Replace with your Upstash Redis password
        DB:       0,                           // Default DB
        TLSConfig: &tls.Config{},              // Enable TLS (important for Upstash)
    })

    // Check if Redis connection is successful
    pong, err := redisClient.Ping(ctx).Result()
    if err != nil {
        log.Fatalf("Failed to connect to Upstash Redis: %v", err)
    }
    fmt.Println("Connected to Upstash Redis:", pong)
    return nil
}
```

3. ### The Rate Limiter Middleware
    
    The rules?
    
    5 requests per IP per minute
    
    More than that, you’ll get a 429 (Too many requests) error.
    

```go
func RateLimiter() gin.HandlerFunc {
    return func(c *gin.Context) {
        ip := c.ClientIP() // Get that IP! (No cheating with proxies, okay?)
        key := fmt.Sprintf("rate_limit:%s", ip) // Redis key. Example: "rate_limit:127.0.0.1"
        limit := 5                              // 5 requests...
        window := 60 * time.Second              // ...per 60 seconds. Play nice!

        // Increment the request counter for this IP
        count, err := redisClient.Incr(ctx, key).Result()
        if err != nil {
            log.Printf("Redis threw a tantrum: %v", err)
            c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"message": "BRB, server's crying"})
            return
        }

        // If this is the first request, set the "self-destruct" timer
        if count == 1 {
            if _, err := redisClient.Expire(ctx, key, window).Result(); err != nil {
                log.Printf("Redis tried to ghost us: %v", err)
                c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"message": "Server's on strike"})
                return
            }
        }

        // Enforce the limit
        if count > int64(limit) {
            c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
                "message": "Slow your roll, cowboy! 🐄 Try again later.",
            })
            return
        }

        c.Next() // All good—proceed to the party!
    }
}
```

**How this works?**

* `Incr` Increments the request count for the IP
    
* `Expire` Set’s a 60 seconds TTL on the key only on the first request (so it auto deletes itself).
    
* Limit check: If the count exceeds 5, deny entry for the requests.
    

4. ### Add the middleware to your Gin router
    
    Add the middleware onto your routes and protect them from getting overwhelmed.
    

```go
func main() {
    if err := ConnectRedis(); err != nil {
        log.Fatal("Redis connection: Pong")
    }

    router := gin.Default()
    router.Use(RateLimiter()) // Apply to all routes

    router.GET("/api/secret-sauce", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"message": "🔥 Secret sauce: 3 parts caffeine, 2 parts chaos"})
    })

    router.Run(":8080")
}
```

You can start your server using `go run main`.

Now, hit `/api/secret-sauce` more than 5 times in a minute, and you’ll get a sassy `429` error. Test it with `curl -I http://localhost:8080/api/secret-sauce` and watch the headers fly!  

## Gotcha and Pro tips

1. **IP-based limits aren’t perfect**: In a shared network (like Starbucks WiFi), you might block innocent bystanders. For critical apps, use API keys or auth tokens.
    
2. **Adjust the limits**: 5 requests/minute too strict? Crank it up! (But not so high that your server melts.)
    
3. **Upgrade to sliding windows**: This example uses a fixed window. For smoother limits, try Redis’s [`ZSET`](https://redis.io/docs/latest/develop/data-types/sorted-sets/) with a sliding window algorithm.
    

## Conclusion

Rate limiting is a technique to control the number of requests a client can make to a server in a given timeframe, helping prevent abuse, ensure fair resource distribution, and maintain system stability. This article covers a simple implementation using Redis, Golang, and Gin, allowing 5 requests per IP per minute. The guide includes setup instructions, connection details for local and cloud-based Redis, and middleware implementation to protect your API routes. Adjusting limits and considering advanced techniques like sliding windows are also discussed for better control and efficiency.

![](https://media3.giphy.com/media/v1.Y2lkPTc5MGI3NjExZzh5a3N4aTVjdzNvbGo2cTRjN3FjeDFqZTlwZm90azZya3ZhYzdldyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/l4KibK3JwaVo0CjDO/giphy.gif align="center")

Bye !
