Redis Client
Redis client for caching and rate limiting
Redis Client
The @entrolytics/redis-client package provides a Redis client for caching, rate limiting, and session management in Entrolytics.
Installation
pnpm add @entrolytics/redis-clientQuick Start
import { EntrolyticsRedisClient } from '@entrolytics/redis-client'
const redis = new EntrolyticsRedisClient({
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 { EntrolyticsRedisClient } from '@entrolytics/redis-client'
const redis = new EntrolyticsRedisClient({
// Required
url: process.env.REDIS_URL,
// Optional
prefix: 'entrolytics:',
defaultTTL: 3600,
connectTimeout: 10000,
commandTimeout: 5000,
lazyConnect: true
})Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
url | string | - | Redis connection URL (required) |
prefix | string | 'entrolytics:' | Key prefix for all operations |
defaultTTL | number | 3600 | Default TTL in seconds |
connectTimeout | number | 10000 | Connection timeout (ms) |
commandTimeout | number | 5000 | Command timeout (ms) |
lazyConnect | boolean | true | Connect 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 timestampdel(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 { EntrolyticsRedisClient } from '@entrolytics/redis-client'
const redis = new EntrolyticsRedisClient({ 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 {
EntrolyticsRedisClient,
rateLimit,
getRateLimitHeaders
} from '@entrolytics/redis-client'
const redis = new EntrolyticsRedisClient({ url: process.env.REDIS_URL })
export async function handleRateLimit(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
}
// Or use the standalone rate limit helper
import {
createRateLimiter,
type RateLimitConfig
} from '@entrolytics/redis-client'
const limiter = createRateLimiter(redis, {
limit: 100,
window: 60
})
const result = await limiter.check('user:123')
if (!result.success) {
const headers = getRateLimitHeaders(result)
return new Response('Too Many Requests', { status: 429, headers })
}Query Caching
import { EntrolyticsRedisClient } from '@entrolytics/redis-client'
const redis = new EntrolyticsRedisClient({ 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 {
EntrolyticsRedisClientOptions,
CacheStats,
TTLOption,
RateLimitConfig,
RateLimitResult
} from '@entrolytics/redis-client'
// Constants
import { CACHE_PREFIX, DELETED, RATE_LIMITS } from '@entrolytics/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.defaultTTLError Handling
import { EntrolyticsRedisClient } from '@entrolytics/redis-client'
const redis = new EntrolyticsRedisClient({ 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 >= 24.20.0
- Redis 6.0+
Support
- GitHub Issues
- Email: hey@entrolytics.dev