Redis

Nimbus ships a thin redis package that wraps go-redis/v9, giving you a fully idiomatic Go Redis client. The same client is used internally by the queue, cache, session, Transmit, and Horizon plugins, so the dependency is already in your project.

Creating a client

Use redis.NewClient with an *redis.Options struct. The most common fields are Addr, Password, and DB.

import "github.com/CodeSyncr/nimbus/redis"

rdb := redis.NewClient(&redis.Options{
    Addr:     "localhost:6379", // or os.Getenv("REDIS_URL")
    Password: "",               // no password by default
    DB:       0,                // default DB
})

If you prefer a URL (e.g. from .env), use redis.ParseURL:

opt, err := redis.ParseURL("redis://localhost:6379/0")
if err != nil {
    log.Fatal(err)
}
rdb := redis.NewClient(opt)

Wiring into the container

Register the client as a singleton so controllers and services can resolve it from the IoC container:

func Boot() *nimbus.App {
    config.Load()
    app := nimbus.New()

    // Redis singleton
    rdb := redis.NewClient(&redis.Options{Addr: os.Getenv("REDIS_ADDR")})
    app.Container.Singleton("redis", func() *redis.Client { return rdb })

    start.RegisterMiddleware(app)
    start.RegisterRoutes(app)
    return app
}

Resolve it inside a controller or service:

type CacheController struct {
    Redis *redis.Client
}

// In routes.go:
rdb := app.Container.MustMake("redis").(*redis.Client)
app.Router.Resource("cache", &controllers.CacheController{Redis: rdb})

Basic operations

Every Redis command is available directly on the client. All operations are async and accept a context.Context.

import (
    "context"
    "time"

    "github.com/CodeSyncr/nimbus/redis"
)

ctx := context.Background()

// โ”€โ”€ Strings โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
err := rdb.Set(ctx, "username", "virk", 0).Err()
username, err := rdb.Get(ctx, "username").Result()

// With TTL
rdb.Set(ctx, "otp:12345", "982344", 5*time.Minute)
val, err := rdb.Get(ctx, "otp:12345").Result()
if err == redis.Nil {
    // key does not exist or has expired
}

// โ”€โ”€ Increment / Decrement โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
rdb.Incr(ctx, "page_views")
rdb.IncrBy(ctx, "score", 10)

// โ”€โ”€ Expiry โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
rdb.Expire(ctx, "username", 30*time.Minute)
ttl, _ := rdb.TTL(ctx, "username").Result()

// โ”€โ”€ Existence check โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
exists, _ := rdb.Exists(ctx, "username").Result() // 1 = exists, 0 = not
rdb.Del(ctx, "username")

Hashes

// Set multiple fields
rdb.HSet(ctx, "user:1", "name", "Virk", "email", "virk@example.com")

// Get one field
name, _ := rdb.HGet(ctx, "user:1", "name").Result()

// Get all fields as map
fields, _ := rdb.HGetAll(ctx, "user:1").Result() // map[string]string

// Delete a field
rdb.HDel(ctx, "user:1", "email")

Lists

// Push to list
rdb.LPush(ctx, "notifications", "msg1", "msg2")
rdb.RPush(ctx, "queue", "job1")

// Pop from list
val, _ := rdb.LPop(ctx, "notifications").Result()

// Get range
items, _ := rdb.LRange(ctx, "notifications", 0, -1).Result()

Sets & Sorted Sets

// Sets
rdb.SAdd(ctx, "tags", "go", "nimbus", "redis")
members, _ := rdb.SMembers(ctx, "tags").Result()
isMember, _ := rdb.SIsMember(ctx, "tags", "go").Result()

// Sorted sets (leaderboard)
rdb.ZAdd(ctx, "leaderboard",
    redis.Z{Score: 100, Member: "alice"},
    redis.Z{Score: 85, Member: "bob"},
)
top, _ := rdb.ZRevRangeWithScores(ctx, "leaderboard", 0, 9).Result()

Pub/Sub

// Subscribe
pubsub := rdb.Subscribe(ctx, "notifications")
defer pubsub.Close()

go func() {
    for msg := range pubsub.Channel() {
        fmt.Println("Received:", msg.Payload)
    }
}()

// Publish from another goroutine or handler
rdb.Publish(ctx, "notifications", "new_order:42")

Error handling

Use redis.Nil to detect missing keys, and always check .Err() for write operations:

val, err := rdb.Get(ctx, "key").Result()
switch {
case err == redis.Nil:
    // key does not exist
case err != nil:
    // real Redis error (connection refused, timeout, etc.)
default:
    fmt.Println("Value:", val)
}

// Write errors
if err := rdb.Set(ctx, "key", "value", 0).Err(); err != nil {
    // handle error
}

Environment variable convention

Add to your .env:

REDIS_URL=redis://localhost:6379
# For password-protected Redis:
# REDIS_URL=redis://:password@localhost:6379/0

Then in Boot():

opt, _ := redis.ParseURL(os.Getenv("REDIS_URL"))
rdb := redis.NewClient(opt)

When to use Redis vs Cache

Use caseRecommendation
Application caching (remember, tags)cache package with Redis backend
Job queues, leaderboards, pub/subDirect redis.Client
Session persistence across restartsRedis-backed session store
Rate limiting, presence, locksDirect redis.Client or ratelimit_redis middleware