Redis Plugin
Redis is the de-facto standard for ephemeral data storage. In a distributed system, it acts as the high-speed bridge between your stateless application instances.
The RedisPlugin manages the complex lifecycle of connections: establishing the link during bootstrap, keeping it alive (or handling reconnection) during execution, and gracefully terminating it during shutdown.
Concept: The Shared Whiteboard
Your application instances are like individual workers at their desks. They have their own short-term memory (RAM), but when they die (restart), they forget everything.
Redis is the large Whiteboard in the middle of the office.
- Caching: Worker A writes a calculation on the board so Worker B doesn't have to redo it.
- Pub/Sub: Worker A writes "New Order!" and all other workers see it instantly.
- Persistance: The text stays on the board even if the workers leave for the night.
Unified Architecture: Standard & Serverless
The Redis ecosystem is split between persistent TCP connections (Standard Redis) and HTTP-based stateless requests (Serverless / Upstash). Karin abstracts this via the Adapter Pattern, but exposes the raw underlying client for maximum performance.
1. The Standard TCP Strategy (VPS / Docker)
Used when deploying to persistent servers (EC2, DigitalOcean, Hetzner). It maintains a live socket connection.
import { RedisPlugin } from "@project-karin/redis";
// Uses 'ioredis' under the hood
const redis = new RedisPlugin({
url: "redis://user:pass@localhost:6379",
options: {
maxRetriesPerRequest: 3,
enableReadyCheck: true, // Fail boot if Redis is down
}
});2. The Serverless HTTP Strategy (Workers / Lambda)
Used in environments where TCP connections are unreliable or forbidden.
import { RedisPlugin, UpstashAdapter } from "@project-karin/redis";
// Uses '@upstash/redis' via HTTP fetch
const redis = new RedisPlugin({
adapter: new UpstashAdapter({
url: "https://global.upstash.io",
token: "..."
})
});Dependency Injection
The plugin registers the client in the global container. However, because ioredis and @upstash/redis have slightly different APIs, we provide the @InjectRedis() decorator to handle the injection token resolution.
import { Service } from "@project-karin/core";
import { InjectRedis } from "@project-karin/redis";
import type { Redis } from "ioredis";
@Service()
export class CacheService {
constructor(
@InjectRedis() private readonly redis: Redis
) {}
async remember(key: string, ttl: number, factory: () => Promise<string>) {
const cached = await this.redis.get(key);
if (cached) return cached;
const fresh = await factory();
await this.redis.set(key, fresh, "EX", ttl);
return fresh;
}
}Pub/Sub Pattern (Event Driven)
Redis is commonly used for real-time messaging. Note that in a Pub/Sub model, the subscriber connection becomes blocking—it cannot be used to send commands, only to receive messages. Therefore, you often need two Redis connections: one for data, one for subscriptions.
// Register a dedicated PubSub instance
const pubSubRedis = new RedisPlugin({
token: "REDIS_PUBSUB",
url: "..."
});
// In your service
@Service()
export class NotificationService implements OnModuleInit {
constructor(
@InjectRedis("REDIS_PUBSUB") private subscriber: Redis
) {}
async onModuleInit() {
await this.subscriber.subscribe("user-events");
this.subscriber.on("message", (channel, message) => {
console.log(`Received event on ${channel}: ${message}`);
});
}
}Lazy Configuration
Just like other core plugins, you can delay the resolution of credentials until the plugin actually initializes. This is essential when relying on ConfigPlugin to verify the environment first.
new RedisPlugin({
// The plugin will execute this function during the 'install' phase
url: () => process.env.REDIS_BACKBONE_URL,
failureStrategy: "warn", // Don't crash if cache is offline, just log it.
});