Skip to main content

Overview

The Redis provider extends your application ports with cache capabilities using ioredis. It provides a simple caching interface for storing and retrieving data with optional TTL (time-to-live) support.

Installation

npm install @contract-kit/provider-redis ioredis

Configuration

The Redis provider reads configuration from environment variables:
VariableRequiredDescriptionExample
REDIS_URLYesRedis connection URLredis://localhost:6379
REDIS_DBNoRedis database number (default: 0)0

Example .env

REDIS_URL=redis://localhost:6379
REDIS_DB=0

Setup

Basic Setup

import { createServer } from "@contract-kit/server";
import { redisProvider } from "@contract-kit/provider-redis";

const app = createServer({
  ports: basePorts,
  providers: [redisProvider],
  createContext: ({ ports }) => ({
    ports,
    // ... other context
  }),
  routes: [
    // ... your routes
  ],
});

Type Your Ports

To get proper type inference for the cache port, extend your ports type:
import type { CachePort } from "@contract-kit/provider-redis";

// Your base ports
const basePorts = definePorts({
  db: dbAdapter,
});

// After using redisProvider, your ports will have this shape:
type AppPorts = typeof basePorts & {
  cache: CachePort;
};

API Reference

The provider extends your ports with a cache property:

get(key: string)

Get a value from the cache.
const value = await ctx.ports.cache.get("my-key");
Returns: Promise<string | null>

set(key: string, value: string, ttlSeconds?: number)

Set a value in the cache with optional TTL (time-to-live) in seconds.
// Without TTL (persists forever)
await ctx.ports.cache.set("key", "value");

// With TTL (expires after 1 hour)
await ctx.ports.cache.set("key", "value", 3600);
Returns: Promise<void>

del(key: string)

Delete a key from the cache. Returns the number of keys deleted (0 or 1).
const deleted = await ctx.ports.cache.del("my-key");
Returns: Promise<number>

exists(key: string)

Check if a key exists in the cache.
const exists = await ctx.ports.cache.exists("my-key");
Returns: Promise<boolean>

client

Access the underlying ioredis client for advanced operations.
// Use ioredis methods directly
await ctx.ports.cache.client.expire("key", 300);
await ctx.ports.cache.client.incr("counter");
Type: Redis (from ioredis)

Usage Examples

Caching User Profiles

async function getUserProfile(ctx: AppCtx) {
  const cacheKey = `user:${ctx.userId}:profile`;
  
  // Try to get from cache
  const cached = await ctx.ports.cache.get(cacheKey);
  if (cached) {
    return JSON.parse(cached);
  }
  
  // Fetch from database
  const profile = await ctx.ports.db.users.findById(ctx.userId);
  
  // Store in cache for 1 hour
  await ctx.ports.cache.set(
    cacheKey,
    JSON.stringify(profile),
    3600
  );
  
  return profile;
}

Cache Invalidation

async function updateUserProfile(ctx: AppCtx, data: ProfileData) {
  // Update in database
  const updated = await ctx.ports.db.users.update(ctx.userId, data);
  
  // Invalidate cache
  await ctx.ports.cache.del(`user:${ctx.userId}:profile`);
  
  return updated;
}

Using Advanced Redis Features

// Use Redis directly for advanced operations
async function incrementCounter(ctx: AppCtx, key: string) {
  const newValue = await ctx.ports.cache.client.incr(key);
  return newValue;
}

async function addToSet(ctx: AppCtx, key: string, value: string) {
  await ctx.ports.cache.client.sadd(key, value);
}

async function getSetMembers(ctx: AppCtx, key: string) {
  return await ctx.ports.cache.client.smembers(key);
}

Lifecycle

The Redis provider:
  1. During register: Connects to Redis and adds the cache port
  2. During onAppStop: Gracefully closes the Redis connection

Error Handling

The provider will throw errors in these cases:
  • Missing REDIS_URL environment variable
  • Failed connection to Redis server
Make sure to handle these during application startup.

Best Practices

Use a consistent pattern like entity:id:field for cache keys to make debugging easier.
Always set TTLs for cached data to prevent stale data and memory issues.
When updating data in your database, remember to invalidate the corresponding cache entries.
Use JSON.stringify() and JSON.parse() for storing and retrieving complex objects.
Always have a fallback to fetch from the primary data source when cache misses occur.

Next Steps