Using Redis in Go: Global Client with Auto Reconnect

Redis is a powerful in-memory data store often used for caching, sessions, and rate limiting.
When integrating Redis into a Go application, you need to decide:
- Where to place your Redis client (global vs dependency injection)
- How to handle Redis downtime (graceful fallback, auto reconnect)
- Which layer should own caching logic (repository, service, or handler)
This guide shows a practical industry-style setup:
- A global Redis client (
pkg/redisclient
) - Auto reconnect & retry settings
- Example of caching user data inside the repository
Install go-redis
🔗shgo get github.com/redis/go-redis/v9
Create a Global Redis Client
🔗We put Redis inside pkg/redisclient
so it can be reused across the whole project without injecting it everywhere.
pkg/redisclient/redis.go
gopackage redisclient
import (
"context"
"fmt"
"log"
"os"
"time"
"github.com/joho/godotenv"
"github.com/redis/go-redis/v9"
)
var Rdb *redis.Client
// Init initializes the global Redis client
func Init() {
_ = godotenv.Load()
host := os.Getenv("REDIS_HOST")
if host == "" {
host = "127.0.0.1"
}
port := os.Getenv("REDIS_PORT")
if port == "" {
port = "6379"
}
password := os.Getenv("REDIS_PASSWORD")
addr := fmt.Sprintf("%s:%s", host, port)
Rdb = redis.NewClient(&redis.Options{
Addr: addr,
Password: password,
DB: 0,
})
if _, err := Rdb.Ping(context.Background()).Result(); err != nil {
log.Printf("⚠️ Redis unavailable: %v", err)
Rdb = nil // mark as unavailable
} else {
log.Println("✅ Redis connected successfully")
}
}
Why global?
- Simplicity: Handlers and services can access
redisclient.Rdb
directly. - Performance: Only one client instance; go-redis internally manages connection pooling.
- Industry practice: Redis is usually an infrastructure service, not per-request state.
Initialize Redis in main.go
🔗gopackage main
import (
"go-template/pkg/redisclient"
)
func main() {
redisclient.Init()
// start Gin or other services...
}
Use Redis in the Repository Layer
🔗Instead of putting caching inside handlers (which makes them “fat”), we keep caching at the repository layer, since caching is part of “data access”.
go// internal/user/repository/user_repository.go
func (r *userRepository) GetUserByID(id int64) (*model.User, string, error) {
cacheKey := fmt.Sprintf("user:%d", id)
// 1. Try Redis first
if redisclient.Rdb != nil {
if val, err := redisclient.Rdb.Get(context.Background(), cacheKey).Result(); err == nil {
var u model.User
if json.Unmarshal([]byte(val), &u) == nil {
return &u, "redis", nil
}
}
}
// 2. Fallback to DB
var u model.User
err := r.DB.QueryRow(
`SELECT id, name, email, role FROM "user" WHERE id = $1`,
id,
).Scan(&u.ID, &u.Name, &u.Email, &u.Role)
if err == sql.ErrNoRows {
return nil, "db", nil
} else if err != nil {
return nil, "db", err
}
// 3. Write back to Redis
if redisclient.Rdb != nil {
if bytes, err := json.Marshal(u); err == nil {
_ = redisclient.Rdb.Set(context.Background(), cacheKey, bytes, 10*time.Minute).Err()
}
}
return &u, "db", nil
}
Now the service just calls the repository, and it doesn’t matter if data came from Redis or the DB.
Handling Redis Downtime
🔗- On startup, if Redis is down → log a warning and continue.
- On runtime, if Redis fails (
redis.Nil
or connection error) → simply fallback to DB. - The application never crashes because of Redis.
This approach is called Cache-Aside Pattern:
- Read: Try cache → fallback DB → write back to cache.
- Write: Update DB → invalidate or update cache.
When to Use Global vs Injection?
🔗Approach | When to Use | Pros | Cons |
---|---|---|---|
Global client (pkg/redisclient.Rdb ) | Small/medium projects, caching as infra | Simple, reusable everywhere | Harder to mock in tests |
Dependency injection | Large projects, strict testability | Explicit dependencies, easier mocking | More boilerplate (passing rdb everywhere) |
Industry trend: Most production Go backends use a global client for Redis, since it’s infrastructure like logging or config. If you’re building a highly modular system or a library, injection may be better.
Summary
🔗- Use
pkg/redisclient
with a global client for simplicity. - Configure
MaxRetries
,DialTimeout
, andPing
to handle downtime gracefully. - Place caching logic in repository or service, not in handlers.
- Follow the Cache-Aside Pattern for reliable caching.
This ensures:
- Your app still runs if Redis is down.
- Redis auto-reconnects when it comes back.
- Handlers stay clean, services focus on logic, and repositories handle data + cache.

Alvin
Software engineer who dislikes pointless busyness, enjoys solving problems with logic, and strives to find balance between the blind pursuit of achievements and a relaxed lifestyle.