Redis Client

Redis client for caching and rate limiting

Redis Client

The @entro314labs/redis-client package provides a Redis client for caching, rate limiting, and session management in Entrolytics.

Installation

pnpm add @entro314labs/redis-client
npm install @entro314labs/redis-client
yarn add @entro314labs/redis-client
bun add @entro314labs/redis-client

Quick Start

import { RedisClient } from '@entro314labs/redis-client'

const redis = new RedisClient({
  url: process.env.REDIS_URL,
})

await redis.connect()

// Basic operations
await redis.set('key', { data: 'value' })
const data = await redis.get('key')

// Rate limiting
const exceeded = await redis.rateLimit('api:user:123', 100, 60)

Configuration

import { RedisClient } from '@entro314labs/redis-client'

const redis = new RedisClient({
  // Required
  url: process.env.REDIS_URL,

  // Optional
  prefix: 'entrolytics:',
  defaultTTL: 3600,
  connectTimeout: 10000,
  commandTimeout: 5000,
  lazyConnect: true,
})

Configuration Options

OptionTypeDefaultDescription
urlstring-Redis connection URL (required)
prefixstring'entrolytics:'Key prefix for all operations
defaultTTLnumber3600Default TTL in seconds
connectTimeoutnumber10000Connection timeout (ms)
commandTimeoutnumber5000Command timeout (ms)
lazyConnectbooleantrueConnect on first operation

API Reference

Connection

connect()

Establish Redis connection.

await redis.connect()

disconnect()

Close Redis connection.

await redis.disconnect()

healthCheck()

Check connection health with latency.

const { connected, latency } = await redis.healthCheck()
console.log(`Connected: ${connected}, Latency: ${latency}ms`)

Basic Operations

get(key)

Get a value (JSON parsed automatically).

const user = await redis.get<User>('user:123')

set(key, value, ttl?)

Set a value with optional TTL.

// With default TTL
await redis.set('user:123', { name: 'John' })

// With custom TTL (seconds)
await redis.set('session:abc', { userId: '123' }, 3600)

// With TTL options
await redis.set('cache:data', data, { ex: 3600 }) // seconds
await redis.set('cache:data', data, { px: 60000 }) // milliseconds
await redis.set('cache:data', data, { exat: 1735689600 }) // unix timestamp

del(key)

Delete a key.

await redis.del('user:123')

exists(key)

Check if key exists.

const exists = await redis.exists('user:123')

Numeric Operations

incr(key)

Increment a value.

const newValue = await redis.incr('counter')

decr(key)

Decrement a value.

const newValue = await redis.decr('counter')

TTL Operations

expire(key, seconds)

Set TTL on existing key.

await redis.expire('session:abc', 3600)

ttl(key)

Get remaining TTL.

const remaining = await redis.ttl('session:abc')

Rate Limiting

rateLimit(key, limit, window)

Check rate limit (returns true if exceeded).

// 100 requests per 60 seconds
const exceeded = await redis.rateLimit('api:user:123', 100, 60)

if (exceeded) {
  return new Response('Too Many Requests', { status: 429 })
}

getRemainingRequests(key, limit, window)

Get remaining requests in window.

const remaining = await redis.getRemainingRequests('api:user:123', 100, 60)
console.log(`Remaining requests: ${remaining}`)

Cache Patterns

fetch(key, fetcher, ttl?)

Cache-through pattern: get from cache or fetch and cache.

const user = await redis.fetch(
  'user:123',
  async () => await db.query.user.findFirst({ where: eq(user.id, '123') }),
  3600
)

store(key, value, ttl?)

Store value in cache (alias for set).

await redis.store('user:123', userData, 3600)

invalidate(key)

Remove from cache (soft delete).

await redis.invalidate('user:123')

invalidatePattern(pattern)

Delete all keys matching pattern.

// Delete all user cache entries
await redis.invalidatePattern('user:*')

// Delete all cache for a website
await redis.invalidatePattern('website:abc:*')

Statistics

getStats()

Get cache hit/miss statistics.

const stats = redis.getStats()
console.log(`Hit rate: ${stats.hitRate}%`)
console.log(`Hits: ${stats.hits}, Misses: ${stats.misses}`)

resetStats()

Reset statistics counters.

redis.resetStats()

Utility

flushPrefix()

Delete all keys with the configured prefix.

// ⚠️ Use with caution
await redis.flushPrefix()

Usage Examples

Session Caching

import { RedisClient } from '@entro314labs/redis-client'

const redis = new RedisClient({ url: process.env.REDIS_URL })

// Store session
async function createSession(userId: string, data: SessionData) {
  const sessionId = crypto.randomUUID()
  await redis.set(`session:${sessionId}`, { userId, ...data }, 86400) // 24 hours
  return sessionId
}

// Get session
async function getSession(sessionId: string) {
  return redis.get<SessionData>(`session:${sessionId}`)
}

// Extend session
async function extendSession(sessionId: string) {
  await redis.expire(`session:${sessionId}`, 86400)
}

API Rate Limiting

import { RedisClient } from '@entro314labs/redis-client'

const redis = new RedisClient({ url: process.env.REDIS_URL })

export async function rateLimit(request: Request) {
  const ip = request.headers.get('x-forwarded-for') || 'unknown'

  // 100 requests per minute
  const exceeded = await redis.rateLimit(`api:${ip}`, 100, 60)

  if (exceeded) {
    const remaining = await redis.getRemainingRequests(`api:${ip}`, 100, 60)
    return new Response('Rate limit exceeded', {
      status: 429,
      headers: {
        'X-RateLimit-Remaining': String(remaining),
        'Retry-After': '60',
      },
    })
  }

  return null // Continue processing
}

Query Caching

import { RedisClient } from '@entro314labs/redis-client'

const redis = new RedisClient({ url: process.env.REDIS_URL })

async function getWebsiteStats(websiteId: string, dateRange: DateRange) {
  const cacheKey = `stats:${websiteId}:${dateRange.start}:${dateRange.end}`

  return redis.fetch(
    cacheKey,
    async () => {
      // Expensive database query
      return await db.execute(sql`
        SELECT * FROM website_event
        WHERE website_id = ${websiteId}
        AND created_at BETWEEN ${dateRange.start} AND ${dateRange.end}
      `)
    },
    300 // Cache for 5 minutes
  )
}

Cache Invalidation

// When website settings change
async function updateWebsite(websiteId: string, data: UpdateData) {
  await db.update(website).set(data).where(eq(website.id, websiteId))

  // Invalidate all cache for this website
  await redis.invalidatePattern(`website:${websiteId}:*`)
  await redis.invalidatePattern(`stats:${websiteId}:*`)
}

TypeScript Types

import type {
  RedisClientConfig,
  RedisStats,
  TTLOptions,
} from '@entro314labs/redis-client'

Properties

Access underlying client and state:

// Access ioredis client directly
const ioredis = redis.client

// Check connection state
const connected = redis.isConnected

// Get configured URL (masked)
const url = redis.url

// Get key prefix
const prefix = redis.prefix

// Get default TTL
const ttl = redis.defaultTTL

Error Handling

import { RedisClient } from '@entro314labs/redis-client'

const redis = new RedisClient({ url: process.env.REDIS_URL })

try {
  await redis.get('key')
} catch (error) {
  if (error instanceof Error) {
    console.error('Redis error:', error.message)
  }
}

Requirements

  • Node.js >= 22.x
  • Redis 6.0+

License

MIT