Redis is one of those tools that shows up in almost every production stack I've worked with. Caching, session storage, rate limiting, pub/sub. It does a lot of things well and it's fast. Pairing it with Go makes for a solid combination.
I covered session management with Redis in an earlier post, but here I want to go broader and cover the fundamentals of using Redis with Go.
Setting Up the Connection
I use the go-redis package. It's well maintained and has a clean API:
go get github.com/go-redis/redis/v8
package main
import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
)
var ctx = context.Background()
func main() {
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
// Test the connection
pong, err := rdb.Ping(ctx).Result()
if err != nil {
panic(err)
}
fmt.Println(pong) // PONG
}
Basic Operations
// Set a value with expiration
err := rdb.Set(ctx, "user:1:name", "Alice", 30*time.Minute).Err()
// Get a value
name, err := rdb.Get(ctx, "user:1:name").Result()
if err == redis.Nil {
fmt.Println("Key does not exist")
} else if err != nil {
panic(err)
}
// Delete a key
rdb.Del(ctx, "user:1:name")
// Check if key exists
exists, _ := rdb.Exists(ctx, "user:1:name").Result()
// Set expiration on existing key
rdb.Expire(ctx, "user:1:name", 1*time.Hour)
Caching Pattern
This is the pattern I use most often. Check Redis first, fall back to the database, then cache the result:
func GetUser(ctx context.Context, rdb *redis.Client, id string) (*User, error) {
// Try cache first
cached, err := rdb.Get(ctx, "user:"+id).Result()
if err == nil {
var user User
json.Unmarshal([]byte(cached), &user)
return &user, nil
}
// Cache miss, hit the database
user, err := db.FindUser(id)
if err != nil {
return nil, err
}
// Cache for next time
data, _ := json.Marshal(user)
rdb.Set(ctx, "user:"+id, data, 15*time.Minute)
return user, nil
}
Hash Maps
When you need to store structured data, Redis hashes are cleaner than serializing to JSON:
// Set hash fields
rdb.HSet(ctx, "user:1", map[string]interface{}{
"name": "Alice",
"email": "alice@example.com",
"role": "admin",
})
// Get a single field
email, _ := rdb.HGet(ctx, "user:1", "email").Result()
// Get all fields
data, _ := rdb.HGetAll(ctx, "user:1").Result()
// data is map[string]string
Lists and Rate Limiting
A simple rate limiter using Redis sorted sets:
func IsRateLimited(ctx context.Context, rdb *redis.Client, userID string) bool {
key := "ratelimit:" + userID
now := time.Now().UnixNano()
windowStart := now - int64(time.Minute)
// Remove old entries
rdb.ZRemRangeByScore(ctx, key, "0", fmt.Sprint(windowStart))
// Count requests in window
count, _ := rdb.ZCard(ctx, key).Result()
if count >= 100 { // 100 requests per minute
return true
}
// Add current request
rdb.ZAdd(ctx, key, &redis.Z{Score: float64(now), Member: now})
rdb.Expire(ctx, key, 2*time.Minute)
return false
}
Connection Pooling
The go-redis client handles connection pooling automatically. You can tune it if needed:
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
PoolSize: 10,
MinIdleConns: 5,
PoolTimeout: 30 * time.Second,
})
For most applications, the defaults are fine. I only tune these when I'm seeing connection timeouts under load.
If you're setting up a full Go backend, also check out my posts on JSON marshaling, PostgreSQL transactions, and environment variables for the complete picture.