Client-side Redis connection management using Upstash HTTP API for Refine applications.
- ✅ Browser-Compatible: Uses Upstash HTTP REST API (no Node.js required)
- ✅ Multiple Connections: Support for named connections (cache, session, etc.)
- ✅ Dependency Injection: Integrates with @stackra/container
- ✅ React Hooks: Easy-to-use hooks for React components
- ✅ TypeScript: Full type safety with comprehensive JSDoc
- ✅ Production-Ready: Error handling, retries, and timeouts
- ✅ Zero Config: Sensible defaults with full customization
npm install @stackra/ts-redis @upstash/redis
# or
yarn add @stackra/ts-redis @upstash/redis
# or
pnpm add @stackra/ts-redis @upstash/redis- Sign up at Upstash Console
- Create a Redis database
- Copy the REST URL and token from the "REST API" section
// app.module.ts
import { Module } from '@stackra/container';
import { RedisModule } from '@stackra/ts-redis';
@Module({
imports: [
RedisModule.forRoot({
default: 'cache',
connections: {
cache: {
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
},
},
}),
],
})
export class AppModule {}import { Injectable } from '@stackra/container';
import { RedisService } from '@stackra/ts-redis';
@Injectable()
export class UserService {
constructor(private readonly redis: RedisService) {}
async cacheUser(user: User): Promise<void> {
const connection = await this.redis.connection();
await connection.set(
`user:${user.id}`,
JSON.stringify(user),
{ ex: 3600 } // Expire in 1 hour
);
}
async getUser(id: string): Promise<User | null> {
const connection = await this.redis.connection();
const data = await connection.get(`user:${id}`);
return data ? JSON.parse(data) : null;
}
}import { useRedis } from '@stackra/ts-redis';
import { useEffect, useState } from 'react';
function UserProfile({ userId }: { userId: string }) {
const redis = useRedis();
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
async function loadUser() {
const connection = await redis.connection();
// Try cache first
const cached = await connection.get(`user:${userId}`);
if (cached) {
setUser(JSON.parse(cached));
return;
}
// Fetch and cache
const user = await fetchUser(userId);
await connection.set(
`user:${userId}`,
JSON.stringify(user),
{ ex: 3600 }
);
setUser(user);
}
loadUser();
}, [userId, redis]);
return <div>{user?.name}</div>;
}Configure multiple Redis instances for different purposes:
RedisModule.forRoot({
default: 'cache',
connections: {
// Cache connection
cache: {
url: process.env.UPSTASH_CACHE_URL!,
token: process.env.UPSTASH_CACHE_TOKEN!,
},
// Session connection
session: {
url: process.env.UPSTASH_SESSION_URL!,
token: process.env.UPSTASH_SESSION_TOKEN!,
timeout: 10000,
},
// Rate limiting connection
ratelimit: {
url: process.env.UPSTASH_RATELIMIT_URL!,
token: process.env.UPSTASH_RATELIMIT_TOKEN!,
enableAutoPipelining: true,
},
},
});interface RedisConnectionConfig {
// Required
url: string; // Upstash Redis REST URL
token: string; // Upstash Redis REST token
// Optional
timeout?: number; // Request timeout in ms (default: 5000)
retry?: {
retries?: number;
backoff?: (retryCount: number) => number;
};
enableAutoPipelining?: boolean; // Batch commands automatically
}{
retry: {
retries: 3,
backoff: (retryCount) => {
// Exponential backoff: 100ms, 200ms, 400ms
return Math.min(100 * 2 ** retryCount, 1000);
}
}
}Main service for connection management.
connection(name?: string): Promise<RedisConnection>- Get a connectiondisconnect(name?: string): Promise<void>- Disconnect a connectiondisconnectAll(): Promise<void>- Disconnect all connectionsgetConnectionNames(): string[]- Get configured connection namesgetDefaultConnectionName(): string- Get default connection nameisConnectionActive(name?: string): boolean- Check if connection is active
Interface for Redis operations.
// Get/Set
await connection.get('key');
await connection.set('key', 'value', { ex: 3600 });
// Delete
await connection.del('key1', 'key2');
// Exists
await connection.exists('key1', 'key2');
// Expiration
await connection.expire('key', 300);
await connection.ttl('key');// Get multiple keys
const [val1, val2] = await connection.mget('key1', 'key2');
// Set multiple keys
await connection.mset({
key1: 'value1',
key2: 'value2',
});// Increment
await connection.incr('counter');
await connection.incrby('counter', 5);
// Decrement
await connection.decr('counter');
await connection.decrby('counter', 3);const results = await connection
.pipeline()
.set('key1', 'value1')
.set('key2', 'value2')
.get('key1')
.del('key3')
.exec();
console.log(results); // ['OK', 'OK', 'value1', 1]// Add to sorted set
await connection.zadd('leaderboard', 100, 'user:1');
// Get range
const top10 = await connection.zrange('leaderboard', 0, 9);
// Remove by score
await connection.zremrangebyscore('leaderboard', 0, 50);Get the Redis service instance.
const redis = useRedis();
const connection = await redis.connection('cache');Get a specific connection (returns a Promise).
const getConnection = useRedisConnection('cache');
useEffect(() => {
getConnection.then(async (connection) => {
await connection.set('key', 'value');
});
}, [getConnection]);async function getUser(id: string): Promise<User> {
const connection = await redis.connection();
// Try cache
const cached = await connection.get(`user:${id}`);
if (cached) {
return JSON.parse(cached);
}
// Fetch from database
const user = await db.users.findById(id);
// Cache for 1 hour
await connection.set(`user:${id}`, JSON.stringify(user), { ex: 3600 });
return user;
}async function acquireLock(
resource: string,
ttl: number = 10
): Promise<boolean> {
const connection = await redis.connection();
const lockKey = `lock:${resource}`;
// Try to acquire lock (set if not exists)
const acquired = await connection.set(lockKey, 'locked', {
nx: true,
ex: ttl,
});
return acquired === 'OK';
}
async function releaseLock(resource: string): Promise<void> {
const connection = await redis.connection();
await connection.del(`lock:${resource}`);
}async function checkRateLimit(
userId: string,
limit: number = 100,
window: number = 60
): Promise<boolean> {
const connection = await redis.connection();
const key = `ratelimit:${userId}`;
const current = await connection.incr(key);
if (current === 1) {
// First request, set expiration
await connection.expire(key, window);
}
return current <= limit;
}async function saveSession(
sessionId: string,
data: SessionData
): Promise<void> {
const connection = await redis.connection('session');
await connection.set(
`session:${sessionId}`,
JSON.stringify(data),
{ ex: 86400 } // 24 hours
);
}
async function getSession(sessionId: string): Promise<SessionData | null> {
const connection = await redis.connection('session');
const data = await connection.get(`session:${sessionId}`);
return data ? JSON.parse(data) : null;
}Always set expiration times to prevent memory leaks:
// Good
await connection.set('temp:data', value, { ex: 300 });
// Bad (no expiration)
await connection.set('temp:data', value);Batch operations for better performance:
// Good
await connection
.pipeline()
.set('key1', 'value1')
.set('key2', 'value2')
.set('key3', 'value3')
.exec();
// Bad (3 HTTP requests)
await connection.set('key1', 'value1');
await connection.set('key2', 'value2');
await connection.set('key3', 'value3');try {
const connection = await redis.connection();
await connection.set('key', 'value');
} catch (error) {
console.error('Redis error:', error);
// Fall back to database or return cached data
}Organize keys with prefixes:
// Good
`user:${userId}:profile``cache:posts:${postId}``session:${sessionId}`
// Bad
`${userId}``${postId}`;// In your app shutdown handler
await redis.disconnectAll();Full TypeScript support with comprehensive types:
import type {
RedisConnection,
RedisConfig,
RedisConnectionConfig,
SetOptions,
RedisPipeline,
} from '@stackra/ts-redis';This package works in all modern browsers that support:
- Fetch API
- Promises
- ES2020 features
No polyfills required for modern browsers (Chrome 80+, Firefox 75+, Safari 13.1+, Edge 80+).
MIT
Contributions are welcome! Please read our contributing guidelines before submitting PRs.
- @stackra/cache - Multi-driver cache system
- @upstash/redis - Upstash Redis client