# Error Handling Source: https://upstash-redis-js.mintlify.app/advanced/error-handling Understanding and handling errors in the Upstash Redis SDK ## Overview The Upstash Redis SDK provides specific error types to help you handle different failure scenarios. All errors extend the standard JavaScript `Error` class with additional context. ## Error Types The SDK defines three main error types in `pkg/error.ts`: ### UpstashError The base error class for general Redis operation failures: ```typescript theme={null} export class UpstashError extends Error { constructor(message: string, options?: ErrorOptions) { super(message, options); this.name = "UpstashError"; } } ``` **When it's thrown**: * Invalid Redis commands * Server-side errors * Invalid responses from Redis * Pipeline/transaction failures **Example**: ```typescript theme={null} import { Redis, UpstashError } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); try { // Try to use HGET on a string key (type mismatch) await redis.set('mykey', 'value'); await redis.hget('mykey', 'field'); } catch (error) { if (error instanceof UpstashError) { console.error('Redis operation failed:', error.message); } } ``` ### UrlError Thrown when an invalid URL is provided during client initialization: ```typescript theme={null} export class UrlError extends Error { constructor(url: string) { super( `Upstash Redis client was passed an invalid URL. You should pass a URL starting with https. Received: "${url}". ` ); this.name = "UrlError"; } } ``` **When it's thrown**: * URL doesn't start with `https://` * Malformed URL format **Example**: ```typescript theme={null} import { Redis, UrlError } from '@upstash/redis'; try { const redis = new Redis({ url: 'http://invalid-url.com', // Missing https token: 'token', }); } catch (error) { if (error instanceof UrlError) { console.error('Invalid URL:', error.message); } } ``` ### UpstashJSONParseError Thrown when the SDK fails to parse a response from Redis: ```typescript theme={null} export class UpstashJSONParseError extends UpstashError { constructor(body: string, options?: UpstashErrorOptions) { const truncatedBody = body.length > 200 ? body.slice(0, 200) + "..." : body; super(`Unable to parse response body: ${truncatedBody}`, options); this.name = "UpstashJSONParseError"; } } ``` **When it's thrown**: * Response body is not valid JSON * Corrupted response data * Unexpected response format **Example**: ```typescript theme={null} import { UpstashJSONParseError } from '@upstash/redis'; try { const result = await redis.get('somekey'); } catch (error) { if (error instanceof UpstashJSONParseError) { console.error('Failed to parse response:', error.message); // The error message includes the truncated response body } } ``` ## Common Error Scenarios ### Type Mismatch Errors Attempting an operation on the wrong data type: ```typescript theme={null} try { await redis.set('user:1', 'John'); await redis.hget('user:1', 'name'); // Error: key is a string, not a hash } catch (error) { if (error instanceof UpstashError) { console.error('Type mismatch:', error.message); // Handle by checking key type first or recreating the key } } ``` ### Authentication Errors ```typescript theme={null} try { const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: 'invalid-token', }); await redis.ping(); } catch (error) { console.error('Authentication failed:', error); // Check your token and URL } ``` ### Network Errors ```typescript theme={null} try { await redis.get('mykey'); } catch (error) { if (error.message.includes('fetch failed') || error.message.includes('network')) { console.error('Network error:', error); // Implement retry logic } } ``` ### Pipeline Errors By default, pipeline errors cause the entire pipeline to fail: ```typescript theme={null} import { UpstashError } from '@upstash/redis'; try { const results = await redis.pipeline() .set('key1', 'value1') .hget('key1', 'field') // This will fail .get('key2') .exec(); } catch (error) { if (error instanceof UpstashError) { console.error('Pipeline failed:', error.message); // Error message indicates which command failed } } ``` ### Handling Pipeline Errors Individually Use `keepErrors` to handle errors per-command: ```typescript theme={null} const results = await redis.pipeline() .set('key1', 'value1') .hget('key1', 'field') // This will fail .get('key1') .exec({ keepErrors: true }); results.forEach((item, index) => { if (item.error) { console.error(`Command ${index} failed:`, item.error); } else { console.log(`Command ${index} succeeded:`, item.result); } }); ``` ## Best Practices ### 1. Use Type Guards ```typescript theme={null} import { UpstashError, UrlError, UpstashJSONParseError } from '@upstash/redis'; try { await redis.get('mykey'); } catch (error) { if (error instanceof UrlError) { // Handle configuration error console.error('Configuration error:', error.message); } else if (error instanceof UpstashJSONParseError) { // Handle parsing error console.error('Response parsing failed:', error.message); } else if (error instanceof UpstashError) { // Handle general Redis error console.error('Redis error:', error.message); } else { // Handle unexpected errors console.error('Unexpected error:', error); } } ``` ### 2. Implement Retry Logic ```typescript theme={null} async function getWithRetry( redis: Redis, key: string, maxRetries = 3 ): Promise { let lastError; for (let i = 0; i < maxRetries; i++) { try { return await redis.get(key); } catch (error) { lastError = error; if (error instanceof UpstashJSONParseError) { // Don't retry parsing errors throw error; } if (i < maxRetries - 1) { // Wait before retrying (exponential backoff) await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000) ); } } } throw lastError; } ``` ### 3. Graceful Degradation ```typescript theme={null} async function getUserData(userId: string) { try { const userData = await redis.hgetall(`user:${userId}`); return userData; } catch (error) { console.error('Failed to fetch from Redis:', error); // Fallback to database or default values return await fetchFromDatabase(userId); } } ``` ### 4. Transaction Error Handling ```typescript theme={null} try { const results = await redis.multi() .set('balance:user1', 100) .decrby('balance:user1', 50) .incrby('balance:user2', 50) .exec(); console.log('Transaction completed:', results); } catch (error) { if (error instanceof UpstashError) { console.error('Transaction failed:', error.message); // Implement compensating transaction or rollback logic } } ``` ### 5. Validation Before Operations ```typescript theme={null} async function safeHashGet(key: string, field: string) { try { // Check key type first const keyType = await redis.type(key); if (keyType !== 'hash' && keyType !== 'none') { throw new Error(`Key '${key}' is of type '${keyType}', expected 'hash'`); } return await redis.hget(key, field); } catch (error) { console.error('Safe hash get failed:', error); return null; } } ``` ### 6. Logging and Monitoring ```typescript theme={null} import { UpstashError } from '@upstash/redis'; function logRedisError(error: unknown, context: string) { const errorInfo = { context, timestamp: new Date().toISOString(), name: error instanceof Error ? error.name : 'Unknown', message: error instanceof Error ? error.message : String(error), isUpstashError: error instanceof UpstashError, }; // Send to your logging service console.error('Redis Error:', errorInfo); // Optional: Send to monitoring service // monitoring.trackError(errorInfo); } try { await redis.get('mykey'); } catch (error) { logRedisError(error, 'getUserData'); throw error; } ``` ## Error Cause Chain Upstash errors support the `cause` option for error chaining: ```typescript theme={null} try { await someOperation(); } catch (originalError) { throw new UpstashError( 'Failed to process user data', { cause: originalError } ); } ``` Access the cause: ```typescript theme={null} catch (error) { if (error instanceof UpstashError && error.cause) { console.error('Original error:', error.cause); } } ``` ## Debugging Tips ### 1. Enable Latency Logging ```typescript theme={null} const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, latencyLogging: true, }); ``` ### 2. Inspect Error Objects ```typescript theme={null} catch (error) { console.error('Error details:', { name: error.name, message: error.message, stack: error.stack, cause: error.cause, }); } ``` ### 3. Test Error Scenarios ```typescript theme={null} // Test type mismatch try { await redis.set('testkey', 'value'); await redis.hget('testkey', 'field'); } catch (error) { console.log('Expected type mismatch error:', error.message); } // Test invalid key try { await redis.get('nonexistent'); console.log('Result is null for nonexistent keys'); } catch (error) { console.error('Unexpected error:', error); } ``` ## See Also * [Transactions](/advanced/transactions) * [Pipelines](/api/pipeline) * [Configuration Options](/concepts/client-configuration) # Lua Scripts Source: https://upstash-redis-js.mintlify.app/advanced/lua-scripts Execute Lua scripts with eval, evalsha, fcall and the Script helper classes ## Overview The Upstash Redis SDK provides multiple ways to execute Lua scripts: * **Direct execution**: `eval`, `evalsha`, `evalRo`, `evalshaRo` * **Script helpers**: `Script` and `ScriptRO` classes for automatic caching * **Functions**: `fcall` and `fcallRo` for Redis Functions (Redis 7.0+) ## Direct Script Execution ### EVAL Command Execute a Lua script directly: ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); const script = ` local key = KEYS[1] local value = ARGV[1] redis.call('SET', key, value) return redis.call('GET', key) `; const result = await redis.eval( script, ['mykey'], // KEYS ['myvalue'] // ARGV ); console.log(result); // 'myvalue' ``` ### Type Parameters Specify return types for better type safety: ```typescript theme={null} // Returns a string const text = await redis.eval( 'return ARGV[1]', [], ['Hello World'] ); // Returns a number const number = await redis.eval( 'return tonumber(ARGV[1])', [], ['42'] ); // Returns an array const array = await redis.eval( 'return {ARGV[1], ARGV[2], ARGV[3]}', [], ['a', 'b', 'c'] ); // Returns an object interface Result { count: number; items: string[]; } const result = await redis.eval( ` local count = redis.call('LLEN', KEYS[1]) local items = redis.call('LRANGE', KEYS[1], 0, -1) return {count = count, items = items} `, ['mylist'], [] ); ``` ### EVALSHA Command Execute a script by its SHA1 hash (must be loaded first): ```typescript theme={null} const script = 'return ARGV[1]'; // First, load the script const sha1 = await redis.scriptLoad(script); // Then execute it by hash const result = await redis.evalsha( sha1, [], ['Hello'] ); ``` ### Read-Only Variants Use `evalRo` and `evalshaRo` for read-only scripts (available in cluster mode): ```typescript theme={null} const result = await redis.evalRo( 'return redis.call("GET", KEYS[1])', ['counter'], [] ); ``` ## Script Helper Classes ### Script Class The `Script` class provides automatic script caching and optimistic execution: ```typescript theme={null} const script = redis.createScript( 'return ARGV[1]' ); const result = await script.exec([], ['Hello World']); console.log(result); // 'Hello World' ``` #### How It Works 1. **First execution**: Tries `EVALSHA` (optimistic) 2. **If script not cached**: Falls back to `EVAL` and caches it 3. **Subsequent executions**: Uses cached `EVALSHA` #### Script Methods ```typescript theme={null} const script = redis.createScript( ` local keys = {} for i, key in ipairs(KEYS) do table.insert(keys, key) end return keys ` ); // Direct EVAL execution const result1 = await script.eval(['key1', 'key2'], []); // Direct EVALSHA execution (throws if not loaded) try { const result2 = await script.evalsha(['key1', 'key2'], []); } catch (error) { console.error('Script not loaded'); } // Optimistic execution (recommended) const result3 = await script.exec(['key1', 'key2'], []); ``` #### SHA1 Hash ```typescript theme={null} const script = redis.createScript( 'return "Hello World"' ); // Wait for hash computation (asynchronous) await new Promise(resolve => setTimeout(resolve, 0)); console.log(script.sha1); // Computed SHA1 hash ``` The `sha1` property is deprecated and initialized asynchronously. Avoid using it immediately after creating the script. ### ScriptRO Class (Read-Only) For read-only scripts, use the `ScriptRO` class: ```typescript theme={null} const script = redis.createScript( 'return redis.call("GET", KEYS[1])', { readonly: true } ); // Only read-only methods are available const result = await script.exec(['mykey'], []); ``` ## Practical Script Examples ### Atomic Increment with Limit ```typescript theme={null} const incrementWithLimit = redis.createScript(` local key = KEYS[1] local max = tonumber(ARGV[1]) local current = tonumber(redis.call('GET', key) or '0') if current < max then return redis.call('INCR', key) else return current end `); const newValue = await incrementWithLimit.exec( ['counter'], ['100'] // Max value ); ``` ### Conditional Set ```typescript theme={null} const conditionalSet = redis.createScript<'OK' | null>(` local key = KEYS[1] local newValue = ARGV[1] local condition = ARGV[2] local expectedValue = ARGV[3] local current = redis.call('GET', key) if condition == 'eq' and current == expectedValue then return redis.call('SET', key, newValue) elseif condition == 'ne' and current ~= expectedValue then return redis.call('SET', key, newValue) elseif condition == 'nx' and not current then return redis.call('SET', key, newValue) end return nil `); const result = await conditionalSet.exec( ['mykey'], ['newValue', 'eq', 'expectedValue'] ); ``` ### Batch Operations ```typescript theme={null} const batchGet = redis.createScript>(` local result = {} for i, key in ipairs(KEYS) do result[key] = redis.call('GET', key) end return cjson.encode(result) `); const values = await batchGet.exec( ['key1', 'key2', 'key3'], [] ); ``` ### Rate Limiting ```typescript theme={null} const rateLimiter = redis.createScript<[number, number]>(` local key = KEYS[1] local limit = tonumber(ARGV[1]) local window = tonumber(ARGV[2]) local current = tonumber(redis.call('GET', key) or '0') if current < limit then local newCount = redis.call('INCR', key) if newCount == 1 then redis.call('EXPIRE', key, window) end local ttl = redis.call('TTL', key) return {newCount, ttl} else local ttl = redis.call('TTL', key) return {current, ttl} end `); const [count, ttl] = await rateLimiter.exec( ['rate:user:123'], ['10', '60'] // 10 requests per 60 seconds ); if (count <= 10) { console.log(`Request allowed. ${count}/10 used. Resets in ${ttl}s`); } else { console.log(`Rate limit exceeded. Try again in ${ttl}s`); } ``` ## Redis Functions (7.0+) ### FCALL Command Execute a loaded Redis Function: ```typescript theme={null} // First, load a function library await redis.functions.load(` #!lua name=mylib redis.register_function('hello', function(keys, args) return 'Hello ' .. args[1] end) `); // Call the function const result = await redis.functions.call( 'hello', [], // keys ['World'] // args ); console.log(result); // 'Hello World' ``` ### Read-Only Function Calls ```typescript theme={null} const result = await redis.functions.callRo( 'myReadOnlyFunc', ['key1'], ['arg1'] ); ``` ### Managing Functions ```typescript theme={null} // List all functions const functions = await redis.functions.list(); // Delete a function library await redis.functions.delete('mylib'); // Flush all functions await redis.functions.flush(); // Get function statistics const stats = await redis.functions.stats(); ``` ## Best Practices 1. **Use Script helpers for frequently executed scripts** - They provide automatic caching 2. **Keep scripts simple** - Complex logic is better handled in application code 3. **Prefer read-only variants** when possible - Better for clustering and replication 4. **Type your results** - Use TypeScript generics for type safety 5. **Handle errors gracefully** - Scripts can fail due to syntax or runtime errors ## See Also * [Redis EVAL documentation](https://redis.io/commands/eval) * [Redis Functions documentation](https://redis.io/docs/manual/programmability/functions-intro/) * [Error Handling](/advanced/error-handling) # Pub/Sub Source: https://upstash-redis-js.mintlify.app/advanced/pub-sub Real-time messaging with Redis pub/sub channels and pattern subscriptions ## Overview Redis pub/sub enables real-time messaging between different parts of your application. The Upstash Redis SDK provides a `Subscriber` class that handles channel subscriptions and message delivery over HTTP using Server-Sent Events (SSE). Pub/sub in Upstash Redis uses HTTP streaming with Server-Sent Events, not the traditional Redis protocol. This makes it compatible with serverless and edge environments. ## Basic Usage ### Subscribe to a Channel ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); // Subscribe to a single channel const subscriber = redis.subscribe('notifications'); subscriber.on('message', (data) => { console.log('Received:', data.message); console.log('Channel:', data.channel); }); ``` ### Subscribe to Multiple Channels ```typescript theme={null} const subscriber = redis.subscribe([ 'channel1', 'channel2', 'channel3' ]); subscriber.on('message', (data) => { console.log(`Message from ${data.channel}: ${data.message}`); }); ``` ### Publishing Messages ```typescript theme={null} // Publish to a channel const receiverCount = await redis.publish('notifications', 'Hello World'); console.log(`Message delivered to ${receiverCount} subscribers`); // Publish complex objects (automatically serialized) await redis.publish('events', { type: 'user.login', userId: '123', timestamp: Date.now(), }); ``` ## Type Safety Specify the message type when creating a subscriber: ```typescript theme={null} interface ChatMessage { user: string; message: string; timestamp: number; } const subscriber = redis.subscribe('chat:room1'); subscriber.on('message', (data) => { // data.message is typed as ChatMessage console.log(`${data.message.user}: ${data.message.message}`); }); ``` ## Event Listeners ### Message Events Listen for all messages across subscribed channels: ```typescript theme={null} subscriber.on('message', (data) => { console.log('Channel:', data.channel); console.log('Message:', data.message); }); ``` ### Channel-Specific Events Listen to messages from a specific channel: ```typescript theme={null} const subscriber = redis.subscribe(['news', 'sports', 'weather']); subscriber.on('message:news', (data) => { console.log('News update:', data.message); }); subscriber.on('message:sports', (data) => { console.log('Sports update:', data.message); }); ``` ### Subscription Events Handle subscription lifecycle events: ```typescript theme={null} subscriber.on('subscribe', (count) => { console.log(`Subscribed. Total subscriptions: ${count}`); }); subscriber.on('unsubscribe', (count) => { console.log(`Unsubscribed. Remaining subscriptions: ${count}`); }); ``` ### Error Events Handle errors during subscription: ```typescript theme={null} subscriber.on('error', (error) => { console.error('Subscription error:', error); }); ``` ## Pattern Subscriptions Subscribe to channels matching a pattern using `psubscribe`: ```typescript theme={null} // Subscribe to all channels starting with 'user:' const subscriber = redis.psubscribe('user:*'); subscriber.on('pmessage', (data) => { console.log('Pattern:', data.pattern); console.log('Channel:', data.channel); console.log('Message:', data.message); }); ``` ### Multiple Patterns ```typescript theme={null} const subscriber = redis.psubscribe([ 'user:*', 'order:*', 'payment:*' ]); subscriber.on('pmessage', (data) => { if (data.pattern === 'user:*') { console.log('User event:', data.message); } else if (data.pattern === 'order:*') { console.log('Order event:', data.message); } }); ``` ### Pattern-Specific Events ```typescript theme={null} const subscriber = redis.psubscribe('notification:*'); subscriber.on('pmessage:notification:*', (data) => { console.log(`Notification on ${data.channel}:`, data.message); }); ``` ## Managing Subscriptions ### Unsubscribe from Channels ```typescript theme={null} const subscriber = redis.subscribe(['channel1', 'channel2', 'channel3']); // Unsubscribe from specific channels await subscriber.unsubscribe(['channel1']); // Unsubscribe from all channels await subscriber.unsubscribe(); ``` ### Get Subscribed Channels ```typescript theme={null} const channels = subscriber.getSubscribedChannels(); console.log('Subscribed to:', channels); ``` ### Remove All Listeners ```typescript theme={null} subscriber.removeAllListeners(); ``` ## Practical Examples ### Real-Time Chat ```typescript theme={null} interface ChatMessage { user: string; message: string; timestamp: number; } const chatRoom = 'room:lobby'; // Subscribe to chat messages const subscriber = redis.subscribe(chatRoom); subscriber.on('message', (data) => { const msg = data.message; console.log(`[${new Date(msg.timestamp).toLocaleTimeString()}] ${msg.user}: ${msg.message}`); }); subscriber.on('error', (error) => { console.error('Chat error:', error); }); // Send a chat message const sendMessage = async (user: string, message: string) => { await redis.publish(chatRoom, { user, message, timestamp: Date.now(), }); }; await sendMessage('Alice', 'Hello everyone!'); await sendMessage('Bob', 'Hi Alice!'); ``` ### Notification System ```typescript theme={null} interface Notification { type: 'info' | 'warning' | 'error'; title: string; message: string; } class NotificationService { private subscriber; constructor(private redis: Redis, private userId: string) { // Subscribe to user-specific notifications this.subscriber = redis.subscribe(`notifications:${userId}`); this.setupListeners(); } private setupListeners() { this.subscriber.on('message', (data) => { const notification = data.message; this.handleNotification(notification); }); this.subscriber.on('error', (error) => { console.error('Notification error:', error); }); } private handleNotification(notification: Notification) { const icon = notification.type === 'error' ? '❌' : notification.type === 'warning' ? '⚠️' : 'ℹ️'; console.log(`${icon} ${notification.title}: ${notification.message}`); } async sendNotification(targetUserId: string, notification: Notification) { await this.redis.publish(`notifications:${targetUserId}`, notification); } async cleanup() { await this.subscriber.unsubscribe(); } } // Usage const notificationService = new NotificationService(redis, 'user123'); await notificationService.sendNotification('user123', { type: 'info', title: 'New Message', message: 'You have a new message from Bob', }); ``` ### Event Broadcasting ```typescript theme={null} interface SystemEvent { eventType: string; data: any; timestamp: number; } // Subscribe to all system events const subscriber = redis.psubscribe('events:*'); subscriber.on('pmessage', (data) => { const event = data.message; const eventCategory = data.channel.split(':')[1]; console.log(`[${eventCategory}] ${event.eventType}:`, event.data); }); // Publish different types of events await redis.publish('events:user', { eventType: 'user.login', data: { userId: '123', ip: '192.168.1.1' }, timestamp: Date.now(), }); await redis.publish('events:order', { eventType: 'order.created', data: { orderId: '456', amount: 99.99 }, timestamp: Date.now(), }); ``` ### Multi-Channel Monitoring ```typescript theme={null} const subscriber = redis.subscribe([ 'logs:error', 'logs:warning', 'logs:info' ]); subscriber.on('message:logs:error', (data) => { console.error('🔴 ERROR:', data.message); // Send alert, log to monitoring service, etc. }); subscriber.on('message:logs:warning', (data) => { console.warn('🟡 WARNING:', data.message); }); subscriber.on('message:logs:info', (data) => { console.info('🟢 INFO:', data.message); }); ``` ## Message Serialization ### Automatic Deserialization By default, messages are automatically deserialized from JSON: ```typescript theme={null} const subscriber = redis.subscribe<{ count: number }>('stats'); subscriber.on('message', (data) => { // data.message is automatically parsed console.log(data.message.count); }); await redis.publish('stats', { count: 42 }); ``` ### Disable Automatic Deserialization To receive raw message strings: ```typescript theme={null} const redisWithoutDeserialization = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, automaticDeserialization: false, }); const subscriber = redisWithoutDeserialization.subscribe('channel'); subscriber.on('message', (data) => { // data.message is a string const parsed = JSON.parse(data.message); }); ``` ## Important Notes Pub/sub messages are fire-and-forget. If a subscriber is offline when a message is published, it will not receive that message. For persistent messaging, consider using Redis Streams instead. ### Connection Management * Subscriptions use HTTP Server-Sent Events (SSE) for real-time updates * Each subscription maintains a persistent HTTP connection * Properly clean up subscriptions when done to avoid resource leaks ```typescript theme={null} // Always cleanup when done process.on('SIGINT', async () => { await subscriber.unsubscribe(); process.exit(0); }); ``` ### Performance Considerations * Pattern subscriptions (`psubscribe`) are more expensive than exact matches * Limit the number of concurrent subscriptions per client * Use channel-specific event handlers for better performance ## See Also * [Redis Pub/Sub documentation](https://redis.io/docs/manual/pubsub/) * [Error Handling](/advanced/error-handling) * [Telemetry](/advanced/telemetry) # Telemetry Source: https://upstash-redis-js.mintlify.app/advanced/telemetry Understanding and configuring telemetry data collection in Upstash Redis SDK ## Overview The Upstash Redis SDK collects anonymous telemetry data to help improve the SDK and understand usage patterns. This data includes runtime information, platform details, and SDK version. Telemetry is **enabled by default** but can be easily disabled. No sensitive data or command payloads are collected. ## What Data is Collected The SDK sends the following metadata with requests: * **Runtime**: The JavaScript runtime environment (e.g., `node@18.0.0`, `edge-light`) * **Platform**: The deployment platform (e.g., `vercel`, `aws`, `console`, or `unknown`) * **SDK version**: The version of `@upstash/redis` being used (e.g., `@upstash/redis@1.22.0`) This information is sent via HTTP headers and does not include: * Your data or Redis commands * API keys or credentials * User identifiable information * IP addresses or personal data ## Disabling Telemetry ### Via Configuration Disable telemetry when creating a Redis client: ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, enableTelemetry: false, }); ``` ### Via Environment Variable Set the `UPSTASH_DISABLE_TELEMETRY` environment variable: ```bash theme={null} export UPSTASH_DISABLE_TELEMETRY=1 ``` Then create your client normally: ```typescript theme={null} const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, // Telemetry is automatically disabled via environment variable }); ``` ### With fromEnv() When using `fromEnv()`, telemetry respects the environment variable: ```typescript theme={null} const redis = Redis.fromEnv({ enableTelemetry: false, // Explicitly disable }); // Or rely on UPSTASH_DISABLE_TELEMETRY env var const redis = Redis.fromEnv(); ``` ## How Telemetry Works ### Runtime Detection The SDK automatically detects your runtime environment: ```typescript theme={null} // Detection logic from platforms/nodejs.ts const runtime = typeof EdgeRuntime === 'string' ? 'edge-light' : nodeVersion ? `node@${nodeVersion}` : 'unknown'; ``` Possible runtime values: * `edge-light` - Edge runtime (Vercel Edge, Cloudflare Workers, etc.) * `node@{version}` - Node.js runtime with version * `unknown` - Unknown runtime ### Platform Detection The SDK detects the deployment platform from environment variables: ```typescript theme={null} // Detection logic from platforms/nodejs.ts const platform = process.env.UPSTASH_CONSOLE ? 'console' : process.env.VERCEL ? 'vercel' : process.env.AWS_REGION ? 'aws' : 'unknown'; ``` Possible platform values: * `console` - Running in Upstash console * `vercel` - Deployed on Vercel * `aws` - Deployed on AWS (Lambda, etc.) * `unknown` - Unknown or local environment ### Telemetry Headers Telemetry data is sent via the `Upstash-Telemetry-*` HTTP headers: ``` Upstash-Telemetry-Runtime: node@18.0.0 Upstash-Telemetry-Platform: vercel Upstash-Telemetry-Sdk: @upstash/redis@1.22.0 ``` ## Implementation Details ### Telemetry Configuration The telemetry flag is checked during client initialization: ```typescript theme={null} // From pkg/redis.ts const enableTelemetry = opts?.enableTelemetry ?? !safeEnv.UPSTASH_DISABLE_TELEMETRY; ``` Priority order: 1. Explicit `enableTelemetry` option in config 2. `UPSTASH_DISABLE_TELEMETRY` environment variable 3. Default: **enabled** ### Adding Telemetry Data The SDK adds telemetry during initialization: ```typescript theme={null} // From pkg/redis.ts this.addTelemetry({ runtime: typeof EdgeRuntime === 'string' ? 'edge-light' : nodeVersion ? `node@${nodeVersion}` : 'unknown', platform: safeEnv.UPSTASH_CONSOLE ? 'console' : safeEnv.VERCEL ? 'vercel' : safeEnv.AWS_REGION ? 'aws' : 'unknown', sdk: `@upstash/redis@${VERSION}`, }); ``` ### Telemetry with Custom Requester If you provide a custom `Requester`, telemetry will still work if your requester implements the `mergeTelemetry` method: ```typescript theme={null} import { Requester, UpstashRequest, UpstashResponse } from '@upstash/redis'; const customRequester: Requester = { request: async (req: UpstashRequest): Promise> => { // Your custom request logic }, mergeTelemetry: (telemetry) => { // Merge telemetry data into request headers } }; const redis = new Redis(customRequester); ``` ## Examples ### Disable for Development ```typescript theme={null} const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, enableTelemetry: process.env.NODE_ENV === 'production', }); ``` ### Environment-Based Configuration ```bash theme={null} # .env.production UPSTASH_REDIS_REST_URL=https://... UPSTASH_REDIS_REST_TOKEN=... # Telemetry enabled by default # .env.development UPSTASH_REDIS_REST_URL=https://... UPSTASH_REDIS_REST_TOKEN=... UPSTASH_DISABLE_TELEMETRY=1 ``` ### Conditional Telemetry ```typescript theme={null} const enableTelemetry = !process.env.CI && process.env.NODE_ENV === 'production'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, enableTelemetry, }); ``` ## Privacy Considerations The Upstash Redis SDK is committed to user privacy: * **No data collection**: Your Redis data and commands are never collected * **No tracking**: No unique identifiers or tracking cookies * **Anonymous**: Runtime and platform info cannot identify individual users * **Transparent**: This page documents exactly what is collected * **Easy to disable**: Multiple ways to opt-out ## Why Telemetry? Telemetry helps us: 1. **Prioritize platform support** - Understand which runtimes and platforms to focus on 2. **Identify compatibility issues** - Detect problems with specific runtime versions 3. **Measure adoption** - Track SDK version distribution to plan deprecations 4. **Improve developer experience** - Make data-driven decisions about features ## FAQs ### Does telemetry affect performance? No. Telemetry data is included as HTTP headers and adds negligible overhead (\< 1KB). ### Can I see the telemetry data being sent? Yes. Enable debug logging or use network inspection tools to see the HTTP headers: ```bash theme={null} # Using curl with verbose output curl -v https://your-redis-url.upstash.io/get/mykey \ -H "Authorization: Bearer YOUR_TOKEN" ``` ### Does disabling telemetry affect functionality? No. The SDK works exactly the same with or without telemetry enabled. ### Is telemetry sent for every request? Yes, telemetry headers are included with every HTTP request when enabled. ## See Also * [Configuration Options](/concepts/client-configuration) * [Error Handling](/advanced/error-handling) * [Environment Variables](https://docs.upstash.com/redis/features/restapi) # Transactions Source: https://upstash-redis-js.mintlify.app/advanced/transactions Execute multiple commands atomically using MULTI/EXEC transactions ## Overview Transactions in Upstash Redis allow you to execute multiple commands atomically. All commands in a transaction are serialized and executed sequentially, guaranteeing that they run as a single isolated operation. The `multi()` method creates a transaction pipeline. Unlike regular pipelines, transactions guarantee atomicity - either all commands succeed or none do. ## Creating a Transaction Use the `multi()` method to create a transaction: ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); const transaction = redis.multi(); ``` ## Basic Usage ### Chaining Commands You can chain commands together and execute them atomically: ```typescript theme={null} const result = await redis.multi() .set('user:1:name', 'Alice') .set('user:1:email', 'alice@example.com') .set('user:1:age', 30) .exec(); console.log(result); // ['OK', 'OK', 'OK'] ``` ### Building Transactions Dynamically You can build transactions incrementally before executing: ```typescript theme={null} const transaction = redis.multi(); transaction.set('key1', 'value1'); transaction.incr('counter'); transaction.get('key1'); const results = await transaction.exec(); console.log(results); // ['OK', 1, 'value1'] ``` ## Type Safety When chaining commands, TypeScript can infer return types: ```typescript theme={null} const [setResult, incrResult, getResult] = await redis.multi() .set('key', 'value') .incr('counter') .get('key') .exec(); // setResult: 'OK' // incrResult: number // getResult: string | null ``` For dynamic transactions, you can specify types manually: ```typescript theme={null} const transaction = redis.multi(); transaction.get('user:name'); transaction.get('user:age'); const results = await transaction.exec<[string | null, number | null]>(); const [name, age] = results; ``` ## Error Handling ### Default Behavior By default, if any command fails, the entire transaction throws an error: ```typescript theme={null} try { await redis.multi() .set('key', 'value') .hget('key', 'field') // This will fail - key is a string, not a hash .exec(); } catch (error) { console.error('Transaction failed:', error); } ``` ### Keep Errors Mode To handle errors individually, use the `keepErrors` option: ```typescript theme={null} const results = await redis.multi() .set('key', 'value') .hget('key', 'field') // This will fail .get('key') .exec({ keepErrors: true }); results.forEach((item, index) => { if (item.error) { console.error(`Command ${index} failed:`, item.error); } else { console.log(`Command ${index} result:`, item.result); } }); ``` ## Practical Examples ### Atomic Counter Update ```typescript theme={null} async function incrementUserScore(userId: string, points: number) { const [newScore, timestamp] = await redis.multi() .incrby(`user:${userId}:score`, points) .set(`user:${userId}:last_update`, Date.now()) .exec(); return { score: newScore, updated: timestamp }; } ``` ### Batch User Creation ```typescript theme={null} interface User { id: string; name: string; email: string; created: number; } async function createUser(user: User) { const transaction = redis.multi(); transaction.hset(`user:${user.id}`, { name: user.name, email: user.email, created: user.created, }); transaction.sadd('users:all', user.id); transaction.sadd(`users:by_email:${user.email}`, user.id); const [hashResult, addToAll, addToEmail] = await transaction.exec(); return hashResult === 1; // Returns true if user was created } ``` ### Transfer Between Accounts ```typescript theme={null} async function transferFunds(fromAccount: string, toAccount: string, amount: number) { const results = await redis.multi() .decrby(`account:${fromAccount}:balance`, amount) .incrby(`account:${toAccount}:balance`, amount) .lpush('transactions', JSON.stringify({ from: fromAccount, to: toAccount, amount, timestamp: Date.now(), })) .exec(); const [fromBalance, toBalance, _] = results; return { fromBalance, toBalance, }; } ``` ### Complex Data Updates ```typescript theme={null} async function updateProduct(productId: string, updates: Record) { const transaction = redis.multi(); // Update product data transaction.hset(`product:${productId}`, updates); // Update last modified timestamp transaction.hset(`product:${productId}`, { lastModified: Date.now() }); // Increment version transaction.hincrby(`product:${productId}`, 'version', 1); // Add to updated products set transaction.sadd('products:recently_updated', productId); const [updateResult, timestampResult, version, addResult] = await transaction.exec(); return { version, updated: true }; } ``` ## Transaction Length You can check how many commands are in a transaction before executing: ```typescript theme={null} const transaction = redis.multi(); for (let i = 0; i < 10; i++) { transaction.set(`key:${i}`, i); } console.log(transaction.length()); // 10 const results = await transaction.exec(); console.log(transaction.length()); // Still 10 after execution ``` ## Important Notes Transactions guarantee atomicity but **not** isolation during execution. Other clients' commands can interleave with individual commands within the transaction. For conditional execution based on watched keys, use Redis WATCH/MULTI/EXEC pattern (not directly supported via REST API). ### Differences from Redis MULTI/EXEC * Commands are executed via HTTP REST API, not native Redis protocol * All commands are sent in a single HTTP request to the `/multi-exec` endpoint * WATCH/DISCARD commands are not supported * Commands execute atomically on the server side ## See Also * [Pipelines](/api/pipeline) - Send multiple commands without atomicity guarantees * [Error Handling](/advanced/error-handling) - Handle errors in your Redis operations # append Source: https://upstash-redis-js.mintlify.app/api/commands/append Append a value to a key ## Description If key already exists and is a string, this command appends the value at the end of the string. If key does not exist, it is created and set as an empty string, so APPEND will be similar to SET in this special case. ## Syntax ```typescript theme={null} redis.append(key: string, value: string): Promise ``` ## Parameters The key to append to The string value to append ## Returns The length of the string after the append operation ## Examples ### Basic Usage ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: 'https://your-redis-url.upstash.io', token: 'your-token' }); // Append to a new key const length1 = await redis.append('message', 'Hello'); console.log(length1); // 5 const length2 = await redis.append('message', ' World'); console.log(length2); // 11 const value = await redis.get('message'); console.log(value); // "Hello World" ``` ### Building Strings Incrementally ```typescript theme={null} // Build a log message incrementally await redis.append('log:request:123', '[2024-03-03] '); await redis.append('log:request:123', 'User logged in '); await redis.append('log:request:123', 'from IP 192.168.1.1'); const log = await redis.get('log:request:123'); console.log(log); // "[2024-03-03] User logged in from IP 192.168.1.1" ``` ### Append to Non-Existent Key ```typescript theme={null} // Appending to a non-existent key creates it const length = await redis.append('newkey', 'First value'); console.log(length); // 11 const value = await redis.get('newkey'); console.log(value); // "First value" ``` ### Building CSV-like Data ```typescript theme={null} // Create a simple CSV row await redis.append('csv:row:1', 'Alice'); await redis.append('csv:row:1', ',30'); await redis.append('csv:row:1', ',Engineer'); const row = await redis.get('csv:row:1'); console.log(row); // "Alice,30,Engineer" ``` ## Notes * APPEND is very efficient for building strings incrementally * The time complexity is O(1), assuming the appended value is small and the already present value is of any size * If the key exists but is not a string, an error is returned ## Related Commands * [set](/api/commands/set) - Set the string value of a key * [get](/api/commands/get) - Get the value of a key * [setrange](/api/commands/setrange) - Overwrite part of a string at a specific offset * [getrange](/api/commands/getrange) - Get a substring of the string stored at a key ## Redis Documentation For more information, see the [Redis APPEND documentation](https://redis.io/commands/append). # dbsize Source: https://upstash-redis-js.mintlify.app/api/commands/dbsize Get the number of keys in the database ## Description Returns the number of keys in the currently selected database. ## Method Signature ```typescript theme={null} dbsize(): Promise ``` ## Parameters This command takes no parameters. ## Return Value The number of keys in the database ## Examples ### Get database size ```typescript theme={null} const size = await redis.dbsize(); console.log(`Database contains ${size} keys`); ``` ### Monitor database growth ```typescript theme={null} const before = await redis.dbsize(); console.log(`Keys before: ${before}`); await redis.set("key1", "value1"); await redis.set("key2", "value2"); await redis.set("key3", "value3"); const after = await redis.dbsize(); console.log(`Keys after: ${after}`); console.log(`Added ${after - before} keys`); ``` ### Check if database is empty ```typescript theme={null} const size = await redis.dbsize(); if (size === 0) { console.log("Database is empty"); } else { console.log(`Database has ${size} keys`); } ``` ### Track deletes ```typescript theme={null} await redis.set("temp1", "value"); await redis.set("temp2", "value"); await redis.set("temp3", "value"); const before = await redis.dbsize(); await redis.del("temp1", "temp2", "temp3"); const after = await redis.dbsize(); console.log(`Deleted ${before - after} keys`); ``` ## Performance `DBSIZE` is an O(1) operation - it returns the count instantly without scanning the keyspace. ## See Also * [keys](/api/commands/keys) - Find all keys matching a pattern * [scan](/api/commands/scan) - Incrementally iterate over keys * [flushdb](/api/commands/flushdb) - Delete all keys in the database # decr Source: https://upstash-redis-js.mintlify.app/api/commands/decr Decrement the integer value of a key by one ## Description Decrements the number stored at key by one. If the key does not exist, it is set to 0 before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that cannot be represented as an integer. ## Syntax ```typescript theme={null} redis.decr(key: string): Promise ``` ## Parameters The key containing the integer to decrement ## Returns The value of the key after the decrement ## Examples ### Basic Usage ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: 'https://your-redis-url.upstash.io', token: 'your-token' }); // Set an initial value await redis.set('counter', 10); // Decrement by 1 const newValue = await redis.decr('counter'); console.log(newValue); // 9 const value = await redis.get('counter'); console.log(value); // "9" ``` ### Decrement Non-Existent Key ```typescript theme={null} // Decrementing a non-existent key initializes it to 0 and then decrements const result = await redis.decr('newcounter'); console.log(result); // -1 ``` ### Multiple Decrements ```typescript theme={null} await redis.set('inventory', 100); // Decrement multiple times await redis.decr('inventory'); // 99 await redis.decr('inventory'); // 98 await redis.decr('inventory'); // 97 const remaining = await redis.get('inventory'); console.log(remaining); // "97" ``` ### Inventory Management ```typescript theme={null} // Track product inventory async function sellProduct(productId: string, quantity: number = 1) { for (let i = 0; i < quantity; i++) { const remaining = await redis.decr(`product:${productId}:stock`); if (remaining < 0) { // Rollback - increment back await redis.incr(`product:${productId}:stock`); throw new Error('Out of stock'); } } } // Initialize stock await redis.set('product:123:stock', 5); // Sell 2 items await sellProduct('123', 2); const stock = await redis.get('product:123:stock'); console.log(stock); // "3" ``` ### Countdown Timer ```typescript theme={null} // Set up a countdown await redis.set('countdown', 10); const interval = setInterval(async () => { const remaining = await redis.decr('countdown'); console.log(`${remaining} seconds remaining`); if (remaining <= 0) { clearInterval(interval); console.log('Countdown complete!'); } }, 1000); ``` ### Rate Limiting (Remaining Credits) ```typescript theme={null} // Initialize user credits await redis.set('user:123:credits', 100); async function useCredit(userId: string): Promise { const remaining = await redis.decr(`user:${userId}:credits`); if (remaining < 0) { // Rollback and throw error await redis.incr(`user:${userId}:credits`); throw new Error('Insufficient credits'); } return remaining; } try { const credits = await useCredit('123'); console.log(`${credits} credits remaining`); } catch (error) { console.error(error.message); } ``` ### Download Counter ```typescript theme={null} // Track remaining downloads async function trackDownload(fileId: string) { const remaining = await redis.decr(`file:${fileId}:downloads:remaining`); if (remaining < 0) { await redis.incr(`file:${fileId}:downloads:remaining`); return { allowed: false, remaining: 0 }; } return { allowed: true, remaining }; } // Set initial download limit await redis.set('file:abc:downloads:remaining', 3); const result1 = await trackDownload('abc'); console.log(result1); // { allowed: true, remaining: 2 } const result2 = await trackDownload('abc'); console.log(result2); // { allowed: true, remaining: 1 } ``` ### Seat Reservation System ```typescript theme={null} // Initialize available seats await redis.set('event:concert:seats', 100); async function reserveSeat(eventId: string): Promise<{ success: boolean; seatsLeft: number }> { const seatsLeft = await redis.decr(`event:${eventId}:seats`); if (seatsLeft < 0) { // No seats available, rollback await redis.incr(`event:${eventId}:seats`); return { success: false, seatsLeft: 0 }; } return { success: true, seatsLeft }; } const reservation = await reserveSeat('concert'); if (reservation.success) { console.log(`Seat reserved! ${reservation.seatsLeft} seats remaining`); } else { console.log('Sorry, event is sold out'); } ``` ## Notes * This is an atomic operation, making it safe for concurrent decrements * The value must be an integer or a string that can be parsed as an integer * If the key contains a non-integer value, an error will be returned * The operation has O(1) time complexity * The range of values is limited to 64-bit signed integers * Decrementing below zero is allowed (will result in negative numbers) ## Error Handling ```typescript theme={null} try { // This will throw an error because "hello" is not a valid integer await redis.set('mykey', 'hello'); await redis.decr('mykey'); } catch (error) { console.error('Cannot decrement non-integer value'); } ``` ## Related Commands * [incr](/api/commands/incr) - Increment the integer value of a key by one ## Redis Documentation For more information, see the [Redis DECR documentation](https://redis.io/commands/decr). # del Source: https://upstash-redis-js.mintlify.app/api/commands/del Delete one or more keys ## Description Removes the specified keys. A key is ignored if it does not exist. ## Method Signature ```typescript theme={null} del(...keys: string[]): Promise ``` ## Parameters One or more keys to delete ## Return Value The number of keys that were removed ## Examples ### Delete a single key ```typescript theme={null} await redis.set("mykey", "value"); const deleted = await redis.del("mykey"); console.log(deleted); // 1 ``` ### Delete multiple keys ```typescript theme={null} await redis.set("key1", "value1"); await redis.set("key2", "value2"); await redis.set("key3", "value3"); const deleted = await redis.del("key1", "key2", "key3"); console.log(deleted); // 3 ``` ### Delete non-existent keys ```typescript theme={null} const deleted = await redis.del("nonexistent"); console.log(deleted); // 0 ``` ## See Also * [exists](/api/commands/exists) - Check if keys exist # echo Source: https://upstash-redis-js.mintlify.app/api/commands/echo Echo a message ## Description Returns the provided message. This command is useful for testing and debugging. ## Method Signature ```typescript theme={null} echo(message: string): Promise ``` ## Parameters The message to echo back ## Return Value The exact message that was sent ## Examples ### Basic echo ```typescript theme={null} const response = await redis.echo("Hello, Redis!"); console.log(response); // "Hello, Redis!" ``` ### Echo with special characters ```typescript theme={null} const response = await redis.echo("Line 1\nLine 2\nLine 3"); console.log(response); // Output: // Line 1 // Line 2 // Line 3 ``` ### Test connection with custom message ```typescript theme={null} const testMessage = `Test at ${new Date().toISOString()}`; const response = await redis.echo(testMessage); console.log(response === testMessage); // true ``` ### Echo empty string ```typescript theme={null} const response = await redis.echo(""); console.log(response); // "" console.log(response.length); // 0 ``` ## See Also * [ping](/api/commands/ping) - Test server connection # EVAL Source: https://upstash-redis-js.mintlify.app/api/commands/eval Execute a Lua script on the Redis server ## Usage ```typescript theme={null} await redis.eval(script, keys, args); ``` Executes a Lua script on the Redis server. The script can access keys and arguments passed to it. ## Parameters The Lua script to execute. Array of key names that the script will access. These are available in the script as `KEYS[1]`, `KEYS[2]`, etc. Array of arguments to pass to the script. These are available in the script as `ARGV[1]`, `ARGV[2]`, etc. ## Response The value returned by the Lua script. The type depends on what the script returns. ## Examples ### Basic Script Execution ```typescript theme={null} const result = await redis.eval( "return ARGV[1]", [], ["Hello World"] ); console.log(result); // "Hello World" ``` ### Incrementing a Counter ```typescript theme={null} const script = ` local current = redis.call('GET', KEYS[1]) if current == false then current = 0 end local next = current + tonumber(ARGV[1]) redis.call('SET', KEYS[1], next) return next `; const newValue = await redis.eval( script, ["counter"], ["5"] ); console.log(newValue); // 5 (or current value + 5) ``` ### Conditional Set ```typescript theme={null} const script = ` local current = redis.call('GET', KEYS[1]) if current == ARGV[1] then redis.call('SET', KEYS[1], ARGV[2]) return 1 else return 0 end `; const updated = await redis.eval( script, ["mykey"], ["oldvalue", "newvalue"] ); console.log(updated); // 1 if updated, 0 if not ``` ### Working with Multiple Keys ```typescript theme={null} const script = ` local val1 = redis.call('GET', KEYS[1]) local val2 = redis.call('GET', KEYS[2]) return {val1, val2} `; const values = await redis.eval( script, ["key1", "key2"], [] ); console.log(values); // [value1, value2] ``` ## EVAL vs EVAL\_RO ### EVAL The `eval` command can execute scripts that modify data: ```typescript theme={null} const script = ` redis.call('SET', KEYS[1], ARGV[1]) return 'OK' `; const result = await redis.eval( script, ["mykey"], ["myvalue"] ); ``` ### EVAL\_RO (Read-Only) Use `evalRo` for read-only scripts that don't modify data. This is more efficient for read replicas: ```typescript theme={null} const script = ` return redis.call('GET', KEYS[1]) `; const result = await redis.evalRo( script, ["mykey"], [] ); ``` Read-only scripts: * Can be executed on read replicas * Are optimized for read-heavy workloads * Will error if they attempt to modify data ## Using the Script Class For better performance with frequently used scripts, use the `Script` class which automatically handles SHA-1 caching: ```typescript theme={null} const script = redis.createScript(` local current = redis.call('GET', KEYS[1]) or 0 local next = current + tonumber(ARGV[1]) redis.call('SET', KEYS[1], next) return next `); // First call uses EVAL, subsequent calls use EVALSHA const result1 = await script.exec(["counter"], ["1"]); const result2 = await script.exec(["counter"], ["1"]); ``` The `Script` class provides three methods: * `eval(keys, args)` - Always uses EVAL * `evalsha(keys, args)` - Always uses EVALSHA (requires script to be loaded) * `exec(keys, args)` - Optimistically tries EVALSHA, falls back to EVAL if needed For read-only scripts, use `createScript` with the `readOnly` option: ```typescript theme={null} const script = redis.createScript( "return redis.call('GET', KEYS[1])", { readOnly: true } ); const result = await script.exec(["mykey"], []); ``` ## See Also * [EVALSHA](/api/commands/evalsha) - Execute a cached script by SHA-1 hash * [SCRIPT LOAD](/api/commands/script-load) - Load a script into the server cache * [FCALL](/api/commands/fcall) - Call a Redis function # EVALSHA Source: https://upstash-redis-js.mintlify.app/api/commands/evalsha Execute a cached Lua script by its SHA-1 hash ## Usage ```typescript theme={null} await redis.evalsha(sha1, keys, args); ``` Executes a Lua script that has been previously loaded into Redis. Instead of sending the entire script, you only send the SHA-1 hash of the script, which is more efficient for frequently executed scripts. ## Parameters The SHA-1 hash of the script to execute. Obtain this by using [SCRIPT LOAD](/api/commands/script-load) or by computing the SHA-1 hash of your script. Array of key names that the script will access. These are available in the script as `KEYS[1]`, `KEYS[2]`, etc. Array of arguments to pass to the script. These are available in the script as `ARGV[1]`, `ARGV[2]`, etc. ## Response The value returned by the Lua script. The type depends on what the script returns. ## Examples ### Load and Execute a Script ```typescript theme={null} const script = ` local current = redis.call('GET', KEYS[1]) or 0 local next = current + tonumber(ARGV[1]) redis.call('SET', KEYS[1], next) return next `; // First, load the script and get its SHA-1 hash const sha1 = await redis.scriptLoad(script); console.log(sha1); // e.g., "a42059b356c875f0717db19a51f6aaca9ae659ea" // Now execute it using the hash const result = await redis.evalsha( sha1, ["counter"], ["5"] ); console.log(result); // 5 (or current value + 5) // Subsequent calls are more efficient const result2 = await redis.evalsha(sha1, ["counter"], ["3"]); console.log(result2); // previous value + 3 ``` ### Handling Missing Scripts If the script isn't loaded, EVALSHA will fail with a NOSCRIPT error: ```typescript theme={null} try { const result = await redis.evalsha( "nonexistent_sha", ["key"], ["arg"] ); } catch (error) { if (error.message.includes("NOSCRIPT")) { // Script not loaded, use EVAL instead or load it first const sha1 = await redis.scriptLoad(script); const result = await redis.evalsha(sha1, ["key"], ["arg"]); } } ``` ### Conditional Operations ```typescript theme={null} const compareAndSetScript = ` local current = redis.call('GET', KEYS[1]) if current == ARGV[1] then redis.call('SET', KEYS[1], ARGV[2]) return 1 else return 0 end `; const sha1 = await redis.scriptLoad(compareAndSetScript); // Try to update if current value matches const updated = await redis.evalsha( sha1, ["mykey"], ["expected_value", "new_value"] ); if (updated === 1) { console.log("Successfully updated"); } else { console.log("Value didn't match, not updated"); } ``` ## EVALSHA vs EVALSHA\_RO ### EVALSHA The `evalsha` command can execute scripts that modify data: ```typescript theme={null} const script = ` redis.call('SET', KEYS[1], ARGV[1]) return 'OK' `; const sha1 = await redis.scriptLoad(script); const result = await redis.evalsha( sha1, ["mykey"], ["myvalue"] ); ``` ### EVALSHA\_RO (Read-Only) Use `evalshaRo` for read-only scripts that don't modify data: ```typescript theme={null} const script = ` return redis.call('GET', KEYS[1]) `; const sha1 = await redis.scriptLoad(script); const result = await redis.evalshaRo( sha1, ["mykey"], [] ); ``` Read-only scripts: * Can be executed on read replicas * Are optimized for read-heavy workloads * Will error if they attempt to modify data ## Using the Script Class The SDK provides a `Script` class that automatically handles SHA-1 computation and caching: ```typescript theme={null} const script = redis.createScript(` local current = redis.call('GET', KEYS[1]) or 0 local next = current + tonumber(ARGV[1]) redis.call('SET', KEYS[1], next) return next `); // Use evalsha method - assumes script is loaded const result = await script.evalsha(["counter"], ["1"]); // Or use exec method - optimistically tries EVALSHA, falls back to EVAL const result2 = await script.exec(["counter"], ["1"]); ``` The `exec` method is recommended as it: 1. First tries EVALSHA (fast, using cached script) 2. If script not found, falls back to EVAL 3. Subsequent calls will use EVALSHA For read-only scripts: ```typescript theme={null} const script = redis.createScript( "return redis.call('GET', KEYS[1])", { readOnly: true } ); // Uses EVALSHA_RO const result = await script.evalshaRo(["mykey"], []); ``` ## Benefits of EVALSHA 1. **Reduced bandwidth**: Only the SHA-1 hash is sent instead of the entire script 2. **Better performance**: Script is pre-compiled and cached on the server 3. **Ideal for frequently executed scripts**: Especially beneficial for scripts executed many times ## See Also * [EVAL](/api/commands/eval) - Execute a Lua script * [SCRIPT LOAD](/api/commands/script-load) - Load a script into the server cache # exists Source: https://upstash-redis-js.mintlify.app/api/commands/exists Check if one or more keys exist ## Description Returns the number of keys that exist from those specified as arguments. ## Method Signature ```typescript theme={null} exists(...keys: string[]): Promise ``` ## Parameters One or more keys to check for existence ## Return Value The number of keys that exist. If the same existing key is mentioned in the arguments multiple times, it will be counted multiple times. ## Examples ### Check single key existence ```typescript theme={null} await redis.set("mykey", "value"); const exists = await redis.exists("mykey"); console.log(exists); // 1 const notExists = await redis.exists("nonexistent"); console.log(notExists); // 0 ``` ### Check multiple keys ```typescript theme={null} await redis.set("key1", "value1"); await redis.set("key2", "value2"); const exists = await redis.exists("key1", "key2", "key3"); console.log(exists); // 2 (key1 and key2 exist, key3 does not) ``` ### Duplicate keys are counted multiple times ```typescript theme={null} await redis.set("mykey", "value"); const exists = await redis.exists("mykey", "mykey", "mykey"); console.log(exists); // 3 ``` ## See Also * [del](/api/commands/del) - Delete keys * [keys](/api/commands/keys) - Find keys matching a pattern # expire Source: https://upstash-redis-js.mintlify.app/api/commands/expire Set a key's time to live in seconds ## Description Set a timeout on a key. After the timeout has expired, the key will automatically be deleted. ## Method Signature ```typescript theme={null} expire(key: string, seconds: number, option?: ExpireOption): Promise<0 | 1> ``` ## Parameters The key to set expiration on The number of seconds until the key expires Optional modifier for the expiration behavior: * `"NX"` - Set expiry only when the key has no expiry * `"XX"` - Set expiry only when the key has an existing expiry * `"GT"` - Set expiry only when the new expiry is greater than current one * `"LT"` - Set expiry only when the new expiry is less than current one Case-insensitive (can use lowercase: `"nx"`, `"xx"`, `"gt"`, `"lt"`) ## Return Value * `1` if the timeout was set * `0` if the timeout was not set (key doesn't exist or condition not met) ## Examples ### Basic expiration ```typescript theme={null} await redis.set("mykey", "value"); const result = await redis.expire("mykey", 10); console.log(result); // 1 // Key will be deleted after 10 seconds ``` ### Set expiry only if key has no expiry (NX) ```typescript theme={null} await redis.set("mykey", "value"); const result1 = await redis.expire("mykey", 100, "NX"); console.log(result1); // 1 (set because key had no expiry) const result2 = await redis.expire("mykey", 50, "NX"); console.log(result2); // 0 (not set because key already has expiry) ``` ### Set expiry only if new expiry is greater (GT) ```typescript theme={null} await redis.set("mykey", "value"); await redis.expire("mykey", 100); const result1 = await redis.expire("mykey", 200, "GT"); console.log(result1); // 1 (200 > 100) const result2 = await redis.expire("mykey", 50, "GT"); console.log(result2); // 0 (50 < 200) ``` ### Non-existent key ```typescript theme={null} const result = await redis.expire("nonexistent", 10); console.log(result); // 0 ``` ## See Also * [ttl](/api/commands/ttl) - Get the time to live for a key * [persist](/api/commands/persist) - Remove the expiration from a key # FCALL Source: https://upstash-redis-js.mintlify.app/api/commands/fcall Call a Redis function by name ## Usage ```typescript theme={null} await redis.fcall(functionName, keys, args); ``` Invokes a Redis function that has been loaded into the server. Redis functions are similar to Lua scripts but offer better organization, reusability, and management capabilities. ## Parameters The name of the function to call. The function must be loaded on the server. Array of key names that the function will access. These are available in the function as `KEYS[1]`, `KEYS[2]`, etc. Array of arguments to pass to the function. These are available in the function as `ARGV[1]`, `ARGV[2]`, etc. ## Response The value returned by the Redis function. The type depends on what the function returns. ## Examples ### Calling a Simple Function ```typescript theme={null} // Assume a function "hello" is loaded that returns a greeting const result = await redis.fcall( "hello", [], ["World"] ); console.log(result); // "Hello, World!" ``` ### Counter Function ```typescript theme={null} // Assume a function "increment_counter" is loaded const newValue = await redis.fcall( "increment_counter", ["my_counter"], ["5"] ); console.log(newValue); // Current value + 5 ``` ### Working with Multiple Keys ```typescript theme={null} // Assume a function "sum_values" is loaded that sums multiple keys const total = await redis.fcall( "sum_values", ["value1", "value2", "value3"], [] ); console.log(total); ``` ### Passing Arguments ```typescript theme={null} // Assume a function "set_with_metadata" is loaded const result = await redis.fcall( "set_with_metadata", ["user:123"], [ "John Doe", "john@example.com", new Date().toISOString() ] ); ``` ### No Keys or Arguments ```typescript theme={null} // Call a function with no keys or arguments const result = await redis.fcall( "get_server_time", [], [] ); // Or omit the optional parameters const result2 = await redis.fcall("get_server_time"); ``` ## FCALL vs FCALL\_RO ### FCALL The `fcall` command can execute functions that modify data: ```typescript theme={null} const result = await redis.fcall( "update_user", ["user:123"], ["new_name", "new_email"] ); ``` ### FCALL\_RO (Read-Only) Use `fcallRo` for read-only functions that don't modify data: ```typescript theme={null} const result = await redis.fcallRo( "get_user", ["user:123"], [] ); ``` Read-only functions: * Can be executed on read replicas * Are optimized for read-heavy workloads * Will error if they attempt to modify data * Provide better performance in distributed setups ### Example: Choosing the Right Command ```typescript theme={null} // Use fcall for writes const updated = await redis.fcall( "increment_counter", ["page_views"], ["1"] ); // Use fcallRo for reads const current = await redis.fcallRo( "get_counter", ["page_views"], [] ); ``` ## Redis Functions vs Lua Scripts ### Functions (FCALL) Advantages: * **Named and organized**: Functions have names and can be grouped into libraries * **Persistent**: Functions survive server restarts (stored in RDB/AOF) * **Versioned**: Can replace functions without affecting running code * **Better management**: Use `FUNCTION LIST`, `FUNCTION DELETE`, etc. ### Scripts (EVAL/EVALSHA) Advantages: * **Simpler**: No need to manage function libraries * **More widely supported**: Available in older Redis versions * **Ad-hoc execution**: Good for one-off scripts ### When to Use FCALL Use FCALL when: * You have reusable logic used across your application * You want functions to persist across server restarts * You need better organization and management of server-side code * You're building a complex application with many server-side operations ### When to Use EVAL Use EVAL when: * You need a one-off script * You're working with older Redis versions * You want simpler deployment (no function management) * You're prototyping or testing ## Loading Functions Before calling a function, it must be loaded on the server. Here's an example using the Redis CLI: ```bash theme={null} # Load a function library redis-cli FUNCTION LOAD "#!lua name=mylib redis.register_function('hello', function(keys, args) return 'Hello, ' .. args[1] .. '!' end)" ``` From Node.js, you would typically load functions during application initialization: ```typescript theme={null} // Note: The SDK may not have a direct FUNCTION LOAD wrapper // You might need to use a raw command or the Redis CLI // Then call the function const result = await redis.fcall( "hello", [], ["World"] ); ``` ## Error Handling ```typescript theme={null} try { const result = await redis.fcall( "my_function", ["key1"], ["arg1"] ); } catch (error) { if (error.message.includes("no such function")) { console.error("Function not loaded on server"); // Load the function or handle the error } else { console.error("Function execution error:", error); } } ``` ## Best Practices 1. **Use descriptive function names**: `increment_user_counter` instead of `inc` 2. **Load functions at startup**: Ensure all required functions are loaded when your application starts 3. **Use FCALL\_RO for reads**: Take advantage of read replicas for better performance 4. **Handle errors gracefully**: Check if functions are loaded before calling them 5. **Document your functions**: Keep documentation of what each function does and its parameters 6. **Version your functions**: When updating functions, consider versioning strategies ## See Also * [EVAL](/api/commands/eval) - Execute a Lua script * [EVALSHA](/api/commands/evalsha) - Execute a cached script by SHA-1 hash # flushdb Source: https://upstash-redis-js.mintlify.app/api/commands/flushdb Delete all keys in the current database ## Description Delete all the keys in the currently selected database. This command never fails. This is a destructive operation that cannot be undone. Use with extreme caution, especially in production environments. ## Method Signature ```typescript theme={null} flushdb(opts?: { async?: boolean }): Promise<"OK"> ``` ## Parameters Optional configuration object: When `true`, performs the flush asynchronously. The command returns immediately and the deletion happens in the background. This is useful for large databases to avoid blocking the server. ## Return Value Always returns `"OK"` ## Examples ### Flush database synchronously ```typescript theme={null} await redis.set("key1", "value1"); await redis.set("key2", "value2"); const result = await redis.flushdb(); console.log(result); // "OK" const size = await redis.dbsize(); console.log(size); // 0 ``` ### Flush database asynchronously ```typescript theme={null} const result = await redis.flushdb({ async: true }); console.log(result); // "OK" // Deletion happens in background // Database may not be empty immediately ``` ### Clear test data ```typescript theme={null} import { beforeEach } from "vitest"; beforeEach(async () => { // Clean database before each test await redis.flushdb(); }); ``` ### Safe flush with confirmation ```typescript theme={null} const confirmFlush = process.env.CONFIRM_FLUSH === "true"; if (confirmFlush) { await redis.flushdb(); console.log("Database flushed"); } else { console.log("Flush cancelled - set CONFIRM_FLUSH=true to proceed"); } ``` ### Check size before and after ```typescript theme={null} const before = await redis.dbsize(); console.log(`Keys before flush: ${before}`); await redis.flushdb(); const after = await redis.dbsize(); console.log(`Keys after flush: ${after}`); ``` ## Async vs Sync ### Synchronous (default) * Blocks the server until all keys are deleted * Suitable for small databases * Guarantees database is empty when command returns ### Asynchronous * Returns immediately * Deletion happens in background * Suitable for large databases * Doesn't block other operations ## See Also * [del](/api/commands/del) - Delete specific keys * [dbsize](/api/commands/dbsize) - Get number of keys # geoadd Source: https://upstash-redis-js.mintlify.app/api/commands/geoadd Add one or more geospatial items to a key ## Usage ```typescript theme={null} import { Redis } from "@upstash/redis"; const redis = new Redis({ url: "", token: "", }); await redis.geoadd("locations", { longitude: -122.4194, latitude: 37.7749, member: "San Francisco" }); ``` ## Parameters The key to store the geospatial index. One or more geospatial members to add. Each member includes: The longitude coordinate. **Note**: Redis expects longitude first, then latitude. Valid range: -180 to 180 degrees The latitude coordinate. Valid range: -85.05112878 to 85.05112878 degrees The unique identifier for this location. Optional configuration: Only add new elements. Don't update existing elements. Only update existing elements. Don't add new elements. Return the number of changed elements instead of just added elements. Only works with `xx` option. ## Response The number of elements added to the sorted set (not including elements already existing for which the score was updated). Returns `null` when used with `xx` or `nx` options and the conditions are not met. ## Examples ### Add a single location ```typescript theme={null} const added = await redis.geoadd("cities", { longitude: -0.1276, latitude: 51.5074, member: "London" }); // Returns: 1 ``` ### Add multiple locations ```typescript theme={null} const added = await redis.geoadd( "cities", { longitude: -122.4194, latitude: 37.7749, member: "San Francisco" }, { longitude: -118.2437, latitude: 34.0522, member: "Los Angeles" }, { longitude: -73.935242, latitude: 40.730610, member: "New York" } ); // Returns: 3 ``` ### Only add new locations (nx) ```typescript theme={null} // First add await redis.geoadd("cities", { longitude: 2.3522, latitude: 48.8566, member: "Paris" }); // Try to add again with nx option const result = await redis.geoadd( "cities", { nx: true }, { longitude: 2.3522, latitude: 48.8566, member: "Paris" // Already exists } ); // Returns: 0 (nothing added) ``` ### Update existing locations only (xx) ```typescript theme={null} const updated = await redis.geoadd( "cities", { xx: true, ch: true }, { longitude: -0.1278, // Updated coordinates latitude: 51.5075, member: "London" // Must already exist } ); // Returns: 1 if updated, 0 if member doesn't exist ``` ## Important Notes ### Coordinate Order Redis stores geospatial data as a sorted set using the Geohash technique. The coordinate order is **longitude first, then latitude**: ```typescript theme={null} // ✅ Correct - longitude first { longitude: -122.4194, // longitude (x-axis) latitude: 37.7749, // latitude (y-axis) member: "San Francisco" } // ❌ Don't confuse with latitude/longitude order used in some other systems ``` ### Valid Ranges * **Longitude**: -180 to 180 degrees * **Latitude**: -85.05112878 to 85.05112878 degrees Coordinates outside these ranges will return an error. # geodist Source: https://upstash-redis-js.mintlify.app/api/commands/geodist Get the distance between two geospatial members ## Usage ```typescript theme={null} import { Redis } from "@upstash/redis"; const redis = new Redis({ url: "", token: "", }); const distance = await redis.geodist("cities", "San Francisco", "Los Angeles", "MI"); ``` ## Parameters The key containing the geospatial index. The first member to measure from. The second member to measure to. The unit of distance to return: * `M` - Meters (default) * `KM` - Kilometers * `FT` - Feet * `MI` - Miles ## Response The distance between the two members in the specified unit. Returns `null` if one or both members do not exist. ## Examples ### Get distance in different units ```typescript theme={null} // First, add some cities await redis.geoadd( "cities", { longitude: -122.4194, latitude: 37.7749, member: "San Francisco" }, { longitude: -118.2437, latitude: 34.0522, member: "Los Angeles" } ); // Distance in meters (default) const meters = await redis.geodist("cities", "San Francisco", "Los Angeles"); // Returns: 559120.3819 (approximately) // Distance in kilometers const km = await redis.geodist("cities", "San Francisco", "Los Angeles", "KM"); // Returns: 559.1204 (approximately) // Distance in miles const miles = await redis.geodist("cities", "San Francisco", "Los Angeles", "MI"); // Returns: 347.4203 (approximately) // Distance in feet const feet = await redis.geodist("cities", "San Francisco", "Los Angeles", "FT"); // Returns: 1834318.8385 (approximately) ``` ### Handle missing members ```typescript theme={null} const distance = await redis.geodist("cities", "San Francisco", "NonExistent", "MI"); // Returns: null (member doesn't exist) ``` ### Calculate multiple distances ```typescript theme={null} await redis.geoadd( "cities", { longitude: -73.935242, latitude: 40.730610, member: "New York" }, { longitude: 2.3522, latitude: 48.8566, member: "Paris" }, { longitude: 139.6917, latitude: 35.6895, member: "Tokyo" } ); const distances = { nyToSf: await redis.geodist("cities", "New York", "San Francisco", "MI"), nyToParis: await redis.geodist("cities", "New York", "Paris", "MI"), parisToTokyo: await redis.geodist("cities", "Paris", "Tokyo", "MI") }; console.log(distances); // { // nyToSf: 2565.88, // nyToParis: 3628.42, // parisToTokyo: 6034.23 // } ``` ## Important Notes ### Distance Calculation * Distances are calculated using the Haversine formula * The Earth is assumed to be a perfect sphere * Results are approximations and may differ slightly from other geospatial systems * The precision of the distance depends on the precision of the stored coordinates ### Unit Conversions | Unit | Abbreviation | Conversion | | ---------- | ------------ | ---------------- | | Meters | M | Base unit | | Kilometers | KM | 1 KM = 1000 M | | Feet | FT | 1 FT ≈ 0.3048 M | | Miles | MI | 1 MI ≈ 1609.34 M | # geohash Source: https://upstash-redis-js.mintlify.app/api/commands/geohash Get Geohash strings representing positions of members ## Usage ```typescript theme={null} import { Redis } from "@upstash/redis"; const redis = new Redis({ url: "", token: "", }); const hashes = await redis.geohash("cities", "San Francisco", "London"); ``` ## Parameters The key containing the geospatial index. One or more members to get the Geohash for. Can be passed as individual arguments or as an array. ## Response An array of Geohash strings in the same order as the requested members. Returns `null` for members that do not exist. ## Examples ### Get Geohash for single member ```typescript theme={null} // First, add a city await redis.geoadd("cities", { longitude: -122.4194, latitude: 37.7749, member: "San Francisco" }); const hashes = await redis.geohash("cities", "San Francisco"); // Returns: ["9q8yyk8yuv0"] ``` ### Get Geohash for multiple members ```typescript theme={null} await redis.geoadd( "cities", { longitude: -122.4194, latitude: 37.7749, member: "San Francisco" }, { longitude: -0.1276, latitude: 51.5074, member: "London" }, { longitude: 139.6917, latitude: 35.6895, member: "Tokyo" } ); const hashes = await redis.geohash("cities", "San Francisco", "London", "Tokyo"); // Returns: ["9q8yyk8yuv0", "gcpvj0h0c60", "xn774c06kf0"] ``` ### Handle missing members ```typescript theme={null} const hashes = await redis.geohash( "cities", "San Francisco", "NonExistent", "London" ); // Returns: ["9q8yyk8yuv0", null, "gcpvj0h0c60"] ``` ### Using array syntax ```typescript theme={null} const members = ["San Francisco", "London", "Tokyo"]; const hashes = await redis.geohash("cities", ...members); // Returns: ["9q8yyk8yuv0", "gcpvj0h0c60", "xn774c06kf0"] ``` ## What is a Geohash? A Geohash is a string encoding of a geographic location into a short string of letters and digits. It's a hierarchical spatial data structure that subdivides space into buckets of grid shape. ### Properties * **Fixed length**: The Geohash returned by Redis is always 11 characters * **Proximity**: Locations close to each other tend to have similar Geohash prefixes * **Precision**: Each additional character increases precision: * 1 character: ±2,500 km * 5 characters: ±2.4 km * 11 characters: ±0.6 mm ### Use Cases ```typescript theme={null} // Get Geohashes const [hash1, hash2] = await redis.geohash("cities", "San Francisco", "Oakland"); // Compare proximity by comparing prefixes const commonPrefix = (s1: string, s2: string) => { let i = 0; while (i < s1.length && i < s2.length && s1[i] === s2[i]) i++; return s1.slice(0, i); }; const prefix = commonPrefix(hash1, hash2); console.log(`Common prefix: ${prefix}`); // Longer common prefix = closer locations ``` ## Important Notes ### Encoding Details * Geohash uses a Base32 encoding system * Characters used: `0-9` and `b-z` (excluding `a`, `i`, `l`, `o`) * The hash represents the position, not the member name * Two members at the exact same coordinates will have the same Geohash ### Limitations * Geohashes are not perfectly suited for proximity searches near the poles * Locations on opposite sides of the equator or prime meridian may have very different hashes despite being close # geopos Source: https://upstash-redis-js.mintlify.app/api/commands/geopos Get longitude and latitude coordinates of members ## Usage ```typescript theme={null} import { Redis } from "@upstash/redis"; const redis = new Redis({ url: "", token: "", }); const positions = await redis.geopos("cities", "San Francisco", "London"); ``` ## Parameters The key containing the geospatial index. One or more members to get the position for. Can be passed as individual arguments or as an array. ## Response An array of coordinate objects in the same order as the requested members. Members that do not exist are omitted from the result array. The longitude coordinate. The latitude coordinate. ## Examples ### Get position for a single member ```typescript theme={null} // First, add a city await redis.geoadd("cities", { longitude: -122.4194, latitude: 37.7749, member: "San Francisco" }); const positions = await redis.geopos("cities", "San Francisco"); // Returns: [{ lng: -122.41940104961395, lat: 37.77490009603002 }] ``` ### Get positions for multiple members ```typescript theme={null} await redis.geoadd( "cities", { longitude: -122.4194, latitude: 37.7749, member: "San Francisco" }, { longitude: -0.1276, latitude: 51.5074, member: "London" }, { longitude: 139.6917, latitude: 35.6895, member: "Tokyo" } ); const positions = await redis.geopos("cities", "San Francisco", "London", "Tokyo"); // Returns: // [ // { lng: -122.41940104961395, lat: 37.77490009603002 }, // { lng: -0.12760043144226074, lat: 51.50739964418565 }, // { lng: 139.69171792268753, lat: 35.689499943217285 } // ] ``` ### Handle missing members ```typescript theme={null} const positions = await redis.geopos( "cities", "San Francisco", "NonExistent", // This member doesn't exist "London" ); // Returns: // [ // { lng: -122.41940104961395, lat: 37.77490009603002 }, // { lng: -0.12760043144226074, lat: 51.50739964418565 } // ] // Note: NonExistent member is omitted from results ``` ### Using array syntax ```typescript theme={null} const members = ["San Francisco", "London", "Tokyo"]; const positions = await redis.geopos("cities", ...members); ``` ### Verify coordinates after adding ```typescript theme={null} const originalCoords = { longitude: 2.3522, latitude: 48.8566 }; await redis.geoadd("cities", { ...originalCoords, member: "Paris" }); const [retrieved] = await redis.geopos("cities", "Paris"); console.log("Original:", originalCoords); console.log("Retrieved:", retrieved); // Original: { longitude: 2.3522, latitude: 48.8566 } // Retrieved: { lng: 2.352199852466583, lat: 48.85660005016098 } // Small differences due to internal encoding precision ``` ## Important Notes ### Coordinate Precision The returned coordinates may have slight variations from the original values due to: * Internal encoding using a 52-bit Geohash * Conversion between Geohash and coordinates * Floating-point arithmetic Typically, the precision loss is very small (within a few meters). ### Return Format * The SDK returns coordinates as objects with `lng` and `lat` properties * Missing members are **omitted** from the results array (not returned as `null`) * The result array may be shorter than the input array if some members don't exist ### Coordinate Order While `geoadd` uses `longitude` and `latitude` property names, `geopos` returns `lng` and `lat`: ```typescript theme={null} // Adding: uses 'longitude' and 'latitude' await redis.geoadd("cities", { longitude: -122.4194, latitude: 37.7749, member: "San Francisco" }); // Getting: uses 'lng' and 'lat' const [pos] = await redis.geopos("cities", "San Francisco"); console.log(pos.lng, pos.lat); ``` # geosearch Source: https://upstash-redis-js.mintlify.app/api/commands/geosearch Search for members within a geographic area ## Usage ```typescript theme={null} import { Redis } from "@upstash/redis"; const redis = new Redis({ url: "", token: "", }); const results = await redis.geosearch( "cities", { type: "FROMLONLAT", coordinate: { lon: -122.4194, lat: 37.7749 } }, { type: "BYRADIUS", radius: 100, radiusType: "MI" }, "ASC" ); ``` ## Parameters The key containing the geospatial index. The center point to search from: ```typescript theme={null} { type: "FROMLONLAT" | "fromlonlat", coordinate: { lon: number, lat: number } } ``` Search from specific longitude/latitude coordinates. ```typescript theme={null} { type: "FROMMEMBER" | "frommember", member: string } ``` Search from the position of an existing member. The search area shape: ```typescript theme={null} { type: "BYRADIUS" | "byradius", radius: number, radiusType: "M" | "KM" | "FT" | "MI" } ``` Search within a circular radius. ```typescript theme={null} { type: "BYBOX" | "bybox", rect: { width: number, height: number }, rectType: "M" | "KM" | "FT" | "MI" } ``` Search within a rectangular bounding box. Sort order: * `ASC` - Sort by distance from center (nearest first) * `DESC` - Sort by distance from center (farthest first) Optional search options: Limit the number of results: ```typescript theme={null} { limit: number, any?: boolean // Return as soon as enough matches are found } ``` Include coordinates in the response. Include distance from center in the response. Include Geohash string in the response. ## Response An array of search results, each containing: The member name (always included). Distance from the center point (if `withDist: true`). Coordinates of the member (if `withCoord: true`). Geohash string of the member (if `withHash: true`). ## Examples ### Search by radius from coordinates ```typescript theme={null} // Add some cities await redis.geoadd( "cities", { longitude: -122.4194, latitude: 37.7749, member: "San Francisco" }, { longitude: -122.2711, latitude: 37.8044, member: "Oakland" }, { longitude: -118.2437, latitude: 34.0522, member: "Los Angeles" }, { longitude: -121.8863, latitude: 37.3382, member: "San Jose" } ); // Find cities within 50 miles of San Francisco coordinates const nearby = await redis.geosearch( "cities", { type: "FROMLONLAT", coordinate: { lon: -122.4194, lat: 37.7749 } }, { type: "BYRADIUS", radius: 50, radiusType: "MI" }, "ASC" ); // Returns: [ // { member: "San Francisco" }, // { member: "Oakland" }, // { member: "San Jose" } // ] ``` ### Search from existing member ```typescript theme={null} const nearby = await redis.geosearch( "cities", { type: "FROMMEMBER", member: "San Francisco" }, { type: "BYRADIUS", radius: 100, radiusType: "MI" }, "ASC" ); ``` ### Include distance and coordinates ```typescript theme={null} const results = await redis.geosearch( "cities", { type: "FROMMEMBER", member: "San Francisco" }, { type: "BYRADIUS", radius: 100, radiusType: "MI" }, "ASC", { withDist: true, withCoord: true } ); // Returns: [ // { // member: "San Francisco", // dist: 0, // coord: { long: -122.41940104961395, lat: 37.77490009603002 } // }, // { // member: "Oakland", // dist: 8.7156, // coord: { long: -122.27110177278519, lat: 37.80439996259009 } // }, // ... // ] ``` ### Limit results ```typescript theme={null} const results = await redis.geosearch( "cities", { type: "FROMMEMBER", member: "San Francisco" }, { type: "BYRADIUS", radius: 500, radiusType: "MI" }, "ASC", { count: { limit: 3 }, withDist: true } ); // Returns only the 3 nearest cities ``` ### Search by rectangular box ```typescript theme={null} const results = await redis.geosearch( "cities", { type: "FROMLONLAT", coordinate: { lon: -122.4194, lat: 37.7749 } }, { type: "BYBOX", rect: { width: 100, height: 100 }, rectType: "MI" }, "ASC" ); // Returns cities within a 100x100 mile box centered on the coordinates ``` ### Sort by distance descending ```typescript theme={null} const results = await redis.geosearch( "cities", { type: "FROMMEMBER", member: "San Francisco" }, { type: "BYRADIUS", radius: 500, radiusType: "MI" }, "DESC", // Farthest first { withDist: true } ); ``` ### Include all metadata ```typescript theme={null} const results = await redis.geosearch( "cities", { type: "FROMMEMBER", member: "San Francisco" }, { type: "BYRADIUS", radius: 100, radiusType: "MI" }, "ASC", { withDist: true, withCoord: true, withHash: true, count: { limit: 5 } } ); // Returns: [ // { // member: "San Francisco", // dist: 0, // hash: "9q8yyk8yuv0", // coord: { long: -122.41940104961395, lat: 37.77490009603002 } // }, // ... // ] ``` ## Important Notes ### Performance Considerations * Use `count.any: true` for better performance when you don't need exact ordering of all results * Smaller search areas are faster than larger ones * Consider using `BYBOX` for rectangular areas instead of very large radius searches ### Coordinate Order Note that `centerPoint` uses `lon` and `lat` (shortened forms) while the response `coord` uses `long` and `lat`: ```typescript theme={null} // Input: uses 'lon' { type: "FROMLONLAT", coordinate: { lon: -122.4194, lat: 37.7749 } } // Output: uses 'long' { member: "...", coord: { long: -122.4194, lat: 37.7749 } } ``` ### Distance Units | Unit | Abbreviation | | ---------- | ------------ | | Meters | M | | Kilometers | KM | | Feet | FT | | Miles | MI | # get Source: https://upstash-redis-js.mintlify.app/api/commands/get Get the value of a key ## Description Get the value of key. If the key does not exist, `null` is returned. An error is returned if the value stored at key is not a string. ## Syntax ```typescript theme={null} redis.get(key: string): Promise ``` ## Parameters The key to retrieve ## Type Parameters The expected type of the returned data. The SDK will attempt to deserialize the value to this type. ## Returns The value of the key, or `null` if the key does not exist ## Examples ### Basic Usage ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: 'https://your-redis-url.upstash.io', token: 'your-token' }); // Set and get a string value await redis.set('mykey', 'Hello, World!'); const value = await redis.get('mykey'); console.log(value); // "Hello, World!" // Get a non-existent key const missing = await redis.get('nonexistent'); console.log(missing); // null ``` ### Working with JSON Data ```typescript theme={null} interface User { id: number; name: string; email: string; } // Store JSON data const user = { id: 1, name: 'Alice', email: 'alice@example.com' }; await redis.set('user:1', JSON.stringify(user)); // Retrieve and parse JSON data const userData = await redis.get('user:1'); if (userData) { const parsedUser: User = JSON.parse(userData); console.log(parsedUser.name); // "Alice" } ``` ### Using Automatic Deserialization ```typescript theme={null} // The SDK can automatically deserialize values interface Product { id: string; name: string; price: number; } const product = { id: 'p1', name: 'Widget', price: 19.99 }; await redis.set('product:1', product); // Retrieve with type parameter const retrieved = await redis.get('product:1'); console.log(retrieved?.price); // 19.99 ``` ## Related Commands * [set](/api/commands/set) - Set the string value of a key * [mget](/api/commands/mget) - Get the values of multiple keys ## Redis Documentation For more information, see the [Redis GET documentation](https://redis.io/commands/get). # getrange Source: https://upstash-redis-js.mintlify.app/api/commands/getrange Get a substring of the string stored at a key ## Description Returns the substring of the string value stored at key, determined by the offsets start and end (both are inclusive). Negative offsets can be used to provide an offset starting from the end of the string. ## Syntax ```typescript theme={null} redis.getrange(key: string, start: number, end: number): Promise ``` ## Parameters The key containing the string value The starting offset (0-based). Negative values count from the end of the string The ending offset (inclusive). Negative values count from the end of the string ## Returns The substring determined by the start and end offsets ## Examples ### Basic Usage ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: 'https://your-redis-url.upstash.io', token: 'your-token' }); // Set a string value await redis.set('message', 'Hello, World!'); // Get a substring from index 0 to 4 (inclusive) const substr1 = await redis.getrange('message', 0, 4); console.log(substr1); // "Hello" // Get a substring from index 7 to 11 const substr2 = await redis.getrange('message', 7, 11); console.log(substr2); // "World" ``` ### Using Negative Offsets ```typescript theme={null} await redis.set('text', 'Hello, World!'); // Get last 6 characters using negative offset const last = await redis.getrange('text', -6, -1); console.log(last); // "World!" // Get from index 0 to the 5th character from the end const mixed = await redis.getrange('text', 0, -8); console.log(mixed); // "Hello" ``` ### Get Entire String ```typescript theme={null} await redis.set('data', 'Complete String'); // Get the entire string using 0 and -1 const full = await redis.getrange('data', 0, -1); console.log(full); // "Complete String" ``` ### Extract Specific Parts ```typescript theme={null} // Store a date in ISO format await redis.set('timestamp', '2024-03-03T10:30:00Z'); // Extract year const year = await redis.getrange('timestamp', 0, 3); console.log(year); // "2024" // Extract month const month = await redis.getrange('timestamp', 5, 6); console.log(month); // "03" // Extract day const day = await redis.getrange('timestamp', 8, 9); console.log(day); // "03" ``` ### Non-Existent Key ```typescript theme={null} // Getting range from a non-existent key returns empty string const result = await redis.getrange('nonexistent', 0, 10); console.log(result); // "" ``` ### Out of Range Offsets ```typescript theme={null} await redis.set('short', 'Hi'); // End offset beyond string length const result = await redis.getrange('short', 0, 100); console.log(result); // "Hi" (returns entire string) // Start offset beyond string length const empty = await redis.getrange('short', 10, 20); console.log(empty); // "" (returns empty string) ``` ## Notes * Offsets are zero-based * Both start and end offsets are inclusive * Negative offsets count from the end: -1 is the last character, -2 is the second to last, etc. * If start is larger than the end of the string, an empty string is returned * If end is larger than the end of the string, the end of the string is used * Time complexity is O(N) where N is the length of the returned string ## Related Commands * [get](/api/commands/get) - Get the value of a key * [setrange](/api/commands/setrange) - Overwrite part of a string at a specific offset ## Redis Documentation For more information, see the [Redis GETRANGE documentation](https://redis.io/commands/getrange). # HDEL Source: https://upstash-redis-js.mintlify.app/api/commands/hdel Delete one or more hash fields ## Usage Removes the specified fields from the hash stored at `key`. Specified fields that do not exist within this hash are ignored. ```typescript theme={null} await redis.hdel(key, ...fields); ``` ## Parameters The key of the hash One or more field names to delete from the hash ## Response Returns `1` if at least one field was removed, `0` if no fields were removed (either because they didn't exist or the key doesn't exist). ## Examples ### Delete a single field ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); // Set hash fields await redis.hset('user:1000', { name: 'Alice', email: 'alice@example.com', age: 30, city: 'New York' }); // Delete a single field const deleted = await redis.hdel('user:1000', 'age'); console.log(deleted); // 1 // Verify deletion const user = await redis.hgetall('user:1000'); console.log(user); // { name: 'Alice', email: 'alice@example.com', city: 'New York' } ``` ### Delete multiple fields ```typescript theme={null} // Delete multiple fields at once const deleted = await redis.hdel('user:1000', 'email', 'city'); console.log(deleted); // 1 ``` ### Delete non-existent field ```typescript theme={null} // Try to delete a field that doesn't exist const deleted = await redis.hdel('user:1000', 'phone'); console.log(deleted); // 0 ``` ### Delete from non-existent key ```typescript theme={null} // Try to delete from a key that doesn't exist const deleted = await redis.hdel('user:9999', 'name'); console.log(deleted); // 0 ``` ### Remove sensitive data ```typescript theme={null} // Store user data with temporary token await redis.hset('user:1000', { name: 'Alice', email: 'alice@example.com', tempToken: 'xyz789' }); // Remove the temporary token after use await redis.hdel('user:1000', 'tempToken'); ``` ### Clean up cache fields ```typescript theme={null} // Remove cached computed values await redis.hdel('product:500', 'cachedPrice', 'cachedInventory', 'cachedRating'); ``` ## Notes * If the key does not exist, it is treated as an empty hash and the command returns `0` * The return value indicates whether any field was removed, not the count of removed fields * If all fields are removed, the key itself will be automatically deleted ## See Also * [HSET](/api/commands/hset) - Set hash field values * [HEXISTS](/api/commands/hexists) - Check if a hash field exists * [HGET](/api/commands/hget) - Get a hash field value # HEXISTS Source: https://upstash-redis-js.mintlify.app/api/commands/hexists Check if a hash field exists ## Usage Returns if `field` is an existing field in the hash stored at `key`. ```typescript theme={null} await redis.hexists(key, field); ``` ## Parameters The key of the hash The field name to check for existence ## Response Returns `1` if the hash contains the field, `0` if the hash does not contain the field or the key does not exist. ## Examples ### Check if a field exists ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); // Set hash fields await redis.hset('user:1000', { name: 'Alice', email: 'alice@example.com' }); // Check if field exists const hasName = await redis.hexists('user:1000', 'name'); console.log(hasName); // 1 const hasAge = await redis.hexists('user:1000', 'age'); console.log(hasAge); // 0 ``` ### Conditional field operations ```typescript theme={null} // Only set a field if it doesn't exist const exists = await redis.hexists('user:1000', 'defaultTheme'); if (!exists) { await redis.hset('user:1000', { defaultTheme: 'light' }); } ``` ### Validate required fields ```typescript theme={null} interface RequiredFields { name: boolean; email: boolean; phone: boolean; } const checkRequired = async (userId: string): Promise => { const [hasName, hasEmail, hasPhone] = await Promise.all([ redis.hexists(`user:${userId}`, 'name'), redis.hexists(`user:${userId}`, 'email'), redis.hexists(`user:${userId}`, 'phone'), ]); return { name: hasName === 1, email: hasEmail === 1, phone: hasPhone === 1, }; }; const validation = await checkRequired('1000'); console.log(validation); // { name: true, email: true, phone: false } ``` ### Check before deletion ```typescript theme={null} // Check if field exists before attempting deletion if (await redis.hexists('user:1000', 'tempToken')) { await redis.hdel('user:1000', 'tempToken'); console.log('Temporary token removed'); } ``` ### Feature flag checking ```typescript theme={null} // Check if a user has a specific feature enabled const hasFeature = await redis.hexists('user:1000:features', 'betaAccess'); if (hasFeature) { console.log('User has beta access'); } ``` ### Non-existent key ```typescript theme={null} // Check field on non-existent key const exists = await redis.hexists('user:9999', 'name'); console.log(exists); // 0 ``` ## Notes * Returns `1` (not `true`) when the field exists * Returns `0` (not `false`) when the field doesn't exist * Useful for conditional logic before setting or deleting fields * More efficient than getting the value when you only need to check existence ## See Also * [HGET](/api/commands/hget) - Get a hash field value * [HSET](/api/commands/hset) - Set hash field values * [HDEL](/api/commands/hdel) - Delete hash fields * [HKEYS](/api/commands/hkeys) - Get all field names in a hash # HGET Source: https://upstash-redis-js.mintlify.app/api/commands/hget Get the value of a hash field ## Usage Returns the value associated with `field` in the hash stored at `key`. ```typescript theme={null} await redis.hget(key, field); ``` ## Parameters The key of the hash The field in the hash to get ## Response The value associated with the field, or `null` when the field is not present in the hash or the key does not exist. ## Examples ### Get a hash field value ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); // Set a hash field await redis.hset('user:1000', { name: 'Alice', email: 'alice@example.com' }); // Get a specific field const name = await redis.hget('user:1000', 'name'); console.log(name); // 'Alice' // Get a non-existent field const age = await redis.hget('user:1000', 'age'); console.log(age); // null ``` ### Working with typed data ```typescript theme={null} interface User { name: string; email: string; } // Get with type inference const email = await redis.hget('user:1000', 'email'); console.log(email); // 'alice@example.com' ``` ## See Also * [HSET](/api/commands/hset) - Set hash field values * [HGETALL](/api/commands/hgetall) - Get all fields and values in a hash * [HMGET](/api/commands/hmget) - Get multiple hash field values # HGETALL Source: https://upstash-redis-js.mintlify.app/api/commands/hgetall Get all fields and values in a hash ## Usage Returns all fields and values of the hash stored at `key`. ```typescript theme={null} await redis.hgetall(key); ``` ## Parameters The key of the hash ## Response An object containing all field-value pairs in the hash, or `null` if the hash does not exist or is empty. The return type is a record where keys are field names and values are the stored values. Values are automatically parsed from JSON when possible. ## Examples ### Get all hash fields ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); // Set hash fields await redis.hset('user:1000', { name: 'Alice', email: 'alice@example.com', age: 30, city: 'New York' }); // Get all fields and values const user = await redis.hgetall('user:1000'); console.log(user); // { // name: 'Alice', // email: 'alice@example.com', // age: 30, // city: 'New York' // } ``` ### Handle non-existent keys ```typescript theme={null} const result = await redis.hgetall('user:9999'); console.log(result); // null ``` ### Working with typed data ```typescript theme={null} interface User { name: string; email: string; age: number; city: string; } const user = await redis.hgetall('user:1000'); if (user) { console.log(`${user.name} is ${user.age} years old`); } ``` ### Iterate over hash fields ```typescript theme={null} const user = await redis.hgetall('user:1000'); if (user) { for (const [field, value] of Object.entries(user)) { console.log(`${field}: ${value}`); } } ``` ### Store and retrieve complex data ```typescript theme={null} // Store session data await redis.hset('session:abc123', { userId: '1000', createdAt: Date.now().toString(), ipAddress: '192.168.1.1', userAgent: 'Mozilla/5.0...' }); // Retrieve all session data const session = await redis.hgetall('session:abc123'); console.log(session); ``` ## Notes * The command automatically parses numeric values that are safe integers * JSON values are parsed when possible * For very large hashes, consider using `HSCAN` to iterate incrementally ## See Also * [HGET](/api/commands/hget) - Get a single hash field value * [HKEYS](/api/commands/hkeys) - Get all field names in a hash * [HVALS](/api/commands/hvals) - Get all values in a hash * [HMGET](/api/commands/hmget) - Get multiple hash field values # HINCRBY Source: https://upstash-redis-js.mintlify.app/api/commands/hincrby Increment a hash field by a number ## Usage Increments the number stored at `field` in the hash stored at `key` by `increment`. If the field does not exist, it is set to 0 before performing the operation. ```typescript theme={null} await redis.hincrby(key, field, increment); ``` ## Parameters The key of the hash The field in the hash to increment The increment value. Can be positive or negative to increment or decrement the field. ## Response The value of the field after the increment operation. ## Examples ### Increment a counter ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); // Initialize counter await redis.hset('user:1000:stats', { loginCount: 0 }); // Increment login counter const newCount = await redis.hincrby('user:1000:stats', 'loginCount', 1); console.log(newCount); // 1 // Increment again const count2 = await redis.hincrby('user:1000:stats', 'loginCount', 1); console.log(count2); // 2 ``` ### Increment non-existent field ```typescript theme={null} // Field doesn't exist, starts at 0 const views = await redis.hincrby('article:500', 'views', 1); console.log(views); // 1 ``` ### Decrement by using negative increment ```typescript theme={null} // Set initial inventory await redis.hset('product:200', { inventory: 100 }); // Decrease inventory by 5 const remaining = await redis.hincrby('product:200', 'inventory', -5); console.log(remaining); // 95 ``` ### Track multiple metrics ```typescript theme={null} // Track user activity metrics await redis.hincrby('user:1000:activity', 'postsCreated', 1); await redis.hincrby('user:1000:activity', 'commentsCreated', 1); await redis.hincrby('user:1000:activity', 'likesGiven', 1); const activity = await redis.hgetall('user:1000:activity'); console.log(activity); // { postsCreated: 1, commentsCreated: 1, likesGiven: 1 } ``` ### Implement rate limiting ```typescript theme={null} const incrementApiCall = async (userId: string): Promise => { const key = `ratelimit:${userId}:${Date.now() / 1000 / 60 | 0}`; // per minute return await redis.hincrby(key, 'requests', 1); }; const requests = await incrementApiCall('user:1000'); if (requests > 100) { console.log('Rate limit exceeded'); } ``` ### Track votes or scores ```typescript theme={null} // Upvote a post await redis.hincrby('post:1000', 'upvotes', 1); // Downvote a post await redis.hincrby('post:1000', 'downvotes', 1); // Calculate score const post = await redis.hgetall('post:1000'); const score = (post?.upvotes || 0) - (post?.downvotes || 0); console.log(`Score: ${score}`); ``` ### Increment by larger amounts ```typescript theme={null} // Add points to user score await redis.hincrby('user:1000:score', 'points', 100); // Bonus points await redis.hincrby('user:1000:score', 'points', 500); const totalPoints = await redis.hget('user:1000:score', 'points'); console.log(totalPoints); // 600 ``` ### Analytics tracking ```typescript theme={null} // Track daily page views const today = new Date().toISOString().split('T')[0]; await redis.hincrby('analytics:pageviews', today, 1); // Track by page await redis.hincrby('analytics:pages', '/home', 1); await redis.hincrby('analytics:pages', '/about', 1); const pageStats = await redis.hgetall('analytics:pages'); console.log(pageStats); // { '/home': 1, '/about': 1 } ``` ## Notes * The operation is atomic * If the hash or field doesn't exist, it's created with the field set to 0 before incrementing * Only works with integer values * For floating-point increments, use `HINCRBYFLOAT` * An error is returned if the field contains a value of the wrong type or a string that cannot be represented as an integer ## See Also * [HSET](/api/commands/hset) - Set hash field values * [HGET](/api/commands/hget) - Get a hash field value * [HINCRBY](/api/commands/hincrby) - Increment a hash field # HKEYS Source: https://upstash-redis-js.mintlify.app/api/commands/hkeys Get all field names in a hash ## Usage Returns all field names in the hash stored at `key`. ```typescript theme={null} await redis.hkeys(key); ``` ## Parameters The key of the hash ## Response An array of field names in the hash. Returns an empty array if the key does not exist. ## Examples ### Get all field names ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); // Set hash fields await redis.hset('user:1000', { name: 'Alice', email: 'alice@example.com', age: 30, city: 'New York' }); // Get all field names const fields = await redis.hkeys('user:1000'); console.log(fields); // ['name', 'email', 'age', 'city'] ``` ### Check available fields ```typescript theme={null} const fields = await redis.hkeys('user:1000'); if (fields.includes('email')) { console.log('Email field exists'); } if (fields.includes('phone')) { console.log('Phone field exists'); } else { console.log('Phone field is missing'); } ``` ### Iterate over fields ```typescript theme={null} const fields = await redis.hkeys('user:1000'); for (const field of fields) { const value = await redis.hget('user:1000', field); console.log(`${field}: ${value}`); } ``` ### Get field count ```typescript theme={null} const fields = await redis.hkeys('user:1000'); console.log(`Total fields: ${fields.length}`); ``` ### Validate schema ```typescript theme={null} const requiredFields = ['name', 'email', 'id']; const actualFields = await redis.hkeys('user:1000'); const missingFields = requiredFields.filter(field => !actualFields.includes(field)); if (missingFields.length > 0) { console.log(`Missing required fields: ${missingFields.join(', ')}`); } ``` ### Non-existent key ```typescript theme={null} const fields = await redis.hkeys('user:9999'); console.log(fields); // [] ``` ### Filter fields by pattern ```typescript theme={null} // Get all fields const allFields = await redis.hkeys('user:1000:settings'); // Filter fields that start with 'notification' const notificationFields = allFields.filter(field => field.startsWith('notification')); console.log(notificationFields); // ['notificationEmail', 'notificationPush', 'notificationSMS'] ``` ### Compare field sets ```typescript theme={null} const user1Fields = await redis.hkeys('user:1000'); const user2Fields = await redis.hkeys('user:2000'); // Find fields unique to user1 const uniqueToUser1 = user1Fields.filter(field => !user2Fields.includes(field)); console.log('Unique to user1:', uniqueToUser1); ``` ### Export field names for documentation ```typescript theme={null} const fields = await redis.hkeys('config:app'); console.log('Available configuration fields:'); fields.forEach(field => { console.log(`- ${field}`); }); ``` ## Notes * The order of fields in the returned array is not guaranteed * Returns an empty array for non-existent keys * Use `HGETALL` if you need both field names and values * For very large hashes, consider using `HSCAN` to iterate incrementally ## See Also * [HVALS](/api/commands/hvals) - Get all values in a hash * [HGETALL](/api/commands/hgetall) - Get all fields and values in a hash * [HEXISTS](/api/commands/hexists) - Check if a hash field exists # HMGET Source: https://upstash-redis-js.mintlify.app/api/commands/hmget Get multiple hash field values ## Usage Returns the values associated with the specified fields in the hash stored at `key`. Returns an object with the requested fields. ```typescript theme={null} await redis.hmget(key, ...fields); ``` ## Parameters The key of the hash One or more field names to retrieve from the hash ## Response An object containing the requested fields and their values. Fields that do not exist will have `null` values. Returns `null` if the hash does not exist or all requested fields are empty. The return type is a record where keys are the requested field names and values are the stored values (or `null` if the field doesn't exist). ## Examples ### Get multiple fields ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); // Set hash fields await redis.hset('user:1000', { name: 'Alice', email: 'alice@example.com', age: 30, city: 'New York', country: 'USA' }); // Get specific fields const user = await redis.hmget('user:1000', 'name', 'email', 'age'); console.log(user); // { name: 'Alice', email: 'alice@example.com', age: 30 } ``` ### Handle missing fields ```typescript theme={null} // Request fields that may not exist const data = await redis.hmget('user:1000', 'name', 'phone', 'address'); console.log(data); // { name: 'Alice', phone: null, address: null } ``` ### Working with typed data ```typescript theme={null} interface UserData { name: string | null; email: string | null; age: number | null; } const user = await redis.hmget('user:1000', 'name', 'email', 'age'); if (user) { console.log(`Name: ${user.name}`); console.log(`Email: ${user.email}`); console.log(`Age: ${user.age}`); } ``` ### Fetch subset of large hash ```typescript theme={null} // Only get the fields you need instead of the entire hash const essentials = await redis.hmget( 'product:500', 'name', 'price', 'inStock' ); if (essentials?.inStock) { console.log(`${essentials.name}: $${essentials.price}`); } ``` ### Non-existent hash ```typescript theme={null} const result = await redis.hmget('user:9999', 'name', 'email'); console.log(result); // null ``` ### All fields are null ```typescript theme={null} // If all requested fields don't exist, returns null const result = await redis.hmget('user:1000', 'field1', 'field2', 'field3'); console.log(result); // null (if none of these fields exist) ``` ### Efficient partial reads ```typescript theme={null} // Instead of HGETALL which gets all fields // const allData = await redis.hgetall('session:abc123'); // Use HMGET to get only what you need const session = await redis.hmget( 'session:abc123', 'userId', 'createdAt', 'expiresAt' ); if (session) { console.log(`User ${session.userId} session expires at ${session.expiresAt}`); } ``` ### Build partial objects ```typescript theme={null} interface UserProfile { name: string | null; bio: string | null; avatar: string | null; } const profile = await redis.hmget( 'user:1000', 'name', 'bio', 'avatar' ); if (profile) { // Use only the fields you need const displayName = profile.name || 'Anonymous'; const displayBio = profile.bio || 'No bio available'; console.log(`${displayName}: ${displayBio}`); } ``` ### Batch read from multiple hashes ```typescript theme={null} // Efficiently read specific fields from multiple users const [user1, user2, user3] = await Promise.all([ redis.hmget('user:1000', 'name', 'email'), redis.hmget('user:2000', 'name', 'email'), redis.hmget('user:3000', 'name', 'email'), ]); console.log([user1, user2, user3]); ``` ### Check field values efficiently ```typescript theme={null} const settings = await redis.hmget( 'user:1000:settings', 'theme', 'language', 'timezone' ); if (settings) { const theme = settings.theme || 'light'; const language = settings.language || 'en'; const timezone = settings.timezone || 'UTC'; console.log({ theme, language, timezone }); } ``` ## Notes * More efficient than multiple `HGET` calls when you need multiple fields * More efficient than `HGETALL` when you only need a subset of fields * Returns `null` for the entire result if the hash doesn't exist or all fields are null * Individual fields that don't exist have `null` values in the returned object * Values are automatically parsed from JSON when possible ## See Also * [HGET](/api/commands/hget) - Get a single hash field value * [HGETALL](/api/commands/hgetall) - Get all fields and values in a hash * [HSET](/api/commands/hset) - Set hash field values * [HMSET](/api/commands/hmset) - Set multiple hash fields # HMSET Source: https://upstash-redis-js.mintlify.app/api/commands/hmset Set multiple hash fields (legacy) ## Usage Sets the specified fields to their respective values in the hash stored at `key`. This command overwrites any specified fields already existing in the hash. ```typescript theme={null} await redis.hmset(key, fieldValueObject); ``` As of Redis 4.0.0, `HMSET` is considered deprecated. Use `HSET` with multiple field-value pairs instead, which has the same behavior. ## Parameters The key of the hash An object containing field-value pairs to set in the hash. Each key in the object represents a field name, and its value is the value to set. ## Response Always returns `'OK'` when successful. ## Examples ### Set multiple fields ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); // Set multiple fields at once const result = await redis.hmset('user:1000', { name: 'Alice', email: 'alice@example.com', age: 30, city: 'New York' }); console.log(result); // 'OK' ``` ### Update existing fields ```typescript theme={null} // Set initial data await redis.hmset('user:1000', { name: 'Alice', email: 'alice@example.com' }); // Update fields (overwrites existing) await redis.hmset('user:1000', { email: 'alice.new@example.com', city: 'Boston' }); const user = await redis.hgetall('user:1000'); console.log(user); // { name: 'Alice', email: 'alice.new@example.com', city: 'Boston' } ``` ### Initialize configuration ```typescript theme={null} // Set application configuration await redis.hmset('config:app', { maxConnections: 100, timeout: 5000, retryAttempts: 3, enableCache: true }); ``` ### Store session data ```typescript theme={null} interface SessionData { userId: string; createdAt: string; expiresAt: string; ipAddress: string; } const sessionData: SessionData = { userId: '1000', createdAt: new Date().toISOString(), expiresAt: new Date(Date.now() + 3600000).toISOString(), ipAddress: '192.168.1.1' }; await redis.hmset('session:abc123', sessionData); ``` ### Migrate from HMSET to HSET ```typescript theme={null} // Old way (HMSET - deprecated) await redis.hmset('user:1000', { name: 'Alice', email: 'alice@example.com' }); // New way (HSET - recommended) await redis.hset('user:1000', { name: 'Alice', email: 'alice@example.com' }); // Both produce the same result ``` ### Bulk insert user data ```typescript theme={null} const users = [ { id: '1000', name: 'Alice', email: 'alice@example.com' }, { id: '2000', name: 'Bob', email: 'bob@example.com' }, { id: '3000', name: 'Charlie', email: 'charlie@example.com' } ]; // Insert all users await Promise.all( users.map(user => redis.hmset(`user:${user.id}`, { name: user.name, email: user.email }) ) ); ``` ### Set default values ```typescript theme={null} // Set default user preferences await redis.hmset('user:1000:preferences', { theme: 'light', language: 'en', notifications: true, emailDigest: 'daily' }); ``` ## Notes * Always returns `'OK'` on success (unlike `HSET` which returns the count) * As of Redis 4.0.0, this command is deprecated in favor of `HSET` * `HSET` can accept multiple field-value pairs and has the same behavior as `HMSET` * The command overwrites existing fields * If the key does not exist, a new hash is created * Consider using `HSET` for new implementations ## Migration Guide Replace `HMSET` with `HSET` in your code: ```typescript theme={null} // Before await redis.hmset('key', { field1: 'value1', field2: 'value2' }); // After await redis.hset('key', { field1: 'value1', field2: 'value2' }); ``` The main difference is the return value: * `HMSET` returns `'OK'` * `HSET` returns the number of fields added/updated ## See Also * [HSET](/api/commands/hset) - Set hash field values (recommended) * [HGET](/api/commands/hget) - Get a hash field value * [HMGET](/api/commands/hmget) - Get multiple hash field values * [HGETALL](/api/commands/hgetall) - Get all fields and values in a hash # HSET Source: https://upstash-redis-js.mintlify.app/api/commands/hset Set hash field values ## Usage Sets the specified fields to their respective values in the hash stored at `key`. This command overwrites any specified fields already existing in the hash. ```typescript theme={null} await redis.hset(key, fieldValueObject); ``` ## Parameters The key of the hash An object containing field-value pairs to set in the hash. Each key in the object represents a field name, and its value is the value to set. ## Response The number of fields that were added or updated in the hash. ## Examples ### Set multiple hash fields ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); // Set multiple fields at once const count = await redis.hset('user:1000', { name: 'Alice', email: 'alice@example.com', age: 30 }); console.log(count); // 3 // Update existing fields const updated = await redis.hset('user:1000', { age: 31, city: 'New York' }); console.log(updated); // 2 (age updated, city added) ``` ### Set a single field ```typescript theme={null} // Set one field await redis.hset('user:1000', { status: 'active' }); ``` ### Store complex objects ```typescript theme={null} interface Profile { bio: string; website: string; } const profile: Profile = { bio: 'Software engineer', website: 'https://example.com' }; await redis.hset('user:1000:profile', profile); ``` ### Store JSON data ```typescript theme={null} // Complex data is automatically serialized await redis.hset('user:1000', { preferences: JSON.stringify({ theme: 'dark', notifications: true }), metadata: JSON.stringify({ lastLogin: Date.now() }) }); ``` ## See Also * [HGET](/api/commands/hget) - Get a hash field value * [HMSET](/api/commands/hmset) - Set multiple hash fields (legacy) * [HGETALL](/api/commands/hgetall) - Get all fields and values in a hash # HVALS Source: https://upstash-redis-js.mintlify.app/api/commands/hvals Get all values in a hash ## Usage Returns all values in the hash stored at `key`. ```typescript theme={null} await redis.hvals(key); ``` ## Parameters The key of the hash ## Response An array of values in the hash. Returns an empty array if the key does not exist. Values are automatically parsed from JSON when possible. ## Examples ### Get all values ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); // Set hash fields await redis.hset('user:1000', { name: 'Alice', email: 'alice@example.com', age: 30, city: 'New York' }); // Get all values const values = await redis.hvals('user:1000'); console.log(values); // ['Alice', 'alice@example.com', 30, 'New York'] ``` ### Working with typed data ```typescript theme={null} interface UserValue { name?: string; email?: string; age?: number; city?: string; } type UserValues = Array; const values = await redis.hvals('user:1000'); console.log(values); ``` ### Process all values ```typescript theme={null} // Get all product prices const prices = await redis.hvals('product:prices'); // Calculate average const average = prices.reduce((sum, price) => sum + price, 0) / prices.length; console.log(`Average price: $${average.toFixed(2)}`); // Find min and max const min = Math.min(...prices); const max = Math.max(...prices); console.log(`Price range: $${min} - $${max}`); ``` ### Non-existent key ```typescript theme={null} const values = await redis.hvals('user:9999'); console.log(values); // [] ``` ### Count occurrences ```typescript theme={null} // Get all status values const statuses = await redis.hvals('order:statuses'); // Count each status const statusCounts = statuses.reduce((acc, status) => { acc[status] = (acc[status] || 0) + 1; return acc; }, {} as Record); console.log(statusCounts); // { pending: 5, shipped: 3, delivered: 2 } ``` ### Filter values ```typescript theme={null} // Get all ages const ages = await redis.hvals('users:ages'); // Filter adults const adults = ages.filter(age => age >= 18); console.log(`Adults: ${adults.length}`); ``` ### Check for duplicates ```typescript theme={null} const emails = await redis.hvals('users:emails'); const uniqueEmails = new Set(emails); if (emails.length !== uniqueEmails.size) { console.log('Duplicate emails found!'); } ``` ### Sum numeric values ```typescript theme={null} // Get all point values const points = await redis.hvals('user:1000:scores'); // Calculate total const total = points.reduce((sum, points) => sum + points, 0); console.log(`Total points: ${total}`); ``` ### Validate all values ```typescript theme={null} const urls = await redis.hvals('config:endpoints'); // Check if all URLs are valid const allValid = urls.every(url => { try { new URL(url); return true; } catch { return false; } }); console.log(`All URLs valid: ${allValid}`); ``` ### Export values list ```typescript theme={null} // Get all feature flags const features = await redis.hvals('app:features'); const enabledCount = features.filter(Boolean).length; console.log(`${enabledCount} of ${features.length} features enabled`); ``` ## Notes * The order of values in the returned array is not guaranteed * The order corresponds to the order of fields returned by `HKEYS` * Returns an empty array for non-existent keys * Use `HGETALL` if you need both field names and values * For very large hashes, consider using `HSCAN` to iterate incrementally ## See Also * [HKEYS](/api/commands/hkeys) - Get all field names in a hash * [HGETALL](/api/commands/hgetall) - Get all fields and values in a hash * [HGET](/api/commands/hget) - Get a specific hash field value # incr Source: https://upstash-redis-js.mintlify.app/api/commands/incr Increment the integer value of a key by one ## Description Increments the number stored at key by one. If the key does not exist, it is set to 0 before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that cannot be represented as an integer. ## Syntax ```typescript theme={null} redis.incr(key: string): Promise ``` ## Parameters The key containing the integer to increment ## Returns The value of the key after the increment ## Examples ### Basic Usage ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: 'https://your-redis-url.upstash.io', token: 'your-token' }); // Set an initial value await redis.set('counter', 10); // Increment by 1 const newValue = await redis.incr('counter'); console.log(newValue); // 11 const value = await redis.get('counter'); console.log(value); // "11" ``` ### Increment Non-Existent Key ```typescript theme={null} // Incrementing a non-existent key initializes it to 0 and then increments const result = await redis.incr('newcounter'); console.log(result); // 1 ``` ### Multiple Increments ```typescript theme={null} await redis.set('views', 0); // Increment multiple times await redis.incr('views'); // 1 await redis.incr('views'); // 2 await redis.incr('views'); // 3 const totalViews = await redis.get('views'); console.log(totalViews); // "3" ``` ### Page View Counter ```typescript theme={null} // Track page views async function trackPageView(pageId: string) { const views = await redis.incr(`page:${pageId}:views`); console.log(`Page ${pageId} has ${views} views`); return views; } await trackPageView('homepage'); // 1 await trackPageView('homepage'); // 2 await trackPageView('about'); // 1 ``` ### Request Counter ```typescript theme={null} // Count API requests per user async function logRequest(userId: string) { const requestCount = await redis.incr(`user:${userId}:requests`); // Set expiration on first request (if needed) if (requestCount === 1) { await redis.expire(`user:${userId}:requests`, 3600); // 1 hour } return requestCount; } const count = await logRequest('user123'); console.log(`User has made ${count} requests this hour`); ``` ### Generating Sequential IDs ```typescript theme={null} // Generate unique sequential IDs async function getNextOrderId(): Promise { return await redis.incr('order:id:counter'); } const orderId1 = await getNextOrderId(); // 1 const orderId2 = await getNextOrderId(); // 2 const orderId3 = await getNextOrderId(); // 3 ``` ### Rate Limiting Example ```typescript theme={null} async function checkRateLimit(userId: string, maxRequests: number): Promise { const key = `ratelimit:${userId}:${Math.floor(Date.now() / 60000)}`; // per minute const current = await redis.incr(key); if (current === 1) { // First request in this minute, set expiration await redis.expire(key, 60); } return current <= maxRequests; } const allowed = await checkRateLimit('user123', 10); if (allowed) { console.log('Request allowed'); } else { console.log('Rate limit exceeded'); } ``` ## Notes * This is an atomic operation, making it safe for concurrent increments * The value must be an integer or a string that can be parsed as an integer * If the key contains a non-integer value, an error will be returned * The operation has O(1) time complexity * The range of values is limited to 64-bit signed integers ## Error Handling ```typescript theme={null} try { // This will throw an error because "hello" is not a valid integer await redis.set('mykey', 'hello'); await redis.incr('mykey'); } catch (error) { console.error('Cannot increment non-integer value'); } ``` ## Related Commands * [decr](/api/commands/decr) - Decrement the integer value of a key by one ## Redis Documentation For more information, see the [Redis INCR documentation](https://redis.io/commands/incr). # JSON.ARRAPPEND Source: https://upstash-redis-js.mintlify.app/api/commands/json-arrappend Append values to a JSON array Appends one or more values to the end of a JSON array at the specified path. ## Usage ```typescript theme={null} const newLength = await redis.json.arrappend("user:123", "$.tags", "premium", "verified"); ``` ## Arguments The key containing the JSON document JSONPath expression pointing to the array to append to One or more values to append to the array. Can be any JSON-serializable values. ## Response An array of numbers representing the new length of the array at each matching path. Returns `null` for paths that don't exist or aren't arrays. ## Examples ### Append single value to array ```typescript theme={null} // Initial: { "tags": ["developer", "admin"] } const newLength = await redis.json.arrappend("user:123", "$.tags", "premium"); console.log(newLength); // [3] // Result: { "tags": ["developer", "admin", "premium"] } ``` ### Append multiple values ```typescript theme={null} const newLength = await redis.json.arrappend( "user:123", "$.tags", "verified", "active", "pro" ); console.log(newLength); // [6] // Result: { "tags": ["developer", "admin", "premium", "verified", "active", "pro"] } ``` ### Append to nested array ```typescript theme={null} interface Order { id: string; items: string[]; } const newLength = await redis.json.arrappend( "order:456", "$.items", "item-123" ); console.log(newLength); // [4] ``` ### Append objects to array ```typescript theme={null} interface Product { id: string; name: string; price: number; } const newProduct: Product = { id: "prod-789", name: "Widget", price: 29.99 }; const newLength = await redis.json.arrappend( "cart:123", "$.products", newProduct ); ``` ### Append to multiple arrays (wildcard path) ```typescript theme={null} // Initial: { // "users": [ // { "id": 1, "tags": ["active"] }, // { "id": 2, "tags": ["inactive"] } // ] // } const newLengths = await redis.json.arrappend( "data:1", "$.users[*].tags", "updated" ); console.log(newLengths); // [2, 2] // Result: Both users now have "updated" tag appended ``` ### Handle non-existent paths ```typescript theme={null} const newLength = await redis.json.arrappend( "user:123", "$.nonexistent", "value" ); console.log(newLength); // [null] ``` ## JSONPath Syntax * `$.array` - Append to a top-level array * `$.nested.array` - Append to a nested array * `$.array[0].items` - Append to array within array element * `$..array` - Append to all matching arrays recursively * `$.items[*].tags` - Append to arrays within each item ## Notes * The path must point to an existing array. If the path doesn't exist or points to a non-array value, `null` is returned for that path. * When using wildcard paths, the operation is performed on all matching arrays. * Values are automatically JSON-serialized before appending. ## See Also * [JSON.ARRINDEX](/api/commands/json-arrindex) - Find index of value in array * [JSON.SET](/api/commands/json-set) - Set JSON values * [Redis JSON.ARRAPPEND documentation](https://redis.io/commands/json.arrappend) # JSON.ARRINDEX Source: https://upstash-redis-js.mintlify.app/api/commands/json-arrindex Find the index of a value in a JSON array Searches for the first occurrence of a value in a JSON array and returns its index. ## Usage ```typescript theme={null} const index = await redis.json.arrindex("user:123", "$.tags", "premium"); ``` ## Arguments The key containing the JSON document JSONPath expression pointing to the array to search The value to search for in the array Optional start index for the search (inclusive). Defaults to `0`. Negative values count from the end of the array. Optional stop index for the search (exclusive). Defaults to the end of the array. Negative values count from the end of the array. ## Response An array of integers representing the index of the first occurrence of the value at each matching path. Returns `-1` if the value is not found, or `null` if the path doesn't exist or isn't an array. ## Examples ### Find value in array ```typescript theme={null} // Array: ["developer", "admin", "premium", "verified"] const index = await redis.json.arrindex("user:123", "$.tags", "premium"); console.log(index); // [2] ``` ### Value not found ```typescript theme={null} const index = await redis.json.arrindex("user:123", "$.tags", "nonexistent"); console.log(index); // [-1] ``` ### Search with start index ```typescript theme={null} // Array: ["a", "b", "c", "b", "d"] // Find "b" starting from index 2 const index = await redis.json.arrindex("data:1", "$.letters", "b", 2); console.log(index); // [3] - finds the second "b" ``` ### Search with start and stop indices ```typescript theme={null} // Array: ["a", "b", "c", "b", "d"] // Search for "b" between indices 0 and 3 const index = await redis.json.arrindex("data:1", "$.letters", "b", 0, 3); console.log(index); // [1] - finds the first "b" ``` ### Search with negative indices ```typescript theme={null} // Array: ["a", "b", "c", "d", "e"] // Search in the last 3 elements const index = await redis.json.arrindex("data:1", "$.letters", "d", -3); console.log(index); // [3] ``` ### Search for objects in array ```typescript theme={null} interface Product { id: string; name: string; } // Array: [{"id":"1","name":"A"}, {"id":"2","name":"B"}] const searchProduct = { id: "2", name: "B" }; const index = await redis.json.arrindex( "products:1", "$.items", searchProduct ); console.log(index); // [1] ``` ### Search in multiple arrays (wildcard path) ```typescript theme={null} // Data: { // "users": [ // { "id": 1, "roles": ["admin", "user"] }, // { "id": 2, "roles": ["user", "guest"] } // ] // } const indices = await redis.json.arrindex( "data:1", "$.users[*].roles", "user" ); console.log(indices); // [1, 0] - "user" is at index 1 in first array, index 0 in second ``` ### Handle non-existent paths ```typescript theme={null} const index = await redis.json.arrindex( "user:123", "$.nonexistent", "value" ); console.log(index); // [null] ``` ## JSONPath Syntax * `$.array` - Search in a top-level array * `$.nested.array` - Search in a nested array * `$.items[0].tags` - Search in array within array element * `$..array` - Search in all matching arrays recursively * `$.users[*].roles` - Search in arrays within each user ## Notes * Only the first occurrence within the search range is returned * Negative indices count backwards from the end of the array * The stop index is exclusive (not included in the search) * Returns `-1` if the value is not found in the array * Returns `null` if the path doesn't exist or doesn't point to an array * Object comparison is based on exact JSON match ## See Also * [JSON.ARRAPPEND](/api/commands/json-arrappend) - Append values to array * [JSON.GET](/api/commands/json-get) - Get JSON values * [Redis JSON.ARRINDEX documentation](https://redis.io/commands/json.arrindex) # JSON.DEL Source: https://upstash-redis-js.mintlify.app/api/commands/json-del Delete JSON values from Redis Deletes a value at the specified path in a JSON document. If no path is specified, deletes the entire key. ## Usage ```typescript theme={null} const deletedCount = await redis.json.del("user:123", "$.age"); ``` ## Arguments The key containing the JSON document JSONPath expression specifying which parts to delete. If omitted, deletes the entire key. ## Response The number of paths deleted. Returns `0` if the key or path doesn't exist. ## Examples ### Delete entire JSON document ```typescript theme={null} const deletedCount = await redis.json.del("user:123"); console.log(deletedCount); // 1 // Key is now deleted const user = await redis.json.get("user:123"); console.log(user); // null ``` ### Delete specific field ```typescript theme={null} // Before: { "name": "Alice", "age": 30, "email": "alice@example.com" } await redis.json.del("user:123", "$.age"); // After: { "name": "Alice", "email": "alice@example.com" } const user = await redis.json.get("user:123"); console.log(user); // { name: "Alice", email: "alice@example.com" } ``` ### Delete nested field ```typescript theme={null} // Delete a nested field await redis.json.del("user:123", "$.address.zipcode"); ``` ### Delete array element ```typescript theme={null} // Before: { "tags": ["developer", "admin", "user"] } await redis.json.del("user:123", "$.tags[1]"); // After: { "tags": ["developer", "user"] } ``` ### Delete multiple paths ```typescript theme={null} // Using a wildcard path to delete multiple matching elements const deletedCount = await redis.json.del("store:1", "$.products[*].discount"); console.log(`Deleted ${deletedCount} discount fields`); ``` ### Check if deletion was successful ```typescript theme={null} const deletedCount = await redis.json.del("user:123", "$.premium"); if (deletedCount > 0) { console.log("Premium flag removed"); } else { console.log("Premium flag didn't exist"); } ``` ## JSONPath Syntax * `$` - Root element (deletes entire document) * `$.field` - Delete a specific field * `$.field[0]` - Delete array element at index * `$.nested.field` - Delete nested field * `$..field` - Delete all matching fields recursively * `$.array[*]` - Delete all array elements ## See Also * [JSON.GET](/api/commands/json-get) - Get JSON values * [JSON.SET](/api/commands/json-set) - Set JSON values * [Redis JSON.DEL documentation](https://redis.io/commands/json.del) # JSON.GET Source: https://upstash-redis-js.mintlify.app/api/commands/json-get Get JSON values from a Redis key Retrieves the JSON value stored at the specified key and optionally formats the output. ## Usage ```typescript theme={null} const result = await redis.json.get("user:123"); ``` ## Arguments The key containing the JSON document Optional formatting options for the returned JSON string Sets the indentation string for nested levels Sets the string that's printed at the end of each line Sets the string that's put between a key and a value JSONPath expression(s) to specify which parts of the JSON to retrieve. If not specified, returns the entire JSON document. ## Response The JSON value at the specified path, or `null` if the key doesn't exist. The return type is generic and can be specified using TypeScript. ## Examples ### Get entire JSON document ```typescript theme={null} interface User { name: string; email: string; age: number; } const user = await redis.json.get("user:123"); console.log(user); // { name: "Alice", email: "alice@example.com", age: 30 } ``` ### Get specific path ```typescript theme={null} const name = await redis.json.get("user:123", "$.name"); console.log(name); // ["Alice"] ``` ### Get with formatting options ```typescript theme={null} const formatted = await redis.json.get( "user:123", { indent: " ", newline: "\n", space: " " }, "$" ); ``` ### Get multiple paths ```typescript theme={null} const data = await redis.json.get("user:123", "$.name", "$.email"); ``` ## JSONPath Syntax Upstash Redis supports JSONPath expressions for querying JSON documents: * `$` - Root element * `$.field` - Access a field * `$.field[0]` - Access array element * `$..field` - Recursive descent * `$.field[*]` - All array elements ## See Also * [JSON.SET](/api/commands/json-set) - Set JSON values * [JSON.DEL](/api/commands/json-del) - Delete JSON values * [Redis JSON.GET documentation](https://redis.io/commands/json.get) # JSON.NUMINCRBY Source: https://upstash-redis-js.mintlify.app/api/commands/json-numincrby Increment a number in a JSON document Increments the numeric value at the specified path by a given amount. Supports both integers and floating-point numbers. ## Usage ```typescript theme={null} const newValue = await redis.json.numincrby("user:123", "$.score", 10); ``` ## Arguments The key containing the JSON document JSONPath expression pointing to the numeric value to increment The increment value. Can be positive (to increase) or negative (to decrease). Supports both integers and floating-point numbers. ## Response An array of numbers representing the new value at each matching path after the increment. Returns `null` for paths that don't exist or aren't numeric values. ## Examples ### Increment an integer ```typescript theme={null} // Initial: { "score": 100 } const newScore = await redis.json.numincrby("user:123", "$.score", 50); console.log(newScore); // [150] // Result: { "score": 150 } ``` ### Decrement by using negative value ```typescript theme={null} // Initial: { "balance": 1000 } const newBalance = await redis.json.numincrby("account:456", "$.balance", -250); console.log(newBalance); // [750] // Result: { "balance": 750 } ``` ### Increment a floating-point number ```typescript theme={null} // Initial: { "price": 19.99 } const newPrice = await redis.json.numincrby("product:789", "$.price", 5.50); console.log(newPrice); // [25.49] // Result: { "price": 25.49 } ``` ### Increment nested numeric value ```typescript theme={null} interface UserStats { profile: { name: string; stats: { points: number; level: number; }; }; } const newPoints = await redis.json.numincrby( "user:123", "$.profile.stats.points", 100 ); console.log(newPoints); // [1100] ``` ### Increment multiple values (wildcard path) ```typescript theme={null} // Initial: { // "products": [ // { "id": 1, "stock": 10 }, // { "id": 2, "stock": 5 } // ] // } const newStocks = await redis.json.numincrby( "inventory:1", "$.products[*].stock", -1 ); console.log(newStocks); // [9, 4] // Result: All product stocks decreased by 1 ``` ### Increment with decimal precision ```typescript theme={null} // Useful for currency calculations const newTotal = await redis.json.numincrby( "order:123", "$.total", 0.01 ); console.log(newTotal); // [99.99] ``` ### Track counter ```typescript theme={null} // Increment page view counter const views = await redis.json.numincrby( "analytics:homepage", "$.views", 1 ); console.log(`Total views: ${views[0]}`); ``` ### Handle non-existent paths ```typescript theme={null} const result = await redis.json.numincrby( "user:123", "$.nonexistent", 10 ); console.log(result); // [null] ``` ### Handle non-numeric values ```typescript theme={null} // Initial: { "name": "Alice" } const result = await redis.json.numincrby( "user:123", "$.name", 10 ); console.log(result); // [null] - cannot increment a string ``` ## JSONPath Syntax * `$.number` - Increment a top-level number * `$.nested.number` - Increment a nested number * `$.object.count` - Increment a number within an object * `$..count` - Increment all matching numbers recursively * `$.items[*].quantity` - Increment numbers within each item ## Notes * The operation is atomic - the increment happens as a single operation * Both integers and floating-point numbers are supported * Negative values can be used to decrement * Returns `null` if the path doesn't exist or doesn't point to a numeric value * When using wildcard paths, the operation is performed on all matching numeric values * Floating-point arithmetic follows standard IEEE 754 rules ## See Also * [JSON.SET](/api/commands/json-set) - Set JSON values * [JSON.GET](/api/commands/json-get) - Get JSON values * [Redis JSON.NUMINCRBY documentation](https://redis.io/commands/json.numincrby) # JSON.OBJKEYS Source: https://upstash-redis-js.mintlify.app/api/commands/json-objkeys Get the keys of a JSON object Returns the keys of the JSON object at the specified path. ## Usage ```typescript theme={null} const keys = await redis.json.objkeys("user:123", "$"); ``` ## Arguments The key containing the JSON document JSONPath expression pointing to the object whose keys to retrieve. If omitted, defaults to the root path `$`. ## Response An array containing arrays of object keys for each matching path. Returns `null` for paths that don't exist or aren't objects. ## Examples ### Get keys from root object ```typescript theme={null} // Document: { "name": "Alice", "age": 30, "email": "alice@example.com" } const keys = await redis.json.objkeys("user:123"); console.log(keys); // [["name", "age", "email"]] ``` ### Get keys with explicit path ```typescript theme={null} // Document: { "name": "Alice", "age": 30, "email": "alice@example.com" } const keys = await redis.json.objkeys("user:123", "$"); console.log(keys); // [["name", "age", "email"]] ``` ### Get keys from nested object ```typescript theme={null} interface User { name: string; profile: { bio: string; website: string; location: string; }; } // Document: { // "name": "Alice", // "profile": { // "bio": "Developer", // "website": "example.com", // "location": "NYC" // } // } const profileKeys = await redis.json.objkeys("user:123", "$.profile"); console.log(profileKeys); // [["bio", "website", "location"]] ``` ### Get keys from multiple objects (wildcard path) ```typescript theme={null} // Document: { // "users": [ // { "id": 1, "name": "Alice", "active": true }, // { "id": 2, "name": "Bob", "role": "admin" } // ] // } const allKeys = await redis.json.objkeys("data:1", "$.users[*]"); console.log(allKeys); // [ // ["id", "name", "active"], // ["id", "name", "role"] // ] ``` ### Check object structure ```typescript theme={null} const keys = await redis.json.objkeys("config:app", "$.database"); if (keys[0]?.includes("password")) { console.log("Database config includes password"); } ``` ### Iterate over object keys ```typescript theme={null} const keys = await redis.json.objkeys("settings:1", "$"); if (keys[0]) { for (const key of keys[0]) { console.log(`Found key: ${key}`); } } ``` ### Get keys from deeply nested objects ```typescript theme={null} // Document: { // "app": { // "settings": { // "theme": { // "colors": { "primary": "blue", "secondary": "green" } // } // } // } // } const colorKeys = await redis.json.objkeys( "config:1", "$.app.settings.theme.colors" ); console.log(colorKeys); // [["primary", "secondary"]] ``` ### Handle non-existent paths ```typescript theme={null} const keys = await redis.json.objkeys("user:123", "$.nonexistent"); console.log(keys); // [null] ``` ### Handle non-object values ```typescript theme={null} // Document: { "count": 42 } const keys = await redis.json.objkeys("data:1", "$.count"); console.log(keys); // [null] - count is a number, not an object ``` ### Get keys from array of objects ```typescript theme={null} // Document: { // "products": [ // { "id": "1", "name": "Widget", "price": 9.99 }, // { "id": "2", "title": "Gadget", "price": 19.99, "stock": 5 } // ] // } const productKeys = await redis.json.objkeys("store:1", "$.products[*]"); console.log(productKeys); // [ // ["id", "name", "price"], // ["id", "title", "price", "stock"] // ] ``` ## JSONPath Syntax * `$` - Get keys from root object * `$.object` - Get keys from a nested object * `$.nested.object` - Get keys from deeply nested object * `$..object` - Get keys from all matching objects recursively * `$.array[*]` - Get keys from objects within an array * `$.array[0]` - Get keys from specific array element ## Notes * The path must point to a JSON object. Arrays, strings, numbers, and other non-object values return `null` * The order of keys in the returned array may not match the insertion order * When using wildcard paths, each matching object returns its own array of keys * Returns an empty array `[]` for empty objects `{}` * Returns `null` for paths that don't exist or don't point to objects ## See Also * [JSON.GET](/api/commands/json-get) - Get JSON values * [JSON.SET](/api/commands/json-set) - Set JSON values * [Redis JSON.OBJKEYS documentation](https://redis.io/commands/json.objkeys) # JSON.SET Source: https://upstash-redis-js.mintlify.app/api/commands/json-set Set JSON values in Redis Sets the JSON value at the specified path in the key. Creates the key if it doesn't exist. ## Usage ```typescript theme={null} await redis.json.set("user:123", "$", { name: "Alice", age: 30 }); ``` ## Arguments The key to store the JSON document JSONPath expression specifying where to set the value in the JSON document. Use `$` to set the entire document. The JSON value to set. Can be a number, string, boolean, object, or array. Optional conditional set options Only set if the path does not exist. Cannot be used with `xx`. Only set if the path already exists. Cannot be used with `nx`. ## Response Returns `"OK"` if the value was set successfully, or `null` if the condition (NX/XX) was not met. ## Examples ### Set entire JSON document ```typescript theme={null} interface User { name: string; email: string; age: number; tags: string[]; } const user: User = { name: "Alice", email: "alice@example.com", age: 30, tags: ["developer", "admin"] }; await redis.json.set("user:123", "$", user); // "OK" ``` ### Set specific field ```typescript theme={null} await redis.json.set("user:123", "$.age", 31); // "OK" ``` ### Set nested value ```typescript theme={null} await redis.json.set("user:123", "$.address.city", "New York"); ``` ### Set array element ```typescript theme={null} await redis.json.set("user:123", "$.tags[0]", "senior-developer"); ``` ### Conditional set - only if path doesn't exist (NX) ```typescript theme={null} const result = await redis.json.set( "user:123", "$.premium", true, { nx: true } ); if (result === "OK") { console.log("Premium flag added"); } else { console.log("Premium flag already exists"); } ``` ### Conditional set - only if path exists (XX) ```typescript theme={null} const result = await redis.json.set( "user:123", "$.age", 32, { xx: true } ); if (result === "OK") { console.log("Age updated"); } else { console.log("Age field doesn't exist"); } ``` ## JSONPath Syntax * `$` - Root element (sets entire document) * `$.field` - Set a specific field * `$.field[0]` - Set array element at index * `$.nested.field` - Set nested field ## See Also * [JSON.GET](/api/commands/json-get) - Get JSON values * [JSON.DEL](/api/commands/json-del) - Delete JSON values * [Redis JSON.SET documentation](https://redis.io/commands/json.set) # keys Source: https://upstash-redis-js.mintlify.app/api/commands/keys Find all keys matching a pattern ## Description Returns all keys matching a glob-style pattern. This command should be used with caution in production environments as it can impact performance on large databases. Consider using `SCAN` instead of `KEYS` for production use cases. `KEYS` blocks the server until it completes, while `SCAN` provides an incremental iteration approach. ## Method Signature ```typescript theme={null} keys(pattern: string): Promise ``` ## Parameters The glob-style pattern to match keys against. Supported glob patterns: * `*` matches any number of characters (including zero) * `?` matches exactly one character * `[abc]` matches one character from the set * `[^abc]` matches one character not in the set * `[a-z]` matches one character from the range * `\` escapes special characters ## Return Value Array of keys matching the pattern. Returns an empty array if no keys match. ## Examples ### Match all keys ```typescript theme={null} await redis.set("user:1", "Alice"); await redis.set("user:2", "Bob"); await redis.set("post:1", "Hello"); const allKeys = await redis.keys("*"); console.log(allKeys); // ["user:1", "user:2", "post:1"] ``` ### Match with prefix ```typescript theme={null} const userKeys = await redis.keys("user:*"); console.log(userKeys); // ["user:1", "user:2"] ``` ### Match with wildcard in middle ```typescript theme={null} await redis.set("user:1:name", "Alice"); await redis.set("user:1:email", "alice@example.com"); await redis.set("user:2:name", "Bob"); const user1Keys = await redis.keys("user:1:*"); console.log(user1Keys); // ["user:1:name", "user:1:email"] ``` ### Match single character ```typescript theme={null} await redis.set("key1", "a"); await redis.set("key2", "b"); await redis.set("key10", "c"); const keys = await redis.keys("key?"); console.log(keys); // ["key1", "key2"] (not key10) ``` ### Match character range ```typescript theme={null} await redis.set("user:a", "Alice"); await redis.set("user:b", "Bob"); await redis.set("user:z", "Zoe"); const keys = await redis.keys("user:[a-c]"); console.log(keys); // ["user:a", "user:b"] (not user:z) ``` ### No matches ```typescript theme={null} const keys = await redis.keys("nonexistent:*"); console.log(keys); // [] ``` ## Performance Considerations `KEYS` is a blocking operation that scans the entire keyspace. For production use: * Use `SCAN` for incremental iteration * Avoid using `KEYS` on large databases * Consider using key naming schemes that allow targeted queries ## See Also * [scan](/api/commands/scan) - Incrementally iterate over keys * [exists](/api/commands/exists) - Check if specific keys exist # LINDEX Source: https://upstash-redis-js.mintlify.app/api/commands/lindex Get an element from a list by index ## Overview Returns the element at index `index` in the list stored at `key`. The index is zero-based. Negative indices can be used to designate elements starting from the tail of the list. ## Method Signature ```typescript theme={null} lindex(key: string, index: number): Promise ``` ## Parameters The key of the list. The index of the element to retrieve (zero-based). Negative indices count from the end of the list (e.g., `-1` is the last element, `-2` is the second-to-last). ## Return Value The element at the specified index, or `null` if the index is out of range or the list does not exist. ## Examples ### Basic Usage ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, }); // Set up a list await redis.rpush('mylist', 'one', 'two', 'three', 'four'); // Get element at index 0 const first = await redis.lindex('mylist', 0); console.log(first); // 'one' // Get element at index 2 const third = await redis.lindex('mylist', 2); console.log(third); // 'three' // Out of range const outOfRange = await redis.lindex('mylist', 10); console.log(outOfRange); // null ``` ### Using Negative Indexes ```typescript theme={null} await redis.rpush('items', 'A', 'B', 'C', 'D', 'E'); // Get the last element const last = await redis.lindex('items', -1); console.log(last); // 'E' // Get the second-to-last element const secondLast = await redis.lindex('items', -2); console.log(secondLast); // 'D' // Get the first element using negative index const first = await redis.lindex('items', -5); console.log(first); // 'A' ``` ### Working with Objects ```typescript theme={null} interface User { id: string; name: string; email: string; } // Add users await redis.rpush('users', { id: '1', name: 'Alice', email: 'alice@example.com' }, { id: '2', name: 'Bob', email: 'bob@example.com' }, { id: '3', name: 'Charlie', email: 'charlie@example.com' } ); // Get specific user const user = await redis.lindex('users', 1); console.log(user?.name); // 'Bob' ``` ### Peek at Queue Elements ```typescript theme={null} // Peek at the first item in a queue without removing it const nextJob = await redis.lindex('queue:jobs', 0); if (nextJob) { console.log('Next job to process:', nextJob); } else { console.log('Queue is empty'); } // Peek at the last item added const lastAdded = await redis.lindex('queue:jobs', -1); console.log('Most recently added job:', lastAdded); ``` ### Accessing List Elements ```typescript theme={null} // Get middle element const length = await redis.llen('items'); const middleIndex = Math.floor(length / 2); const middleElement = await redis.lindex('items', middleIndex); console.log('Middle element:', middleElement); ``` ### Error Handling ```typescript theme={null} const element = await redis.lindex('mylist', 5); if (element === null) { console.log('Element not found - index out of range or list does not exist'); } else { console.log('Found element:', element); } ``` ### Iterating Through a List ```typescript theme={null} const length = await redis.llen('mylist'); for (let i = 0; i < length; i++) { const element = await redis.lindex('mylist', i); console.log(`Element ${i}:`, element); } // Note: For iterating through all elements, LRANGE is more efficient ``` ## See Also * [LSET](/api/commands/lset) - Set the value of an element by index * [LRANGE](/api/commands/lrange) - Get a range of elements from a list * [LLEN](/api/commands/llen) - Get the length of a list * [LPUSH](/api/commands/lpush) - Insert elements at the head of a list # LLEN Source: https://upstash-redis-js.mintlify.app/api/commands/llen Get the length of a list ## Overview Returns the length of the list stored at `key`. If `key` does not exist, it is interpreted as an empty list and `0` is returned. ## Method Signature ```typescript theme={null} llen(key: string): Promise ``` ## Parameters The key of the list. ## Return Value The length of the list. Returns `0` if the list does not exist. ## Examples ### Basic Usage ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, }); // Add some elements await redis.rpush('mylist', 'one', 'two', 'three'); // Get the length const length = await redis.llen('mylist'); console.log(length); // 3 // Check non-existent list const emptyLength = await redis.llen('nonexistent'); console.log(emptyLength); // 0 ``` ### Queue Size Monitoring ```typescript theme={null} // Check queue size before processing const queueSize = await redis.llen('queue:jobs'); if (queueSize > 100) { console.log('Queue is getting long, consider scaling up workers'); } if (queueSize === 0) { console.log('No jobs to process'); } ``` ### Tracking List Growth ```typescript theme={null} // Monitor list size over time const initialSize = await redis.llen('events'); // Add new events await redis.rpush('events', 'event1', 'event2', 'event3'); const newSize = await redis.llen('events'); const added = newSize - initialSize; console.log(`Added ${added} new events`); ``` ### Conditional Operations ```typescript theme={null} // Only add items if list is below a certain size const maxSize = 1000; const currentSize = await redis.llen('limited:list'); if (currentSize < maxSize) { await redis.rpush('limited:list', 'new-item'); } else { console.log('List is full, cannot add more items'); } ``` ### Pagination Info ```typescript theme={null} const pageSize = 10; const totalItems = await redis.llen('items'); const totalPages = Math.ceil(totalItems / pageSize); console.log(`Total items: ${totalItems}`); console.log(`Total pages: ${totalPages}`); ``` ### Health Check ```typescript theme={null} // Check if critical queues are empty const criticalQueues = ['queue:high-priority', 'queue:normal', 'queue:low']; for (const queue of criticalQueues) { const size = await redis.llen(queue); console.log(`${queue}: ${size} items`); } ``` ### Rate Limiting Check ```typescript theme={null} // Check if user has exceeded rate limit const userKey = `rate:user:${userId}`; const requestCount = await redis.llen(userKey); if (requestCount > 100) { throw new Error('Rate limit exceeded'); } await redis.rpush(userKey, Date.now().toString()); ``` ## See Also * [LRANGE](/api/commands/lrange) - Get a range of elements from a list * [LPUSH](/api/commands/lpush) - Insert elements at the head of a list * [RPUSH](/api/commands/rpush) - Insert elements at the tail of a list * [LPOP](/api/commands/lpop) - Remove and return the first element of a list # LPOP Source: https://upstash-redis-js.mintlify.app/api/commands/lpop Remove and return the first element(s) of a list ## Overview Removes and returns the first element of the list stored at `key`. When a `count` is provided, removes and returns up to `count` elements. ## Method Signature ```typescript theme={null} lpop(key: string): Promise lpop(key: string, count: number): Promise ``` ## Parameters The key of the list. The number of elements to pop. When specified, returns an array of elements instead of a single element. ## Return Value When called without `count`: The first element of the list, or `null` if the list is empty or does not exist. When called with `count`: An array of popped elements, or `null` if the list is empty or does not exist. ## Examples ### Basic Usage ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, }); // Set up a list await redis.lpush('mylist', 'world', 'hello'); // List is: ['hello', 'world'] // Pop the first element const element = await redis.lpop('mylist'); console.log(element); // 'hello' // Pop from empty list const empty = await redis.lpop('nonexistent'); console.log(empty); // null ``` ### Pop Multiple Elements ```typescript theme={null} // Set up a list await redis.rpush('queue', 'job-1', 'job-2', 'job-3', 'job-4'); // Pop multiple elements const jobs = await redis.lpop('queue', 2); console.log(jobs); // ['job-1', 'job-2'] ``` ### Working with Objects ```typescript theme={null} interface Task { id: string; title: string; priority: number; } // Add tasks await redis.lpush('tasks', { id: '123', title: 'Complete documentation', priority: 1, }); // Pop the task const task = await redis.lpop('tasks'); console.log(task?.title); // 'Complete documentation' ``` ### Queue Processing ```typescript theme={null} // Process items from the head of a queue while (true) { const item = await redis.lpop('queue:processing'); if (!item) { break; // Queue is empty } // Process the item await processItem(item); } ``` ### Batch Processing ```typescript theme={null} // Process multiple items at once const batchSize = 10; const batch = await redis.lpop('queue:batch', batchSize); if (batch) { await Promise.all(batch.map(item => processItem(item))); } ``` ## See Also * [RPOP](/api/commands/rpop) - Remove and return the last element of a list * [LPUSH](/api/commands/lpush) - Insert elements at the head of a list * [LRANGE](/api/commands/lrange) - Get a range of elements from a list * [LLEN](/api/commands/llen) - Get the length of a list # LPUSH Source: https://upstash-redis-js.mintlify.app/api/commands/lpush Insert elements at the head of a list ## Overview Inserts one or more values at the head of the list stored at `key`. If `key` does not exist, it is created as an empty list before performing the push operations. ## Method Signature ```typescript theme={null} lpush(key: string, ...elements: TData[]): Promise ``` ## Parameters The key of the list. One or more elements to insert at the head of the list. Elements are inserted one after the other, so the first element in the arguments will be the first element of the list. ## Return Value The length of the list after the push operations. ## Examples ### Basic Usage ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, }); // Push a single element const length1 = await redis.lpush('mylist', 'world'); console.log(length1); // 1 // Push multiple elements const length2 = await redis.lpush('mylist', 'hello', 'hi'); console.log(length2); // 3 // List is now: ['hi', 'hello', 'world'] ``` ### Working with Objects ```typescript theme={null} interface Task { id: string; title: string; priority: number; } const task: Task = { id: '123', title: 'Complete documentation', priority: 1, }; const length = await redis.lpush('tasks', task); console.log(length); // 1 ``` ### Building a Queue ```typescript theme={null} // Add new items to the head of the queue await redis.lpush('queue:jobs', 'job-1'); await redis.lpush('queue:jobs', 'job-2'); await redis.lpush('queue:jobs', 'job-3'); // Queue is now: ['job-3', 'job-2', 'job-1'] ``` ## See Also * [RPUSH](/api/commands/rpush) - Insert elements at the tail of a list * [LPOP](/api/commands/lpop) - Remove and return the first element of a list * [LRANGE](/api/commands/lrange) - Get a range of elements from a list * [LLEN](/api/commands/llen) - Get the length of a list # LRANGE Source: https://upstash-redis-js.mintlify.app/api/commands/lrange Get a range of elements from a list ## Overview Returns the specified elements of the list stored at `key`. The offsets `start` and `end` are zero-based indexes. These offsets can also be negative numbers indicating offsets from the end of the list. ## Method Signature ```typescript theme={null} lrange(key: string, start: number, end: number): Promise ``` ## Parameters The key of the list. The starting index (zero-based). Negative values indicate offsets from the end of the list (e.g., `-1` is the last element, `-2` is the second-to-last). The ending index (zero-based, inclusive). Negative values indicate offsets from the end of the list. Use `-1` to get all elements from `start` to the end of the list. ## Return Value An array of elements in the specified range. Returns an empty array if the list does not exist or the range is out of bounds. ## Examples ### Basic Usage ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, }); // Set up a list await redis.rpush('mylist', 'one', 'two', 'three', 'four', 'five'); // Get elements from index 1 to 3 const range = await redis.lrange('mylist', 1, 3); console.log(range); // ['two', 'three', 'four'] // Get all elements const all = await redis.lrange('mylist', 0, -1); console.log(all); // ['one', 'two', 'three', 'four', 'five'] ``` ### Using Negative Indexes ```typescript theme={null} await redis.rpush('items', 'A', 'B', 'C', 'D', 'E'); // Get the last 3 elements const lastThree = await redis.lrange('items', -3, -1); console.log(lastThree); // ['C', 'D', 'E'] // Get all except the first element const withoutFirst = await redis.lrange('items', 1, -1); console.log(withoutFirst); // ['B', 'C', 'D', 'E'] // Get all except the last element const withoutLast = await redis.lrange('items', 0, -2); console.log(withoutLast); // ['A', 'B', 'C', 'D'] ``` ### Working with Objects ```typescript theme={null} interface Product { id: string; name: string; price: number; } // Add products await redis.rpush('products', { id: '1', name: 'Laptop', price: 999 }, { id: '2', name: 'Mouse', price: 25 }, { id: '3', name: 'Keyboard', price: 75 } ); // Get first two products const products = await redis.lrange('products', 0, 1); console.log(products[0].name); // 'Laptop' console.log(products[1].name); // 'Mouse' ``` ### Pagination ```typescript theme={null} const pageSize = 10; const pageNumber = 2; // 0-based const start = pageNumber * pageSize; const end = start + pageSize - 1; const page = await redis.lrange('items', start, end); console.log(`Page ${pageNumber + 1}:`, page); ``` ### Getting Recent Items ```typescript theme={null} // Get the 5 most recent items (assuming newer items are pushed with RPUSH) const recentCount = 5; const recentItems = await redis.lrange('recent:activity', -recentCount, -1); console.log('Recent items:', recentItems); ``` ### Viewing Queue Contents ```typescript theme={null} // View the next 10 items in a queue without removing them const nextJobs = await redis.lrange('queue:jobs', 0, 9); console.log('Next jobs to process:', nextJobs); // Check if queue has items const hasItems = nextJobs.length > 0; ``` ## See Also * [LLEN](/api/commands/llen) - Get the length of a list * [LINDEX](/api/commands/lindex) - Get an element by index * [LPUSH](/api/commands/lpush) - Insert elements at the head of a list * [RPUSH](/api/commands/rpush) - Insert elements at the tail of a list # LSET Source: https://upstash-redis-js.mintlify.app/api/commands/lset Set the value of an element in a list by index ## Overview Sets the list element at `index` to `value`. The index is zero-based. An error is returned for out of range indexes. ## Method Signature ```typescript theme={null} lset(key: string, index: number, value: TData): Promise<"OK"> ``` ## Parameters The key of the list. The index of the element to set (zero-based). Negative indices count from the end of the list (e.g., `-1` is the last element, `-2` is the second-to-last). The new value to set at the specified index. ## Return Value Returns `"OK"` on success. Throws an error if the index is out of range or the key does not exist. ## Examples ### Basic Usage ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, }); // Set up a list await redis.rpush('mylist', 'one', 'two', 'three'); // Update element at index 1 const result = await redis.lset('mylist', 1, 'TWO'); console.log(result); // 'OK' // Verify the change const updated = await redis.lindex('mylist', 1); console.log(updated); // 'TWO' ``` ### Using Negative Indexes ```typescript theme={null} await redis.rpush('items', 'A', 'B', 'C', 'D'); // Update the last element await redis.lset('items', -1, 'Z'); // Update the second-to-last element await redis.lset('items', -2, 'Y'); const list = await redis.lrange('items', 0, -1); console.log(list); // ['A', 'B', 'Y', 'Z'] ``` ### Working with Objects ```typescript theme={null} interface Task { id: string; title: string; status: 'pending' | 'completed'; } // Add tasks await redis.rpush('tasks', { id: '1', title: 'Write docs', status: 'pending' }, { id: '2', title: 'Review code', status: 'pending' } ); // Update a task const updatedTask: Task = { id: '1', title: 'Write docs', status: 'completed' }; await redis.lset('tasks', 0, updatedTask); ``` ### Error Handling ```typescript theme={null} try { // This will throw an error if the index is out of range await redis.lset('mylist', 100, 'value'); } catch (error) { console.error('Failed to set element:', error); } // Check list length before setting const length = await redis.llen('mylist'); const index = 5; if (index < length) { await redis.lset('mylist', index, 'new value'); } else { console.log('Index out of range'); } ``` ### Updating Specific Fields ```typescript theme={null} interface Config { version: string; enabled: boolean; settings: Record; } // Get current config const currentConfig = await redis.lindex('configs', 0); if (currentConfig) { // Update the config const updatedConfig: Config = { ...currentConfig, enabled: false, version: '2.0.0' }; await redis.lset('configs', 0, updatedConfig); } ``` ### Batch Updates ```typescript theme={null} // Update multiple elements const updates = [ { index: 0, value: 'updated-0' }, { index: 2, value: 'updated-2' }, { index: 4, value: 'updated-4' } ]; for (const { index, value } of updates) { await redis.lset('mylist', index, value); } ``` ### Toggle Values ```typescript theme={null} // Toggle a boolean value in a list const index = 0; const current = await redis.lindex('flags', index); const newValue = current === 'true' ? 'false' : 'true'; await redis.lset('flags', index, newValue); ``` ### Replacing Queue Items ```typescript theme={null} // Replace a failed job with a retry const failedIndex = 0; const job = await redis.lindex('queue:failed', failedIndex); if (job) { // Modify job to retry const retryJob = { ...job, retryCount: (job.retryCount || 0) + 1 }; await redis.lset('queue:failed', failedIndex, retryJob); } ``` ## See Also * [LINDEX](/api/commands/lindex) - Get an element from a list by index * [LRANGE](/api/commands/lrange) - Get a range of elements from a list * [LLEN](/api/commands/llen) - Get the length of a list * [LPUSH](/api/commands/lpush) - Insert elements at the head of a list # mget Source: https://upstash-redis-js.mintlify.app/api/commands/mget Get the values of multiple keys ## Description Returns the values of all specified keys. For every key that does not hold a string value or does not exist, the special value `null` is returned. ## Syntax ```typescript theme={null} // Using array redis.mget(keys: string[]): Promise // Using rest parameters redis.mget(...keys: string[]): Promise ``` ## Parameters The keys to retrieve. Can be passed as an array or as individual arguments ## Type Parameters An array type representing the expected types of the returned values. Each element can be a specific type or `null` if the key doesn't exist. ## Returns An array of values at the specified keys. For keys that don't exist, `null` is returned at that position. ## Examples ### Basic Usage ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: 'https://your-redis-url.upstash.io', token: 'your-token' }); // Set multiple values await redis.set('key1', 'value1'); await redis.set('key2', 'value2'); await redis.set('key3', 'value3'); // Get multiple values using array const values = await redis.mget(['key1', 'key2', 'key3']); console.log(values); // ["value1", "value2", "value3"] ``` ### Using Rest Parameters ```typescript theme={null} // Get multiple values using rest parameters const values = await redis.mget('key1', 'key2', 'key3'); console.log(values); // ["value1", "value2", "value3"] ``` ### Handling Non-Existent Keys ```typescript theme={null} await redis.set('exists1', 'value1'); await redis.set('exists2', 'value2'); // Mix of existing and non-existent keys const values = await redis.mget(['exists1', 'nonexistent', 'exists2']); console.log(values); // ["value1", null, "value2"] ``` ### Fetching User Data ```typescript theme={null} // Store user data await redis.set('user:1:name', 'Alice'); await redis.set('user:1:email', 'alice@example.com'); await redis.set('user:1:age', '30'); // Fetch all user data at once const [name, email, age] = await redis.mget( 'user:1:name', 'user:1:email', 'user:1:age' ); console.log({ name, email, age }); // { name: "Alice", email: "alice@example.com", age: "30" } ``` ### Batch Fetching with Type Safety ```typescript theme={null} interface UserData { name: string; email: string; role: string; } // Store user data as JSON const user1 = { name: 'Alice', email: 'alice@example.com', role: 'admin' }; const user2 = { name: 'Bob', email: 'bob@example.com', role: 'user' }; await redis.set('user:1', user1); await redis.set('user:2', user2); // Fetch multiple users const users = await redis.mget<(UserData | null)[]>('user:1', 'user:2', 'user:3'); console.log(users); // [ // { name: "Alice", email: "alice@example.com", role: "admin" }, // { name: "Bob", email: "bob@example.com", role: "user" }, // null // ] ``` ### Configuration Settings ```typescript theme={null} // Store configuration values await redis.set('config:api_url', 'https://api.example.com'); await redis.set('config:timeout', '5000'); await redis.set('config:max_retries', '3'); // Fetch all config at once const [apiUrl, timeout, maxRetries] = await redis.mget( 'config:api_url', 'config:timeout', 'config:max_retries' ); const config = { apiUrl, timeout: timeout ? parseInt(timeout) : 3000, maxRetries: maxRetries ? parseInt(maxRetries) : 3 }; console.log(config); ``` ### Dynamic Key Fetching ```typescript theme={null} // Generate keys dynamically const userIds = ['123', '456', '789']; const keys = userIds.map(id => `user:${id}:score`); // Set some scores await redis.set('user:123:score', '100'); await redis.set('user:456:score', '85'); // user:789:score doesn't exist // Fetch all scores const scores = await redis.mget(keys); console.log(scores); // ["100", "85", null] // Filter out null values const validScores = scores.filter((score): score is string => score !== null); console.log(validScores); // ["100", "85"] ``` ### Cache Warming ```typescript theme={null} // Warm up cache with multiple keys async function warmCache(productIds: string[]) { const keys = productIds.map(id => `product:${id}`); const products = await redis.mget(keys); // Find missing products const missingIndices = products .map((p, i) => p === null ? i : -1) .filter(i => i !== -1); if (missingIndices.length > 0) { console.log(`Cache miss for products: ${missingIndices.map(i => productIds[i])}`); // Fetch from database and populate cache } return products; } const productData = await warmCache(['p1', 'p2', 'p3']); ``` ## Performance Considerations * MGET is more efficient than multiple GET commands because it reduces network round trips * For fetching many keys, MGET significantly reduces latency compared to sequential GET calls * The command has O(N) time complexity where N is the number of keys to retrieve ## Notes * The order of values in the result matches the order of keys in the request * Non-existent keys return `null` at their respective positions * This is an atomic operation * MGET is significantly faster than multiple GET commands due to reduced network overhead ## Related Commands * [get](/api/commands/get) - Get the value of a single key * [mset](/api/commands/mset) - Set multiple keys to multiple values ## Redis Documentation For more information, see the [Redis MGET documentation](https://redis.io/commands/mget). # mset Source: https://upstash-redis-js.mintlify.app/api/commands/mset Set multiple keys to multiple values ## Description Sets the given keys to their respective values. MSET replaces existing values with new values, just as regular SET. MSET is atomic, so all given keys are set at once. It is not possible for clients to see that some of the keys were updated while others are unchanged. ## Syntax ```typescript theme={null} redis.mset(kv: Record): Promise<"OK"> ``` ## Parameters An object where keys are Redis keys and values are the values to set ## Type Parameters The type of values being stored. All values in the object should be of the same type. ## Returns Always returns `"OK"` when the operation succeeds ## Examples ### Basic Usage ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: 'https://your-redis-url.upstash.io', token: 'your-token' }); // Set multiple string values const result = await redis.mset({ key1: 'value1', key2: 'value2', key3: 'value3' }); console.log(result); // "OK" // Verify the values were set const values = await redis.mget('key1', 'key2', 'key3'); console.log(values); // ["value1", "value2", "value3"] ``` ### Setting User Profile Data ```typescript theme={null} // Set multiple user fields at once await redis.mset({ 'user:123:name': 'Alice', 'user:123:email': 'alice@example.com', 'user:123:age': '30', 'user:123:role': 'admin' }); // Fetch user data const [name, email, age, role] = await redis.mget( 'user:123:name', 'user:123:email', 'user:123:age', 'user:123:role' ); console.log({ name, email, age, role }); ``` ### Storing Objects ```typescript theme={null} interface Product { id: string; name: string; price: number; } const products: Product[] = [ { id: 'p1', name: 'Widget', price: 19.99 }, { id: 'p2', name: 'Gadget', price: 29.99 }, { id: 'p3', name: 'Doohickey', price: 39.99 } ]; // Create a key-value object from products array const productData = products.reduce((acc, product) => { acc[`product:${product.id}`] = product; return acc; }, {} as Record); // Set all products at once await redis.mset(productData); // Retrieve a product const widget = await redis.get('product:p1'); console.log(widget); // { id: "p1", name: "Widget", price: 19.99 } ``` ### Configuration Initialization ```typescript theme={null} // Initialize application configuration await redis.mset({ 'config:api_url': 'https://api.example.com', 'config:api_key': 'secret-key-123', 'config:timeout': '5000', 'config:max_retries': '3', 'config:debug_mode': 'false' }); // Later, fetch all config const config = await redis.mget( 'config:api_url', 'config:api_key', 'config:timeout', 'config:max_retries', 'config:debug_mode' ); ``` ### Updating Multiple Counters ```typescript theme={null} // Initialize multiple counters await redis.mset({ 'stats:page_views': '0', 'stats:unique_visitors': '0', 'stats:api_calls': '0', 'stats:errors': '0' }); // Later increment them individually await redis.incr('stats:page_views'); await redis.incr('stats:unique_visitors'); ``` ### Session Data ```typescript theme={null} interface SessionData { userId: string; loginTime: string; lastActivity: string; ipAddress: string; } const sessionId = 'sess_abc123'; const sessionData: SessionData = { userId: 'user_456', loginTime: new Date().toISOString(), lastActivity: new Date().toISOString(), ipAddress: '192.168.1.1' }; // Store session fields await redis.mset({ [`session:${sessionId}:userId`]: sessionData.userId, [`session:${sessionId}:loginTime`]: sessionData.loginTime, [`session:${sessionId}:lastActivity`]: sessionData.lastActivity, [`session:${sessionId}:ipAddress`]: sessionData.ipAddress }); ``` ### Batch Cache Population ```typescript theme={null} // Populate cache with multiple entries async function populateCache(items: Map) { const cacheData: Record = {}; items.forEach((value, key) => { cacheData[`cache:${key}`] = value; }); await redis.mset(cacheData); console.log(`Cached ${items.size} items`); } const dataToCache = new Map([ ['user:1', { name: 'Alice', age: 30 }], ['user:2', { name: 'Bob', age: 25 }], ['user:3', { name: 'Charlie', age: 35 }] ]); await populateCache(dataToCache); ``` ### Feature Flags ```typescript theme={null} // Set multiple feature flags atomically await redis.mset({ 'feature:new_ui': 'true', 'feature:beta_features': 'false', 'feature:dark_mode': 'true', 'feature:analytics': 'true' }); // Check feature flags const [newUI, betaFeatures, darkMode] = await redis.mget( 'feature:new_ui', 'feature:beta_features', 'feature:dark_mode' ); const features = { newUI: newUI === 'true', betaFeatures: betaFeatures === 'true', darkMode: darkMode === 'true' }; ``` ## Performance Considerations * MSET is more efficient than multiple SET commands because it reduces network round trips * The operation is atomic - all keys are set simultaneously * For setting many keys, MSET significantly reduces latency compared to sequential SET calls * Time complexity is O(N) where N is the number of keys to set ## Notes * MSET is atomic - all keys are set at once * Existing values are overwritten * Always returns `"OK"` on success * More efficient than multiple SET operations due to reduced network overhead * All values in the object are serialized according to the SDK's serialization settings ## Related Commands * [set](/api/commands/set) - Set the value of a single key * [mget](/api/commands/mget) - Get the values of multiple keys ## Redis Documentation For more information, see the [Redis MSET documentation](https://redis.io/commands/mset). # persist Source: https://upstash-redis-js.mintlify.app/api/commands/persist Remove the expiration from a key ## Description Remove the existing timeout on a key, turning the key from volatile (a key with an expire set) to persistent (a key that will never expire as no timeout is associated). ## Method Signature ```typescript theme={null} persist(key: string): Promise<0 | 1> ``` ## Parameters The key to remove expiration from ## Return Value * `1` if the timeout was removed * `0` if key does not exist or does not have an associated timeout ## Examples ### Remove expiration from a key ```typescript theme={null} await redis.set("mykey", "value"); await redis.expire("mykey", 100); let ttl = await redis.ttl("mykey"); console.log(ttl); // ~100 const result = await redis.persist("mykey"); console.log(result); // 1 ttl = await redis.ttl("mykey"); console.log(ttl); // -1 (no expiration) ``` ### Key without expiration ```typescript theme={null} await redis.set("mykey", "value"); const result = await redis.persist("mykey"); console.log(result); // 0 (key has no timeout) ``` ### Non-existent key ```typescript theme={null} const result = await redis.persist("nonexistent"); console.log(result); // 0 ``` ### Make a temporary key permanent ```typescript theme={null} // Set a key with expiration await redis.set("session:user123", "data", { ex: 3600 }); // Later, decide to keep it permanently await redis.persist("session:user123"); // Key will now never expire const ttl = await redis.ttl("session:user123"); console.log(ttl); // -1 ``` ## See Also * [expire](/api/commands/expire) - Set a key's time to live * [ttl](/api/commands/ttl) - Get the time to live for a key # ping Source: https://upstash-redis-js.mintlify.app/api/commands/ping Test server connection ## Description Returns `PONG` if no argument is provided, otherwise returns a copy of the argument as a bulk string. This command is useful for testing if a connection is still alive, or measuring latency. ## Method Signature ```typescript theme={null} ping(message?: string): Promise ``` ## Parameters Optional message to echo back. If not provided, returns `"PONG"`. ## Return Value * `"PONG"` if no message is provided * The provided message if one was given ## Examples ### Basic ping ```typescript theme={null} const response = await redis.ping(); console.log(response); // "PONG" ``` ### Ping with message ```typescript theme={null} const response = await redis.ping("Hello Redis!"); console.log(response); // "Hello Redis!" ``` ### Test connection ```typescript theme={null} try { await redis.ping(); console.log("Connection is alive"); } catch (error) { console.error("Connection failed:", error); } ``` ### Measure latency ```typescript theme={null} const start = Date.now(); await redis.ping(); const latency = Date.now() - start; console.log(`Latency: ${latency}ms`); ``` ### Health check ```typescript theme={null} async function healthCheck() { try { const response = await redis.ping(); return response === "PONG"; } catch { return false; } } const isHealthy = await healthCheck(); console.log(`Server is ${isHealthy ? "healthy" : "down"}`); ``` ## See Also * [echo](/api/commands/echo) - Echo a message * [time](/api/commands/time) - Get server time # RPOP Source: https://upstash-redis-js.mintlify.app/api/commands/rpop Remove and return the last element(s) of a list ## Overview Removes and returns the last element of the list stored at `key`. When a `count` is provided, removes and returns up to `count` elements from the tail of the list. ## Method Signature ```typescript theme={null} rpop(key: string): Promise rpop(key: string, count: number): Promise ``` ## Parameters The key of the list. The number of elements to pop from the tail. When specified, returns an array of elements instead of a single element. ## Return Value When called without `count`: The last element of the list, or `null` if the list is empty or does not exist. When called with `count`: An array of popped elements (from tail to head), or `null` if the list is empty or does not exist. ## Examples ### Basic Usage ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, }); // Set up a list await redis.rpush('mylist', 'hello', 'world'); // List is: ['hello', 'world'] // Pop the last element const element = await redis.rpop('mylist'); console.log(element); // 'world' // Pop from empty list const empty = await redis.rpop('nonexistent'); console.log(empty); // null ``` ### Pop Multiple Elements ```typescript theme={null} // Set up a list await redis.rpush('stack', 'first', 'second', 'third', 'fourth'); // Pop multiple elements from the tail const items = await redis.rpop('stack', 2); console.log(items); // ['fourth', 'third'] ``` ### Working with Objects ```typescript theme={null} interface Event { type: string; timestamp: number; data: Record; } // Add events await redis.rpush('events', { type: 'user.login', timestamp: Date.now(), data: { userId: '123' }, }); // Pop the most recent event const event = await redis.rpop('events'); console.log(event?.type); // 'user.login' ``` ### Stack Implementation ```typescript theme={null} // RPUSH and RPOP implement a stack (LIFO) await redis.rpush('stack', 'A'); await redis.rpush('stack', 'B'); await redis.rpush('stack', 'C'); const third = await redis.rpop('stack'); console.log(third); // 'C' const second = await redis.rpop('stack'); console.log(second); // 'B' const first = await redis.rpop('stack'); console.log(first); // 'A' ``` ### Recent Items Processing ```typescript theme={null} // Process the most recent items const recentItems = await redis.rpop('recent:activity', 5); if (recentItems) { // Items are ordered from most recent to least recent for (const item of recentItems) { await processRecentActivity(item); } } ``` ## See Also * [LPOP](/api/commands/lpop) - Remove and return the first element of a list * [RPUSH](/api/commands/rpush) - Insert elements at the tail of a list * [LRANGE](/api/commands/lrange) - Get a range of elements from a list * [LLEN](/api/commands/llen) - Get the length of a list # RPUSH Source: https://upstash-redis-js.mintlify.app/api/commands/rpush Insert elements at the tail of a list ## Overview Inserts one or more values at the tail of the list stored at `key`. If `key` does not exist, it is created as an empty list before performing the push operations. ## Method Signature ```typescript theme={null} rpush(key: string, ...elements: TData[]): Promise ``` ## Parameters The key of the list. One or more elements to insert at the tail of the list. Elements are inserted one after the other, so the first element in the arguments will be inserted first. ## Return Value The length of the list after the push operations. ## Examples ### Basic Usage ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, }); // Push a single element const length1 = await redis.rpush('mylist', 'hello'); console.log(length1); // 1 // Push multiple elements const length2 = await redis.rpush('mylist', 'world', 'foo'); console.log(length2); // 3 // List is now: ['hello', 'world', 'foo'] ``` ### Working with Objects ```typescript theme={null} interface Message { id: string; content: string; timestamp: number; } const message: Message = { id: 'msg-1', content: 'Hello, world!', timestamp: Date.now(), }; const length = await redis.rpush('messages', message); console.log(length); // 1 ``` ### Building a Log Stream ```typescript theme={null} // Append log entries in chronological order await redis.rpush('logs:app', 'Server started'); await redis.rpush('logs:app', 'Database connected'); await redis.rpush('logs:app', 'Ready to accept connections'); // List is now: ['Server started', 'Database connected', 'Ready to accept connections'] ``` ### Batch Insert ```typescript theme={null} // Insert multiple items at once const items = ['item1', 'item2', 'item3', 'item4']; const length = await redis.rpush('items', ...items); console.log(length); // 4 ``` ## See Also * [LPUSH](/api/commands/lpush) - Insert elements at the head of a list * [RPOP](/api/commands/rpop) - Remove and return the last element of a list * [LRANGE](/api/commands/lrange) - Get a range of elements from a list * [LLEN](/api/commands/llen) - Get the length of a list # SADD Source: https://upstash-redis-js.mintlify.app/api/commands/sadd Add one or more members to a set Add one or more members to a set stored at key. Members that already exist in the set are ignored. If the key does not exist, a new set is created. ## Usage ```typescript theme={null} await redis.sadd(key, member, ...members); ``` ## Parameters The key of the set The first member to add to the set Additional members to add to the set ## Response The number of elements that were added to the set, not including elements already present ## Examples ### Add a single member ```typescript theme={null} const result = await redis.sadd("myset", "hello"); console.log(result); // 1 ``` ### Add multiple members ```typescript theme={null} const result = await redis.sadd("fruits", "apple", "banana", "orange"); console.log(result); // 3 ``` ### Add duplicate members ```typescript theme={null} await redis.sadd("colors", "red", "blue"); const result = await redis.sadd("colors", "red", "green"); console.log(result); // 1 (only "green" was added) ``` ### Using with custom types ```typescript theme={null} const result = await redis.sadd("numbers", 1, 2, 3, 4, 5); console.log(result); // 5 ``` # scan Source: https://upstash-redis-js.mintlify.app/api/commands/scan Incrementally iterate over keys ## Description Iterates the set of keys in the database using a cursor-based iterator. Unlike `KEYS`, `SCAN` doesn't block the server and is safe to use in production. ## Method Signatures ```typescript theme={null} // Basic scan scan(cursor: string | number): Promise<[string, string[]]> // Scan with options (standard) scan( cursor: string | number, opts: ScanCommandOptionsStandard ): Promise<[string, string[]]> // Scan with type information scan( cursor: string | number, opts: ScanCommandOptionsWithType ): Promise<[string, { key: string; type: string }[]]> ``` ## Parameters The cursor position. Use `"0"` or `0` to start a new iteration. Optional configuration object: Pattern to filter keys (glob-style pattern like `"user:*"`) Hint for number of keys to return per iteration (default: 10). Redis may return more or fewer keys. Filter by key type: `"string"`, `"list"`, `"set"`, `"zset"`, or `"hash"` When `true`, includes the type of each key in the result. Changes return type to include type information. ## Return Value A tuple containing: 1. **Next cursor** - Use this value in the next `scan` call. When `"0"`, iteration is complete. 2. **Keys** - Array of matching keys (or objects with key and type if `withType: true`) ## Examples ### Basic iteration ```typescript theme={null} let cursor = "0"; const allKeys: string[] = []; do { const [nextCursor, keys] = await redis.scan(cursor); allKeys.push(...keys); cursor = nextCursor; } while (cursor !== "0"); console.log(allKeys); ``` ### Scan with pattern matching ```typescript theme={null} let cursor = "0"; const userKeys: string[] = []; do { const [nextCursor, keys] = await redis.scan(cursor, { match: "user:*" }); userKeys.push(...keys); cursor = nextCursor; } while (cursor !== "0"); console.log(userKeys); ``` ### Scan with count hint ```typescript theme={null} const [cursor, keys] = await redis.scan("0", { count: 100 // Request approximately 100 keys per iteration }); console.log(`Found ${keys.length} keys, next cursor: ${cursor}`); ``` ### Filter by key type ```typescript theme={null} let cursor = "0"; const stringKeys: string[] = []; do { const [nextCursor, keys] = await redis.scan(cursor, { type: "string" }); stringKeys.push(...keys); cursor = nextCursor; } while (cursor !== "0"); console.log(stringKeys); ``` ### Scan with type information ```typescript theme={null} let cursor = "0"; do { const [nextCursor, items] = await redis.scan(cursor, { withType: true }); items.forEach(({ key, type }) => { console.log(`${key} is a ${type}`); }); cursor = nextCursor; } while (cursor !== "0"); ``` ### Combine options ```typescript theme={null} const [cursor, items] = await redis.scan("0", { match: "user:*", count: 50, withType: true }); items.forEach(({ key, type }) => { console.log(`${key} (${type})`); }); ``` ### Single-pass scan ```typescript theme={null} // Get first batch of keys const [cursor, keys] = await redis.scan("0"); console.log(keys); // If cursor is "0", we've seen all keys // Otherwise, there are more keys to fetch if (cursor !== "0") { console.log("More keys available"); } ``` ## Cursor-Based Iteration The `SCAN` command uses cursor-based iteration: 1. Start with cursor `"0"` 2. Call `scan(cursor)` and receive `[nextCursor, keys]` 3. Process the keys 4. If `nextCursor` is `"0"`, iteration is complete 5. Otherwise, call `scan(nextCursor)` and repeat The cursor is opaque - don't parse or manipulate it. Always use the cursor returned from the previous call. ## Guarantees and Characteristics * **Full iteration guarantee**: A key that exists from start to finish will be returned * **No duplicates guarantee**: A key that is never modified will be returned only once * **Safe for production**: Non-blocking, suitable for large databases * **Variable results**: The number of keys returned per call may vary * **Pattern matching**: Filtering happens after fetching, so you may get empty results ## See Also * [keys](/api/commands/keys) - Get all keys matching a pattern (blocking) * [type](/api/commands/type) - Get the type of a key # SCARD Source: https://upstash-redis-js.mintlify.app/api/commands/scard Get the number of members in a set Returns the cardinality (number of elements) of the set stored at key. ## Usage ```typescript theme={null} await redis.scard(key); ``` ## Parameters The key of the set ## Response The cardinality (number of members) of the set, or `0` if the key does not exist ## Examples ### Get set size ```typescript theme={null} await redis.sadd("myset", "hello", "world", "foo"); const size = await redis.scard("myset"); console.log(size); // 3 ``` ### Get size of empty set ```typescript theme={null} const size = await redis.scard("nonexistent"); console.log(size); // 0 ``` ### Track active sessions ```typescript theme={null} await redis.sadd("sessions:active", "session1", "session2", "session3"); const activeCount = await redis.scard("sessions:active"); console.log(`Active sessions: ${activeCount}`); // Active sessions: 3 ``` ### Monitor set size changes ```typescript theme={null} await redis.sadd("tags", "javascript", "typescript"); console.log(await redis.scard("tags")); // 2 await redis.sadd("tags", "python", "go"); console.log(await redis.scard("tags")); // 4 await redis.srem("tags", "javascript"); console.log(await redis.scard("tags")); // 3 ``` # SCRIPT LOAD Source: https://upstash-redis-js.mintlify.app/api/commands/script-load Load a Lua script into Redis without executing it ## Usage ```typescript theme={null} await redis.scriptLoad(script); ``` Loads a Lua script into the Redis server's script cache without executing it. Returns the SHA-1 hash of the script, which can be used with [EVALSHA](/api/commands/evalsha) to execute the script later. ## Parameters The Lua script to load into the server cache. ## Response The SHA-1 hash of the loaded script. This hash can be used with EVALSHA to execute the script. ## Examples ### Load a Simple Script ```typescript theme={null} const script = "return ARGV[1]"; const sha1 = await redis.scriptLoad(script); console.log(sha1); // e.g., "4e6d8fc8bb01276962cce5371fa795a7763657ae" // Now execute it using EVALSHA const result = await redis.evalsha(sha1, [], ["Hello World"]); console.log(result); // "Hello World" ``` ### Load a Counter Script ```typescript theme={null} const counterScript = ` local current = redis.call('GET', KEYS[1]) or 0 local next = current + tonumber(ARGV[1]) redis.call('SET', KEYS[1], next) return next `; const sha1 = await redis.scriptLoad(counterScript); // Execute the cached script multiple times const count1 = await redis.evalsha(sha1, ["counter"], ["1"]); const count2 = await redis.evalsha(sha1, ["counter"], ["5"]); const count3 = await redis.evalsha(sha1, ["counter"], ["10"]); console.log(count3); // 16 (1 + 5 + 10) ``` ### Load Multiple Scripts ```typescript theme={null} const scripts = { increment: ` local val = redis.call('INCR', KEYS[1]) return val `, decrement: ` local val = redis.call('DECR', KEYS[1]) return val `, reset: ` redis.call('SET', KEYS[1], 0) return 0 ` }; // Load all scripts and store their hashes const hashes = { increment: await redis.scriptLoad(scripts.increment), decrement: await redis.scriptLoad(scripts.decrement), reset: await redis.scriptLoad(scripts.reset) }; // Use the scripts await redis.evalsha(hashes.reset, ["counter"], []); const val1 = await redis.evalsha(hashes.increment, ["counter"], []); const val2 = await redis.evalsha(hashes.increment, ["counter"], []); const val3 = await redis.evalsha(hashes.decrement, ["counter"], []); console.log(val3); // 1 ``` ### Atomic Multi-Key Operations ```typescript theme={null} const transferScript = ` local from_balance = tonumber(redis.call('GET', KEYS[1]) or 0) local amount = tonumber(ARGV[1]) if from_balance >= amount then redis.call('DECRBY', KEYS[1], amount) redis.call('INCRBY', KEYS[2], amount) return 1 else return 0 end `; const sha1 = await redis.scriptLoad(transferScript); // Initialize balances await redis.set("account:alice", "100"); await redis.set("account:bob", "50"); // Transfer 30 from Alice to Bob const success = await redis.evalsha( sha1, ["account:alice", "account:bob"], ["30"] ); if (success === 1) { console.log("Transfer successful"); const aliceBalance = await redis.get("account:alice"); // "70" const bobBalance = await redis.get("account:bob"); // "80" } ``` ### Complex Data Processing ```typescript theme={null} const batchUpdateScript = ` local key_prefix = ARGV[1] local count = tonumber(ARGV[2]) local value = ARGV[3] for i = 1, count do local key = key_prefix .. ':' .. i redis.call('SET', key, value) end return count `; const sha1 = await redis.scriptLoad(batchUpdateScript); // Set user:1 through user:100 to "active" const updated = await redis.evalsha( sha1, [], ["user", "100", "active"] ); console.log(`Updated ${updated} keys`); ``` ## When to Use SCRIPT LOAD ### Good Use Cases 1. **Frequently executed scripts**: Load once, execute many times 2. **Application startup**: Pre-load all scripts your application needs 3. **Performance optimization**: Reduce bandwidth and parsing overhead 4. **Complex atomic operations**: Ensure scripts are available before use ### Example: Application Initialization ```typescript theme={null} class RedisScripts { private hashes: Record = {}; async initialize(redis: Redis) { const scripts = { incrementCounter: ` local val = redis.call('INCR', KEYS[1]) return val `, checkAndSet: ` local current = redis.call('GET', KEYS[1]) if current == ARGV[1] then redis.call('SET', KEYS[1], ARGV[2]) return 1 end return 0 `, getMultiple: ` local results = {} for i, key in ipairs(KEYS) do results[i] = redis.call('GET', key) end return results ` }; // Load all scripts at startup for (const [name, script] of Object.entries(scripts)) { this.hashes[name] = await redis.scriptLoad(script); } } async incrementCounter(redis: Redis, key: string) { return redis.evalsha(this.hashes.incrementCounter, [key], []); } async checkAndSet( redis: Redis, key: string, expected: string, newValue: string ) { return redis.evalsha( this.hashes.checkAndSet, [key], [expected, newValue] ); } async getMultiple(redis: Redis, keys: string[]) { return redis.evalsha(this.hashes.getMultiple, keys, []); } } // Usage const scripts = new RedisScripts(); await scripts.initialize(redis); const count = await scripts.incrementCounter(redis, "visitors"); ``` ## Script Caching Behavior * Scripts are cached on the Redis server until: * The server restarts * The script cache is explicitly flushed * Memory pressure causes eviction (rare) * Scripts are **not** automatically replicated to read replicas. If using read replicas, you must load scripts on each instance. ## Using the Script Class Instead of manually managing SHA-1 hashes, use the `Script` class: ```typescript theme={null} const script = redis.createScript(` local current = redis.call('GET', KEYS[1]) or 0 local next = current + tonumber(ARGV[1]) redis.call('SET', KEYS[1], next) return next `); // The Script class automatically computes and caches the SHA-1 hash const result = await script.exec(["counter"], ["1"]); ``` The `Script` class: * Automatically computes the SHA-1 hash * Optimistically tries EVALSHA first * Falls back to EVAL if the script isn't loaded * Caches the script on the server after first use ## See Also * [EVAL](/api/commands/eval) - Execute a Lua script * [EVALSHA](/api/commands/evalsha) - Execute a cached script by SHA-1 hash # SDIFF Source: https://upstash-redis-js.mintlify.app/api/commands/sdiff Get the difference between sets Returns the members of the set resulting from the difference between the first set and all the successive sets. Keys that do not exist are considered to be empty sets. ## Usage ```typescript theme={null} await redis.sdiff(key, ...keys); ``` ## Parameters The first set key (the set to subtract from) Additional set keys to subtract from the first set ## Response An array containing the members of the resulting set (difference of the sets) ## Examples ### Difference between two sets ```typescript theme={null} await redis.sadd("set1", "a", "b", "c", "d"); await redis.sadd("set2", "c", "d", "e", "f"); const result = await redis.sdiff("set1", "set2"); console.log(result); // ["a", "b"] ``` ### Difference with multiple sets ```typescript theme={null} await redis.sadd("all:items", "item1", "item2", "item3", "item4", "item5"); await redis.sadd("sold:items", "item2", "item4"); await redis.sadd("reserved:items", "item5"); const available = await redis.sdiff( "all:items", "sold:items", "reserved:items" ); console.log(available); // ["item1", "item3"] ``` ### Find unique skills ```typescript theme={null} await redis.sadd("team:skills", "javascript", "python", "go", "rust"); await redis.sadd("required:skills", "javascript", "python"); const bonusSkills = await redis.sdiff("team:skills", "required:skills"); console.log(bonusSkills); // ["go", "rust"] ``` ### Difference with non-existing set ```typescript theme={null} await redis.sadd("set1", "a", "b", "c"); const result = await redis.sdiff("set1", "nonexistent"); console.log(result); // ["a", "b", "c"] ``` ### Order matters ```typescript theme={null} await redis.sadd("set1", "a", "b", "c"); await redis.sadd("set2", "b", "c", "d"); console.log(await redis.sdiff("set1", "set2")); // ["a"] console.log(await redis.sdiff("set2", "set1")); // ["d"] ``` # set Source: https://upstash-redis-js.mintlify.app/api/commands/set Set the string value of a key ## Description Set key to hold the string value. If key already holds a value, it is overwritten, regardless of its type. Any previous time to live associated with the key is discarded on successful SET operation. ## Syntax ```typescript theme={null} redis.set( key: string, value: TData, opts?: SetCommandOptions ): Promise ``` ## Parameters The key to set The value to store. Can be a string, number, object, or any serializable data Optional configuration for the SET command Set the specified expire time, in seconds (a positive integer) Set the specified expire time, in milliseconds (a positive integer) Set the specified Unix time at which the key will expire, in seconds (a positive integer) Set the specified Unix time at which the key will expire, in milliseconds (a positive integer) Only set the key if it does not already exist Only set the key if it already exists Retain the time to live associated with the key Return the old string stored at key, or `null` if key did not exist ## Type Parameters The type of the value being stored ## Returns * Returns `"OK"` if SET was executed correctly * Returns `null` if the SET operation was not performed (due to `nx` or `xx` conditions) * Returns the old value if the `get` option was specified ## Examples ### Basic Usage ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: 'https://your-redis-url.upstash.io', token: 'your-token' }); // Simple string set await redis.set('greeting', 'Hello, World!'); // Returns: "OK" // Set a number await redis.set('counter', 42); // Returns: "OK" ``` ### Set with Expiration ```typescript theme={null} // Expire in 60 seconds await redis.set('session:abc', 'user-data', { ex: 60 }); // Expire in 5000 milliseconds await redis.set('temp:key', 'value', { px: 5000 }); // Expire at specific Unix timestamp (seconds) const expirationTime = Math.floor(Date.now() / 1000) + 3600; // 1 hour from now await redis.set('token', 'secret-token', { exat: expirationTime }); // Expire at specific Unix timestamp (milliseconds) const expirationTimeMs = Date.now() + 3600000; // 1 hour from now await redis.set('token', 'secret-token', { pxat: expirationTimeMs }); ``` ### Conditional Set ```typescript theme={null} // Only set if key doesn't exist (SET NX) const result1 = await redis.set('key', 'value', { nx: true }); console.log(result1); // "OK" if key didn't exist, null otherwise // Only set if key exists (SET XX) const result2 = await redis.set('key', 'new-value', { xx: true }); console.log(result2); // "OK" if key existed, null otherwise ``` ### Set and Get Old Value ```typescript theme={null} // Set initial value await redis.set('score', 100); // Set new value and get the old one const oldValue = await redis.set('score', 200, { get: true }); console.log(oldValue); // 100 ``` ### Working with Objects ```typescript theme={null} interface User { id: number; name: string; email: string; } const user: User = { id: 1, name: 'Alice', email: 'alice@example.com' }; // Store object with 1 hour expiration await redis.set('user:1', user, { ex: 3600 }); ``` ### Keep TTL ```typescript theme={null} // Set a key with expiration await redis.set('key', 'value1', { ex: 300 }); // 5 minutes // Update the value but keep the remaining TTL await redis.set('key', 'value2', { keepTtl: true }); // The key still has the same remaining TTL from the first SET ``` ## Notes * The `ex`, `px`, `exat`, `pxat`, and `keepTtl` options are mutually exclusive * The `nx` and `xx` options are mutually exclusive * When using the `get` option, the command returns the old value instead of `"OK"` ## Related Commands * [get](/api/commands/get) - Get the value of a key * [mset](/api/commands/mset) - Set multiple keys to multiple values ## Redis Documentation For more information, see the [Redis SET documentation](https://redis.io/commands/set). # setrange Source: https://upstash-redis-js.mintlify.app/api/commands/setrange Overwrite part of a string at a specific offset ## Description Overwrites part of the string stored at key, starting at the specified offset, for the entire length of value. If the offset is larger than the current length of the string at key, the string is padded with zero-bytes to make offset fit. Non-existing keys are considered as empty strings. ## Syntax ```typescript theme={null} redis.setrange(key: string, offset: number, value: string): Promise ``` ## Parameters The key containing the string to modify The position at which to start overwriting (0-based) The string to write at the specified offset ## Returns The length of the string after it was modified by the command ## Examples ### Basic Usage ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: 'https://your-redis-url.upstash.io', token: 'your-token' }); // Set an initial string await redis.set('message', 'Hello World'); // Overwrite starting at offset 6 const length = await redis.setrange('message', 6, 'Redis'); console.log(length); // 11 const result = await redis.get('message'); console.log(result); // "Hello Redis" ``` ### Overwrite in the Middle ```typescript theme={null} await redis.set('text', 'Hello, World!'); // Replace "World" with "there" await redis.setrange('text', 7, 'there'); const result = await redis.get('text'); console.log(result); // "Hello, there!" ``` ### Padding with Zero-Bytes ```typescript theme={null} // Start with a short string await redis.set('key', 'abc'); // Set range beyond current length const length = await redis.setrange('key', 10, 'xyz'); console.log(length); // 13 const result = await redis.get('key'); console.log(result); // "abc\x00\x00\x00\x00\x00\x00\x00xyz" // The string is padded with zero-bytes (\x00) from position 3 to 9 ``` ### Setting on Non-Existent Key ```typescript theme={null} // SETRANGE on a non-existent key creates it const length = await redis.setrange('newkey', 0, 'Hello'); console.log(length); // 5 const result = await redis.get('newkey'); console.log(result); // "Hello" // SETRANGE with offset on non-existent key const length2 = await redis.setrange('another', 5, 'Redis'); console.log(length2); // 10 const result2 = await redis.get('another'); console.log(result2); // "\x00\x00\x00\x00\x00Redis" ``` ### Updating Specific Fields ```typescript theme={null} // Store a fixed-format string (e.g., status flags) await redis.set('flags', '0000000000'); // Update specific positions await redis.setrange('flags', 0, '1'); // Set first flag await redis.setrange('flags', 5, '1'); // Set sixth flag const flags = await redis.get('flags'); console.log(flags); // "1000010000" ``` ### Modifying Structured Data ```typescript theme={null} // Store a date in format YYYY-MM-DD await redis.set('date', '2024-01-15'); // Update just the day await redis.setrange('date', 8, '20'); const updated = await redis.get('date'); console.log(updated); // "2024-01-20" ``` ## Notes * The offset is zero-based * If the key does not exist, it is created as an empty string before performing the operation * If the offset is beyond the current string length, the string is padded with null bytes (`\x00`) * Time complexity is O(1), not counting the time taken to copy the new string in place * Returns the length of the string after modification * Be careful when using large offsets on non-existent keys, as this will create a large string filled with zero-bytes ## Related Commands * [getrange](/api/commands/getrange) - Get a substring of the string stored at a key * [append](/api/commands/append) - Append a value to a key * [set](/api/commands/set) - Set the string value of a key ## Redis Documentation For more information, see the [Redis SETRANGE documentation](https://redis.io/commands/setrange). # SINTER Source: https://upstash-redis-js.mintlify.app/api/commands/sinter Get the intersection of multiple sets Returns the members of the set resulting from the intersection of all the given sets. Keys that do not exist are considered to be empty sets. ## Usage ```typescript theme={null} await redis.sinter(key, ...keys); ``` ## Parameters The first set key Additional set keys to intersect with ## Response An array containing the members of the resulting set (intersection of all sets) ## Examples ### Intersect two sets ```typescript theme={null} await redis.sadd("set1", "a", "b", "c", "d"); await redis.sadd("set2", "c", "d", "e", "f"); const result = await redis.sinter("set1", "set2"); console.log(result); // ["c", "d"] ``` ### Intersect multiple sets ```typescript theme={null} await redis.sadd("tags:post1", "javascript", "typescript", "react"); await redis.sadd("tags:post2", "typescript", "react", "node"); await redis.sadd("tags:post3", "react", "vue", "typescript"); const commonTags = await redis.sinter("tags:post1", "tags:post2", "tags:post3"); console.log(commonTags); // ["typescript", "react"] ``` ### Find common friends ```typescript theme={null} await redis.sadd("friends:alice", "bob", "charlie", "david"); await redis.sadd("friends:bob", "alice", "charlie", "eve"); const mutualFriends = await redis.sinter("friends:alice", "friends:bob"); console.log(mutualFriends); // ["charlie"] ``` ### Empty intersection ```typescript theme={null} await redis.sadd("set1", "a", "b", "c"); await redis.sadd("set2", "d", "e", "f"); const result = await redis.sinter("set1", "set2"); console.log(result); // [] ``` # SISMEMBER Source: https://upstash-redis-js.mintlify.app/api/commands/sismember Check if a member exists in a set Returns if member is a member of the set stored at key. ## Usage ```typescript theme={null} await redis.sismember(key, member); ``` ## Parameters The key of the set The member to check for existence in the set ## Response Returns `1` if the member exists in the set, `0` otherwise ## Examples ### Check if member exists ```typescript theme={null} await redis.sadd("myset", "hello", "world"); const exists = await redis.sismember("myset", "hello"); console.log(exists); // 1 ``` ### Check for non-existing member ```typescript theme={null} await redis.sadd("myset", "hello", "world"); const exists = await redis.sismember("myset", "foo"); console.log(exists); // 0 ``` ### Using in conditionals ```typescript theme={null} const isMember = await redis.sismember("users:premium", "user:123"); if (isMember === 1) { console.log("User has premium access"); } else { console.log("User does not have premium access"); } ``` ### Check membership with custom types ```typescript theme={null} await redis.sadd("numbers", 1, 2, 3, 4, 5); const exists = await redis.sismember("numbers", 3); console.log(exists); // 1 ``` # SMEMBERS Source: https://upstash-redis-js.mintlify.app/api/commands/smembers Get all members of a set Returns all members of the set stored at key. If the key does not exist, an empty array is returned. ## Usage ```typescript theme={null} await redis.smembers(key); ``` ## Parameters The key of the set ## Response An array containing all members of the set ## Examples ### Get all members ```typescript theme={null} await redis.sadd("myset", "hello", "world"); const members = await redis.smembers("myset"); console.log(members); // ["hello", "world"] ``` ### Get members from empty set ```typescript theme={null} const members = await redis.smembers("nonexistent"); console.log(members); // [] ``` ### Using with custom types ```typescript theme={null} await redis.sadd("numbers", 1, 2, 3, 4, 5); const members = await redis.smembers("numbers"); console.log(members); // [1, 2, 3, 4, 5] ``` ### Working with user data ```typescript theme={null} await redis.sadd("users:online", "user:1", "user:2", "user:3"); const onlineUsers = await redis.smembers("users:online"); console.log(onlineUsers); // ["user:1", "user:2", "user:3"] ``` # SPOP Source: https://upstash-redis-js.mintlify.app/api/commands/spop Remove and return one or more random members from a set Removes and returns one or more random members from the set stored at key. ## Usage ```typescript theme={null} await redis.spop(key); await redis.spop(key, count); ``` ## Parameters The key of the set The number of members to pop. If not specified, one member is popped ## Response * When `count` is not specified: Returns the removed member, or `null` if the set is empty * When `count` is specified: Returns an array of removed members (may be empty if the set is empty) ## Examples ### Pop a single member ```typescript theme={null} await redis.sadd("myset", "one", "two", "three"); const member = await redis.spop("myset"); console.log(member); // "one" (or "two" or "three") ``` ### Pop multiple members ```typescript theme={null} await redis.sadd("numbers", "1", "2", "3", "4", "5"); const members = await redis.spop("numbers", 3); console.log(members); // ["1", "3", "5"] (random selection) ``` ### Pop from empty set ```typescript theme={null} const member = await redis.spop("nonexistent"); console.log(member); // null ``` ### Random prize selection ```typescript theme={null} await redis.sadd("prizes", "car", "bike", "laptop", "phone"); const winner = await redis.spop("prizes"); console.log(`Winner gets: ${winner}`); ``` ### Pop more members than exist ```typescript theme={null} await redis.sadd("small", "a", "b"); const members = await redis.spop("small", 10); console.log(members); // ["a", "b"] (returns all available members) ``` # SREM Source: https://upstash-redis-js.mintlify.app/api/commands/srem Remove one or more members from a set Remove one or more members from a set stored at key. Members that do not exist in the set are ignored. If the key does not exist, it is treated as an empty set and the command returns 0. ## Usage ```typescript theme={null} await redis.srem(key, ...members); ``` ## Parameters The key of the set One or more members to remove from the set ## Response The number of members that were removed from the set, not including non-existing members ## Examples ### Remove a single member ```typescript theme={null} await redis.sadd("myset", "hello", "world"); const result = await redis.srem("myset", "hello"); console.log(result); // 1 ``` ### Remove multiple members ```typescript theme={null} await redis.sadd("fruits", "apple", "banana", "orange", "grape"); const result = await redis.srem("fruits", "apple", "orange"); console.log(result); // 2 ``` ### Remove non-existing members ```typescript theme={null} await redis.sadd("colors", "red", "blue"); const result = await redis.srem("colors", "green", "yellow"); console.log(result); // 0 ``` ### Remove from non-existing set ```typescript theme={null} const result = await redis.srem("nonexistent", "member"); console.log(result); // 0 ``` # SUNION Source: https://upstash-redis-js.mintlify.app/api/commands/sunion Get the union of multiple sets Returns the members of the set resulting from the union of all the given sets. Keys that do not exist are considered to be empty sets. ## Usage ```typescript theme={null} await redis.sunion(key, ...keys); ``` ## Parameters The first set key Additional set keys to union with ## Response An array containing the members of the resulting set (union of all sets) ## Examples ### Union of two sets ```typescript theme={null} await redis.sadd("set1", "a", "b", "c"); await redis.sadd("set2", "c", "d", "e"); const result = await redis.sunion("set1", "set2"); console.log(result); // ["a", "b", "c", "d", "e"] ``` ### Union of multiple sets ```typescript theme={null} await redis.sadd("skills:alice", "javascript", "python"); await redis.sadd("skills:bob", "python", "go"); await redis.sadd("skills:charlie", "java", "javascript"); const allSkills = await redis.sunion( "skills:alice", "skills:bob", "skills:charlie" ); console.log(allSkills); // ["javascript", "python", "go", "java"] ``` ### Combine tag sets ```typescript theme={null} await redis.sadd("tags:frontend", "react", "vue", "angular"); await redis.sadd("tags:backend", "node", "django", "rails"); const allTags = await redis.sunion("tags:frontend", "tags:backend"); console.log(allTags); // ["react", "vue", "angular", "node", "django", "rails"] ``` ### Union with non-existing set ```typescript theme={null} await redis.sadd("set1", "a", "b", "c"); const result = await redis.sunion("set1", "nonexistent"); console.log(result); // ["a", "b", "c"] ``` # time Source: https://upstash-redis-js.mintlify.app/api/commands/time Get the server time ## Description Returns the current server time as a tuple of Unix timestamp and microseconds. ## Method Signature ```typescript theme={null} time(): Promise<[number, number]> ``` ## Parameters This command takes no parameters. ## Return Value A tuple containing: 1. **Unix timestamp** - Current server time in seconds since Unix epoch 2. **Microseconds** - Microseconds component of the current time ## Examples ### Get server time ```typescript theme={null} const [seconds, microseconds] = await redis.time(); console.log(`Seconds: ${seconds}`); console.log(`Microseconds: ${microseconds}`); ``` ### Convert to JavaScript Date ```typescript theme={null} const [seconds, microseconds] = await redis.time(); const milliseconds = seconds * 1000 + Math.floor(microseconds / 1000); const date = new Date(milliseconds); console.log(date.toISOString()); ``` ### Calculate timestamp with microsecond precision ```typescript theme={null} const [seconds, microseconds] = await redis.time(); const timestamp = seconds + microseconds / 1_000_000; console.log(`Timestamp: ${timestamp}`); ``` ### Measure server time difference ```typescript theme={null} const [sec1, micro1] = await redis.time(); // Do some operations... await redis.set("key", "value"); await redis.get("key"); const [sec2, micro2] = await redis.time(); const elapsed = (sec2 - sec1) * 1_000_000 + (micro2 - micro1); console.log(`Elapsed: ${elapsed} microseconds`); console.log(`Elapsed: ${elapsed / 1000} milliseconds`); ``` ### Compare server time with client time ```typescript theme={null} const clientTime = Date.now(); const [seconds, microseconds] = await redis.time(); const serverTime = seconds * 1000 + Math.floor(microseconds / 1000); const drift = Math.abs(serverTime - clientTime); console.log(`Time drift: ${drift}ms`); if (drift > 1000) { console.warn("Server and client clocks differ by more than 1 second"); } ``` ### Use for unique IDs ```typescript theme={null} const [seconds, microseconds] = await redis.time(); const uniqueId = `${seconds}${microseconds.toString().padStart(6, '0')}`; console.log(uniqueId); // e.g., "1709472123456789" ``` ## Use Cases * **Clock synchronization** - Compare server and client times * **Distributed timestamps** - Get consistent time across multiple clients * **Benchmarking** - Measure operation duration with microsecond precision * **Unique ID generation** - Create time-based unique identifiers ## See Also * [ping](/api/commands/ping) - Test server connection # ttl Source: https://upstash-redis-js.mintlify.app/api/commands/ttl Get the time to live for a key in seconds ## Description Returns the remaining time to live of a key that has a timeout. This introspection capability allows a Redis client to check how many seconds a given key will continue to be part of the dataset. ## Method Signature ```typescript theme={null} ttl(key: string): Promise ``` ## Parameters The key to get the time to live for ## Return Value The time to live in seconds, or: * `-2` if the key does not exist * `-1` if the key exists but has no associated expire ## Examples ### Get TTL for a key with expiration ```typescript theme={null} await redis.set("mykey", "value"); await redis.expire("mykey", 100); const ttl = await redis.ttl("mykey"); console.log(ttl); // ~100 (depends on timing) ``` ### Key with no expiration ```typescript theme={null} await redis.set("mykey", "value"); const ttl = await redis.ttl("mykey"); console.log(ttl); // -1 ``` ### Non-existent key ```typescript theme={null} const ttl = await redis.ttl("nonexistent"); console.log(ttl); // -2 ``` ### Monitor TTL over time ```typescript theme={null} await redis.set("mykey", "value"); await redis.expire("mykey", 60); const ttl1 = await redis.ttl("mykey"); console.log(ttl1); // ~60 // Wait some time... setTimeout(async () => { const ttl2 = await redis.ttl("mykey"); console.log(ttl2); // Less than ttl1 }, 5000); ``` ## See Also * [expire](/api/commands/expire) - Set a key's time to live * [persist](/api/commands/persist) - Remove the expiration from a key # type Source: https://upstash-redis-js.mintlify.app/api/commands/type Determine the type of a key ## Description Returns the string representation of the type of the value stored at key. The different types that can be returned are: `string`, `list`, `set`, `zset`, `hash`, and `none`. ## Method Signature ```typescript theme={null} type(key: string): Promise ``` ## Parameters The key to get the type of ## Return Value The type of the key, one of: * `"string"` - String value * `"list"` - List * `"set"` - Set * `"zset"` - Sorted set * `"hash"` - Hash * `"none"` - Key does not exist ## Examples ### String type ```typescript theme={null} await redis.set("mykey", "value"); const type = await redis.type("mykey"); console.log(type); // "string" ``` ### List type ```typescript theme={null} await redis.lpush("mylist", "item"); const type = await redis.type("mylist"); console.log(type); // "list" ``` ### Hash type ```typescript theme={null} await redis.hset("myhash", { field: "value" }); const type = await redis.type("myhash"); console.log(type); // "hash" ``` ### Set type ```typescript theme={null} await redis.sadd("myset", "member"); const type = await redis.type("myset"); console.log(type); // "set" ``` ### Sorted set type ```typescript theme={null} await redis.zadd("myzset", { score: 1, member: "member" }); const type = await redis.type("myzset"); console.log(type); // "zset" ``` ### Non-existent key ```typescript theme={null} const type = await redis.type("nonexistent"); console.log(type); // "none" ``` ### Type-specific operations ```typescript theme={null} const type = await redis.type("mykey"); switch (type) { case "string": const value = await redis.get("mykey"); console.log(value); break; case "list": const items = await redis.lrange("mykey", 0, -1); console.log(items); break; case "hash": const fields = await redis.hgetall("mykey"); console.log(fields); break; case "none": console.log("Key does not exist"); break; default: console.log(`Key is of type: ${type}`); } ``` ## See Also * [exists](/api/commands/exists) - Check if a key exists * [scan](/api/commands/scan) - Scan with type filtering # xadd Source: https://upstash-redis-js.mintlify.app/api/commands/xadd Add a new entry to a stream ## Usage ```typescript theme={null} import { Redis } from "@upstash/redis"; const redis = new Redis({ url: "", token: "", }); const id = await redis.xadd("events", "*", { user: "alice", action: "login", timestamp: Date.now() }); ``` ## Parameters The key of the stream. The entry ID. Can be: * `"*"` - Fully automatic ID generation (recommended) * `"-"` - Explicit ID (e.g., `"1526919030474-55"`) * `"-*"` - Auto-generate sequence number for the given millisecond timestamp (Redis 8+) IDs must be greater than previous entries in the stream. The field-value pairs to store in the stream entry. Values are automatically serialized to strings. Optional configuration: Don't create the stream if it doesn't exist. Returns `null` if the stream doesn't exist. Automatically trim the stream to limit its size: ```typescript theme={null} { type: "MAXLEN" | "maxlen", threshold: number, comparison: "=" | "~", limit?: number // Only with "~" } ``` Trim to keep at most `threshold` entries. ```typescript theme={null} { type: "MINID" | "minid", threshold: string, comparison: "=" | "~", limit?: number // Only with "~" } ``` Trim to remove entries older than `threshold` ID. * `comparison: "="` - Exact trimming (slower) * `comparison: "~"` - Approximate trimming (faster, recommended) * `limit` - Maximum number of entries to trim (only with `"~"`) ## Response The ID of the added entry (e.g., `"1526919030474-0"`). Returns `null` if `nomkStream: true` and the stream doesn't exist. ## Examples ### Add entry with automatic ID ```typescript theme={null} const id = await redis.xadd("user-events", "*", { userId: "123", action: "page_view", page: "/dashboard" }); console.log(id); // Returns: "1678901234567-0" ``` ### Add multiple entries ```typescript theme={null} const ids = await Promise.all([ redis.xadd("logs", "*", { level: "info", message: "Server started" }), redis.xadd("logs", "*", { level: "warn", message: "High memory usage" }), redis.xadd("logs", "*", { level: "error", message: "Connection failed" }) ]); console.log(ids); // Returns: [ // "1678901234567-0", // "1678901234568-0", // "1678901234569-0" // ] ``` ### Store complex data ```typescript theme={null} const event = { userId: 42, action: "purchase", product: "premium-plan", amount: 99.99, metadata: JSON.stringify({ referral: "email", campaign: "spring-sale" }) }; const id = await redis.xadd("purchases", "*", event); ``` ### Use explicit ID ```typescript theme={null} // Use current timestamp with sequence 0 const timestamp = Date.now(); const id = await redis.xadd("events", `${timestamp}-0`, { type: "custom", data: "test" }); ``` ### Trim stream by maximum length ```typescript theme={null} // Keep only the most recent 1000 entries const id = await redis.xadd( "events", "*", { user: "alice", action: "click" }, { trim: { type: "MAXLEN", comparison: "~", // Approximate trimming (efficient) threshold: 1000 } } ); ``` ### Trim stream by minimum ID ```typescript theme={null} // Remove entries older than 1 hour const oneHourAgo = Date.now() - (60 * 60 * 1000); const id = await redis.xadd( "events", "*", { user: "bob", action: "logout" }, { trim: { type: "MINID", comparison: "~", threshold: `${oneHourAgo}-0` } } ); ``` ### Don't create stream if it doesn't exist ```typescript theme={null} const id = await redis.xadd( "maybe-exists", "*", { data: "test" }, { nomkStream: true } ); if (id === null) { console.log("Stream doesn't exist"); } else { console.log(`Added with ID: ${id}`); } ``` ### Limit trimming operations ```typescript theme={null} // Trim at most 100 entries per operation const id = await redis.xadd( "events", "*", { user: "charlie", action: "update" }, { trim: { type: "MAXLEN", comparison: "~", threshold: 1000, limit: 100 // Don't trim more than 100 in one operation } } ); ``` ## Stream IDs Explained ### Format Stream IDs have the format: `-` * `milliseconds` - Unix timestamp in milliseconds * `sequence` - Sequence number for entries added in the same millisecond Example: `1678901234567-0` * Timestamp: 1678901234567 (March 15, 2023) * Sequence: 0 (first entry in that millisecond) ### Automatic Generation Using `"*"` automatically generates an ID: ```typescript theme={null} const id1 = await redis.xadd("stream", "*", { data: "first" }); const id2 = await redis.xadd("stream", "*", { data: "second" }); // If added in the same millisecond: // id1: "1678901234567-0" // id2: "1678901234567-1" (sequence incremented) // If added in different milliseconds: // id1: "1678901234567-0" // id2: "1678901234568-0" (new timestamp, sequence reset) ``` ### ID Ordering IDs must always increase: ```typescript theme={null} // ✅ Valid - IDs increase await redis.xadd("stream", "1000-0", { data: "first" }); await redis.xadd("stream", "1000-1", { data: "second" }); await redis.xadd("stream", "1001-0", { data: "third" }); // ❌ Error - ID must be greater than last entry await redis.xadd("stream", "1001-0", { data: "fourth" }); await redis.xadd("stream", "1000-5", { data: "fifth" }); // Error! ``` ## Important Notes ### Trimming Performance * Use `comparison: "~"` (approximate) for better performance * Exact trimming (`"="`) may block for large deletions * Set `limit` to prevent long blocking operations ### Memory Management Without trimming, streams grow indefinitely. Consider: ```typescript theme={null} // Time-based retention (keep last 24 hours) const yesterday = Date.now() - (24 * 60 * 60 * 1000); await redis.xadd("events", "*", data, { trim: { type: "MINID", comparison: "~", threshold: `${yesterday}-0` } }); // Size-based retention (keep last 10,000 entries) await redis.xadd("events", "*", data, { trim: { type: "MAXLEN", comparison: "~", threshold: 10000 } }); ``` # xdel Source: https://upstash-redis-js.mintlify.app/api/commands/xdel Delete entries from a stream by ID ## Usage ```typescript theme={null} import { Redis } from "@upstash/redis"; const redis = new Redis({ url: "", token: "", }); const deleted = await redis.xdel("events", "1678901234567-0"); ``` ## Parameters The key of the stream. One or more entry IDs to delete. Can be a single ID string or an array of IDs. ## Response The number of entries actually deleted. IDs that don't exist are ignored and don't contribute to the count. ## Examples ### Delete a single entry ```typescript theme={null} // Add an entry const id = await redis.xadd("events", "*", { user: "alice", action: "login" }); console.log(await redis.xlen("events")); // 1 // Delete it const deleted = await redis.xdel("events", id); console.log(deleted); // 1 console.log(await redis.xlen("events")); // 0 ``` ### Delete multiple entries ```typescript theme={null} // Add several entries const id1 = await redis.xadd("events", "*", { event: "first" }); const id2 = await redis.xadd("events", "*", { event: "second" }); const id3 = await redis.xadd("events", "*", { event: "third" }); console.log(await redis.xlen("events")); // 3 // Delete multiple at once const deleted = await redis.xdel("events", [id1, id2]); console.log(deleted); // 2 console.log(await redis.xlen("events")); // 1 ``` ### Handle non-existent IDs ```typescript theme={null} const id = await redis.xadd("events", "*", { data: "test" }); // Try to delete existing and non-existent IDs const deleted = await redis.xdel("events", [ id, // Exists "9999999999999-0", // Doesn't exist "9999999999998-0" // Doesn't exist ]); console.log(deleted); // 1 (only the existing ID was deleted) ``` ### Delete all entries individually ```typescript theme={null} // Get all entry IDs const entries = await redis.xrange("events", "-", "+"); const ids = Object.keys(entries); if (ids.length > 0) { const deleted = await redis.xdel("events", ids); console.log(`Deleted ${deleted} entries`); } // Stream still exists but is empty console.log(await redis.xlen("events")); // 0 ``` ### Conditional deletion based on data ```typescript theme={null} // Get all entries const entries = await redis.xrange("events", "-", "+"); // Find entries to delete (e.g., error events) const idsToDelete = Object.entries(entries) .filter(([_, data]) => data.level === "error") .map(([id, _]) => id); if (idsToDelete.length > 0) { const deleted = await redis.xdel("events", idsToDelete); console.log(`Deleted ${deleted} error entries`); } ``` ### Delete old entries by time ```typescript theme={null} // Delete entries older than 1 hour const oneHourAgo = Date.now() - (60 * 60 * 1000); // Get all entries const entries = await redis.xrange("events", "-", "+"); // Filter by timestamp const oldIds = Object.keys(entries).filter(id => { const timestamp = parseInt(id.split("-")[0]); return timestamp < oneHourAgo; }); if (oldIds.length > 0) { const deleted = await redis.xdel("events", oldIds); console.log(`Deleted ${deleted} old entries`); } ``` ### Delete with verification ```typescript theme={null} const id = await redis.xadd("events", "*", { data: "test" }); // Verify entry exists const before = await redis.xrange("events", id, id); console.log("Before:", Object.keys(before).length); // 1 // Delete const deleted = await redis.xdel("events", id); if (deleted === 1) { console.log("Entry successfully deleted"); // Verify deletion const after = await redis.xrange("events", id, id); console.log("After:", Object.keys(after).length); // 0 } ``` ### Batch delete with error handling ```typescript theme={null} async function deleteEntries( key: string, ids: string[] ): Promise<{ deleted: number; failed: string[] }> { const deleted = await redis.xdel(key, ids); // If some IDs weren't deleted, find which ones const failed: string[] = []; if (deleted < ids.length) { // Check each ID to see if it still exists for (const id of ids) { const exists = await redis.xrange(key, id, id); if (Object.keys(exists).length > 0) { failed.push(id); } } } return { deleted, failed }; } const result = await deleteEntries("events", [ "1678901234567-0", "nonexistent-id", "1678901234568-0" ]); console.log(`Deleted: ${result.deleted}, Failed: ${result.failed.length}`); ``` ### Clear stream completely ```typescript theme={null} // Option 1: Delete all entries (stream still exists) const entries = await redis.xrange("events", "-", "+"); if (Object.keys(entries).length > 0) { await redis.xdel("events", Object.keys(entries)); } // Option 2: Delete the entire stream await redis.del("events"); // After option 1: stream exists but is empty // After option 2: stream doesn't exist ``` ## Important Notes ### XDEL vs DEL * `XDEL` removes specific entries from a stream (stream still exists) * `DEL` removes the entire stream key ```typescript theme={null} const id = await redis.xadd("stream", "*", { data: "test" }); // Using XDEL - removes entry, stream still exists await redis.xdel("stream", id); console.log(await redis.exists("stream")); // 1 (stream exists) console.log(await redis.xlen("stream")); // 0 (but empty) // Using DEL - removes entire stream await redis.xadd("stream", "*", { data: "test" }); await redis.del("stream"); console.log(await redis.exists("stream")); // 0 (stream doesn't exist) ``` ### Performance * XDEL is O(1) for each ID to delete * Deleting N entries is O(N) * More efficient to delete multiple IDs in one call than multiple calls: ```typescript theme={null} // ✅ Better - single call await redis.xdel("events", [id1, id2, id3]); // ❌ Less efficient - multiple calls await redis.xdel("events", id1); await redis.xdel("events", id2); await redis.xdel("events", id3); ``` ### Memory Reclamation Deleted entries are removed from memory, but: * The stream key itself still exists (even if empty) * Use `DEL` to completely remove an empty stream * XDEL doesn't automatically delete the stream when it becomes empty ### Return Value The return value is the number of entries **actually deleted**: ```typescript theme={null} // Add 2 entries const id1 = await redis.xadd("stream", "*", { n: 1 }); const id2 = await redis.xadd("stream", "*", { n: 2 }); // Try to delete 3 IDs (2 exist, 1 doesn't) const deleted = await redis.xdel("stream", [ id1, id2, "nonexistent-id" ]); console.log(deleted); // 2 (not 3) ``` ### Use Cases * **Data cleanup**: Remove processed or expired entries * **Error handling**: Delete invalid entries * **Privacy compliance**: Remove user data on request * **Space management**: Delete old entries to free memory * **Selective retention**: Keep only relevant entries # xlen Source: https://upstash-redis-js.mintlify.app/api/commands/xlen Get the number of entries in a stream ## Usage ```typescript theme={null} import { Redis } from "@upstash/redis"; const redis = new Redis({ url: "", token: "", }); const length = await redis.xlen("events"); ``` ## Parameters The key of the stream. ## Response The number of entries in the stream. Returns `0` if the key doesn't exist or is an empty stream. ## Examples ### Get stream length ```typescript theme={null} // Add some entries await redis.xadd("events", "*", { user: "alice", action: "login" }); await redis.xadd("events", "*", { user: "bob", action: "logout" }); await redis.xadd("events", "*", { user: "charlie", action: "update" }); // Get the length const length = await redis.xlen("events"); console.log(length); // 3 ``` ### Check if stream exists ```typescript theme={null} const length = await redis.xlen("nonexistent"); if (length === 0) { console.log("Stream doesn't exist or is empty"); } else { console.log(`Stream has ${length} entries`); } ``` ### Monitor stream growth ```typescript theme={null} const before = await redis.xlen("events"); // Add new entries await redis.xadd("events", "*", { new: "entry1" }); await redis.xadd("events", "*", { new: "entry2" }); const after = await redis.xlen("events"); const added = after - before; console.log(`Added ${added} new entries`); // Output: Added 2 new entries ``` ### Track stream size with trimming ```typescript theme={null} // Add 1000 entries for (let i = 0; i < 1000; i++) { await redis.xadd("events", "*", { index: i }); } console.log(await redis.xlen("events")); // 1000 // Add new entry with trimming to max 500 await redis.xadd( "events", "*", { index: 1000 }, { trim: { type: "MAXLEN", comparison: "~", threshold: 500 } } ); console.log(await redis.xlen("events")); // ~500 (approximate due to "~") ``` ### Check multiple streams ```typescript theme={null} const streams = ["stream1", "stream2", "stream3"]; const lengths = await Promise.all( streams.map(stream => redis.xlen(stream)) ); streams.forEach((stream, i) => { console.log(`${stream}: ${lengths[i]} entries`); }); // Output: // stream1: 10 entries // stream2: 25 entries // stream3: 0 entries ``` ### Stream capacity check ```typescript theme={null} const MAX_STREAM_SIZE = 10000; const currentSize = await redis.xlen("events"); const remainingCapacity = MAX_STREAM_SIZE - currentSize; if (remainingCapacity > 0) { console.log(`Can add ${remainingCapacity} more entries`); await redis.xadd("events", "*", { data: "new entry" }); } else { console.log("Stream is at capacity, trimming required"); await redis.xadd( "events", "*", { data: "new entry" }, { trim: { type: "MAXLEN", comparison: "=", threshold: MAX_STREAM_SIZE } } ); } ``` ### Compare stream sizes ```typescript theme={null} const activeLength = await redis.xlen("active-events"); const archivedLength = await redis.xlen("archived-events"); console.log(`Active: ${activeLength}, Archived: ${archivedLength}`); console.log(`Total events processed: ${activeLength + archivedLength}`); ``` ### Conditional operations based on size ```typescript theme={null} async function addEntry(key: string, data: Record) { const length = await redis.xlen(key); // If stream is getting large, use approximate trimming const trimOptions = length > 5000 ? { trim: { type: "MAXLEN" as const, comparison: "~" as const, threshold: 5000 } } : undefined; return await redis.xadd(key, "*", data, trimOptions); } await addEntry("events", { user: "alice", action: "click" }); ``` ### Calculate stream growth rate ```typescript theme={null} const initialLength = await redis.xlen("metrics"); // Wait 1 minute await new Promise(resolve => setTimeout(resolve, 60000)); const finalLength = await redis.xlen("metrics"); const entriesPerMinute = finalLength - initialLength; console.log(`Stream growth rate: ${entriesPerMinute} entries/minute`); ``` ## Important Notes ### Empty Streams XLEN returns `0` for both non-existent keys and empty streams: ```typescript theme={null} // Non-existent stream const len1 = await redis.xlen("never-created"); console.log(len1); // 0 // Empty stream (all entries deleted) await redis.xadd("temp", "*", { data: "test" }); await redis.del("temp"); const len2 = await redis.xlen("temp"); console.log(len2); // 0 ``` To distinguish between them: ```typescript theme={null} const exists = await redis.exists("events"); const length = await redis.xlen("events"); if (!exists) { console.log("Stream doesn't exist"); } else if (length === 0) { console.log("Stream exists but is empty"); } else { console.log(`Stream has ${length} entries`); } ``` ### Performance * XLEN is an O(1) operation (constant time) * Very fast even for streams with millions of entries * Safe to call frequently for monitoring ### After Trimming XLEN reflects the current number of entries after any trimming: ```typescript theme={null} await redis.xadd("events", "*", { data: "1" }); await redis.xadd("events", "*", { data: "2" }); await redis.xadd("events", "*", { data: "3" }); console.log(await redis.xlen("events")); // 3 // Add with exact trim to 2 entries await redis.xadd( "events", "*", { data: "4" }, { trim: { type: "MAXLEN", comparison: "=", threshold: 2 } } ); console.log(await redis.xlen("events")); // 2 ``` ### Use Cases * **Monitoring**: Track stream size over time * **Capacity planning**: Check if trimming is needed * **Metrics**: Calculate processing rates * **Validation**: Verify entries were added successfully * **Debugging**: Understand stream state # xrange Source: https://upstash-redis-js.mintlify.app/api/commands/xrange Get a range of entries from a stream ## Usage ```typescript theme={null} import { Redis } from "@upstash/redis"; const redis = new Redis({ url: "", token: "", }); const entries = await redis.xrange("events", "-", "+"); ``` ## Parameters The key of the stream to read from. The minimum entry ID to include: * `"-"` - Start from the first entry in the stream * `"-"` - Start from this specific ID (inclusive) * `""` - Start from this timestamp (equivalent to `"-0"`) The maximum entry ID to include: * `"+"` - Include all entries up to the last one * `"-"` - Include entries up to this specific ID (inclusive) * `""` - Include entries up to this timestamp Maximum number of entries to return. ## Response An object where keys are entry IDs and values are objects containing the entry's field-value pairs. The SDK automatically deserializes values and attempts to parse JSON. Returns an empty object `{}` if no entries match the range. ## Examples ### Get all entries ```typescript theme={null} // Add some entries await redis.xadd("events", "*", { user: "alice", action: "login" }); await redis.xadd("events", "*", { user: "bob", action: "logout" }); await redis.xadd("events", "*", { user: "charlie", action: "update" }); // Get all entries const all = await redis.xrange("events", "-", "+"); console.log(all); // Returns: // { // "1678901234567-0": { user: "alice", action: "login" }, // "1678901234568-0": { user: "bob", action: "logout" }, // "1678901234569-0": { user: "charlie", action: "update" } // } ``` ### Get entries in a time range ```typescript theme={null} // Add entries over time const id1 = await redis.xadd("events", "1000-0", { event: "first" }); const id2 = await redis.xadd("events", "2000-0", { event: "second" }); const id3 = await redis.xadd("events", "3000-0", { event: "third" }); const id4 = await redis.xadd("events", "4000-0", { event: "fourth" }); // Get entries between timestamp 1500 and 3500 const range = await redis.xrange("events", "1500", "3500"); console.log(range); // Returns: // { // "2000-0": { event: "second" }, // "3000-0": { event: "third" } // } ``` ### Get entries starting from a specific ID ```typescript theme={null} const id1 = await redis.xadd("events", "*", { n: 1 }); const id2 = await redis.xadd("events", "*", { n: 2 }); const id3 = await redis.xadd("events", "*", { n: 3 }); // Get entries from id2 onwards const entries = await redis.xrange("events", id2, "+"); // Returns entries with id2 and id3 ``` ### Get entries up to a specific ID ```typescript theme={null} // Get entries from beginning up to id2 const entries = await redis.xrange("events", "-", id2); // Returns entries with id1 and id2 ``` ### Limit number of entries ```typescript theme={null} // Add many entries for (let i = 0; i < 100; i++) { await redis.xadd("events", "*", { index: i }); } // Get only first 10 entries const limited = await redis.xrange("events", "-", "+", 10); const count = Object.keys(limited).length; console.log(`Received ${count} entries`); // 10 ``` ### Get entries from last hour ```typescript theme={null} const now = Date.now(); const oneHourAgo = now - (60 * 60 * 1000); const recent = await redis.xrange( "events", `${oneHourAgo}-0`, // Start from 1 hour ago `${now}-0` // Up to now ); ``` ### Iterate through entries ```typescript theme={null} const entries = await redis.xrange("events", "-", "+"); for (const [id, data] of Object.entries(entries)) { console.log(`Entry ${id}:`, data); } // Or get specific fields for (const [id, data] of Object.entries(entries)) { const timestamp = id.split("-")[0]; const date = new Date(parseInt(timestamp)); console.log(`${date.toISOString()}: ${data.action}`); } ``` ### Pagination with XRANGE ```typescript theme={null} let lastId = "-"; const pageSize = 10; // Get first page const page1 = await redis.xrange("events", lastId, "+", pageSize); const ids = Object.keys(page1); if (ids.length > 0) { lastId = ids[ids.length - 1]; // Last ID from this page } // Get next page (entries after lastId) const page2 = await redis.xrange("events", `(${lastId}`, "+", pageSize); // Note: Use "(" prefix for exclusive range (Redis 6.2+) ``` ### Get entries in reverse order ```typescript theme={null} // XRANGE returns entries in ascending order const ascending = await redis.xrange("events", "-", "+"); // To get descending order, use XREVRANGE const descending = await redis.xrevrange("events", "+", "-"); // (Note: XREVRANGE not shown in source, but available in Redis) ``` ### Working with complex data ```typescript theme={null} // Add entries with complex data await redis.xadd("orders", "*", { orderId: "ORD-123", userId: 42, items: JSON.stringify(["item1", "item2"]), total: 99.99, metadata: JSON.stringify({ source: "web", campaign: "summer" }) }); // Read and parse const orders = await redis.xrange("orders", "-", "+"); for (const [id, order] of Object.entries(orders)) { const items = JSON.parse(order.items as string); const metadata = JSON.parse(order.metadata as string); console.log({ id, orderId: order.orderId, userId: order.userId, items, total: order.total, metadata }); } ``` ## Important Notes ### Inclusive Ranges Both `start` and `end` are **inclusive**: ```typescript theme={null} await redis.xadd("stream", "1000-0", { n: 1 }); await redis.xadd("stream", "2000-0", { n: 2 }); await redis.xadd("stream", "3000-0", { n: 3 }); const result = await redis.xrange("stream", "1000-0", "2000-0"); // Returns BOTH 1000-0 and 2000-0 (inclusive) ``` ### Special Range Values | Value | Meaning | | -------- | ---------------------------- | | `"-"` | The minimum ID in the stream | | `"+"` | The maximum ID in the stream | | `""` | Shorthand for `"-0"` | ### Entry ID Format Entry IDs are strings in the format `"-"`: ```typescript theme={null} const entries = await redis.xrange("events", "-", "+"); for (const id of Object.keys(entries)) { const [ms, seq] = id.split("-"); const timestamp = parseInt(ms); const date = new Date(timestamp); console.log(`Entry at ${date.toISOString()}, sequence ${seq}`); } ``` ### Return Format The SDK deserializes the response into a clean object: ```typescript theme={null} // Raw Redis response: // [["1678901234567-0", ["field1", "value1", "field2", "value2"]]] // SDK returns: { "1678901234567-0": { field1: "value1", field2: "value2" } } ``` ### Automatic JSON Parsing The SDK attempts to parse JSON values: ```typescript theme={null} await redis.xadd("stream", "*", { name: "test", data: JSON.stringify({ nested: "object" }) }); const entries = await redis.xrange("stream", "-", "+"); const firstEntry = Object.values(entries)[0]; console.log(firstEntry.data); // Returns: { nested: "object" } (already parsed) ``` ### Performance Considerations * Use `count` to limit results for large streams * Specific time ranges are faster than full scans (`"-"` to `"+"`) * For reverse order, consider using `XREVRANGE` if available * Pagination is efficient when using the last ID from previous page # xread Source: https://upstash-redis-js.mintlify.app/api/commands/xread Read entries from one or more streams ## Usage ```typescript theme={null} import { Redis } from "@upstash/redis"; const redis = new Redis({ url: "", token: "", }); const entries = await redis.xread("events", "0"); ``` ## Parameters The stream key(s) to read from. Can be a single key or an array of keys. The starting ID(s) for each stream. Must match the number of keys: * `"0"` - Read from the beginning of the stream * `"-"` - Read entries after this specific ID * `"$"` - Read only new entries (commonly used with BLOCK) If `key` is an array, `id` must also be an array with the same length. Optional reading options: Maximum number of entries to return per stream. **Deprecated**: Block for the specified milliseconds waiting for new entries. Not yet supported in Upstash Redis. ## Response An array of stream data. The exact structure depends on the streams read. For single stream reads, returns entries in the format: ```typescript theme={null} [ ["stream-name", [ ["id", ["field1", "value1", "field2", "value2", ...]], ... ]] ] ``` Returns an empty array if no entries are found. ## Examples ### Read from beginning of stream ```typescript theme={null} // Add some entries first await redis.xadd("events", "*", { user: "alice", action: "login" }); await redis.xadd("events", "*", { user: "bob", action: "logout" }); // Read all entries const result = await redis.xread("events", "0"); console.log(result); // Returns: // [ // ["events", [ // ["1678901234567-0", ["user", "alice", "action", "login"]], // ["1678901234568-0", ["user", "bob", "action", "logout"]] // ]] // ] ``` ### Read entries after a specific ID ```typescript theme={null} const id1 = await redis.xadd("events", "*", { event: "first" }); const id2 = await redis.xadd("events", "*", { event: "second" }); const id3 = await redis.xadd("events", "*", { event: "third" }); // Read entries after id1 const result = await redis.xread("events", id1); // Returns only entries with id2 and id3 ``` ### Limit number of entries ```typescript theme={null} // Add 10 entries for (let i = 0; i < 10; i++) { await redis.xadd("events", "*", { index: i }); } // Read only 5 entries const result = await redis.xread("events", "0", { count: 5 }); // Returns only the first 5 entries ``` ### Read from multiple streams ```typescript theme={null} // Add entries to multiple streams await redis.xadd("stream1", "*", { source: "stream1", data: "a" }); await redis.xadd("stream2", "*", { source: "stream2", data: "b" }); // Read from both streams const result = await redis.xread( ["stream1", "stream2"], // Array of keys ["0", "0"] // Array of starting IDs ); console.log(result); // Returns: // [ // ["stream1", [["1678901234567-0", ["source", "stream1", "data", "a"]]]], // ["stream2", [["1678901234568-0", ["source", "stream2", "data", "b"]]]] // ] ``` ### Poll for new entries ```typescript theme={null} let lastId = "0"; // Initial read const initial = await redis.xread("events", lastId); if (initial && initial.length > 0) { const entries = initial[0][1] as any[]; if (entries.length > 0) { lastId = entries[entries.length - 1][0]; // Update to last seen ID } } // Later, poll for new entries const newEntries = await redis.xread("events", lastId); ``` ### Read only new entries using \$ ```typescript theme={null} // Add some existing entries await redis.xadd("events", "*", { existing: "data" }); // Read only entries added after this point const futureEntries = await redis.xread("events", "$"); // Returns: [] (nothing new yet) // Add new entry await redis.xadd("events", "*", { new: "data" }); const newEntries = await redis.xread("events", "$"); // Still returns [] because $ means "entries newer than when the read started" ``` ### Processing stream entries ```typescript theme={null} const result = await redis.xread("events", "0"); if (result && result.length > 0) { const [streamName, entries] = result[0] as [string, any[]]; for (const [id, fields] of entries) { // Parse field-value pairs const data: Record = {}; for (let i = 0; i < fields.length; i += 2) { data[fields[i]] = fields[i + 1]; } console.log(`Entry ${id}:`, data); } } ``` ## Important Notes ### Balanced Keys and IDs When reading from multiple streams, the number of keys must match the number of IDs: ```typescript theme={null} // ✅ Correct - 2 keys, 2 IDs await redis.xread(["stream1", "stream2"], ["0", "0"]); // ✅ Correct - 1 key, 1 ID await redis.xread("stream1", "0"); // ❌ Error - 2 keys, 1 ID (unbalanced) await redis.xread(["stream1", "stream2"], "0"); // ❌ Error - 2 keys, 3 IDs (unbalanced) await redis.xread(["stream1", "stream2"], ["0", "0", "0"]); ``` ### Entry ID Positioning XREAD returns entries **after** the specified ID, not including it: ```typescript theme={null} const id1 = await redis.xadd("stream", "*", { n: 1 }); const id2 = await redis.xadd("stream", "*", { n: 2 }); const id3 = await redis.xadd("stream", "*", { n: 3 }); // Read after id1 const result = await redis.xread("stream", id1); // Returns entries with id2 and id3 (NOT id1) ``` ### Special ID Values | ID | Meaning | | -------------- | -------------------------------------- | | `"0"` | Start from the beginning | | `"$"` | Only new entries (none if called once) | | `"-"` | Start after this specific ID | ### Return Format The response format is: ```typescript theme={null} [ ["stream-name", [ ["entry-id", ["field1", "value1", "field2", "value2"]], ["entry-id", ["field1", "value1", "field2", "value2"]] ]], // ... more streams if reading multiple ] ``` Note that field-value pairs are returned as a flat array, not as an object. ### Count Limit The `count` option limits entries **per stream**, not total: ```typescript theme={null} // Reads up to 10 entries from stream1 AND up to 10 from stream2 await redis.xread( ["stream1", "stream2"], ["0", "0"], { count: 10 } ); // May return up to 20 total entries (10 per stream) ``` # ZADD Source: https://upstash-redis-js.mintlify.app/api/commands/zadd Add one or more members to a sorted set, or update their scores ## Method Signature ```typescript theme={null} zadd( key: string, scoreMember: ScoreMember | ZAddCommandOptions, ...scoreMembers: ScoreMember[] ): Promise ``` ## Parameters The key of the sorted set Either a score-member pair object or options object. If options are provided, at least one score-member pair must follow. **ScoreMember object:** * `score` (number): The score for the member * `member` (TData): The member value to add Additional score-member pairs to add to the sorted set ### ZAddCommandOptions Only add new elements. Don't update existing elements. Mutually exclusive with `xx`. Only update existing elements. Don't add new elements. Mutually exclusive with `nx`. Only update existing elements if the new score is greater than the current score. Mutually exclusive with `lt`. Only update existing elements if the new score is less than the current score. Mutually exclusive with `gt`. Modify the return value to be the number of elements changed (added or updated), instead of only newly added elements. When this option is specified, ZADD acts like ZINCRBY. Only one score-member pair can be specified in this mode. ## Response The number of elements added to the sorted set (not including elements already existing for which the score was updated). When `ch` option is used, returns the number of elements that were changed (added or updated). When `incr` option is used, returns the new score of the member (as a number), or `null` if the operation was not performed. ## Examples ### Add a single member ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); const result = await redis.zadd('leaderboard', { score: 100, member: 'player1' }); console.log(result); // 1 ``` ### Add multiple members ```typescript theme={null} const result = await redis.zadd( 'leaderboard', { score: 100, member: 'player1' }, { score: 85, member: 'player2' }, { score: 92, member: 'player3' } ); console.log(result); // 3 ``` ### Add only if member doesn't exist (NX) ```typescript theme={null} const result = await redis.zadd( 'leaderboard', { nx: true }, { score: 100, member: 'player1' } ); console.log(result); // 1 if added, 0 if already exists ``` ### Update only if member exists (XX) ```typescript theme={null} const result = await redis.zadd( 'leaderboard', { xx: true }, { score: 150, member: 'player1' } ); console.log(result); // 0 (score updated but not a new member) ``` ### Update only if new score is greater (GT) ```typescript theme={null} const result = await redis.zadd( 'leaderboard', { gt: true }, { score: 120, member: 'player1' } ); // Only updates if 120 is greater than current score ``` ### Increment member score (INCR) ```typescript theme={null} const newScore = await redis.zadd( 'leaderboard', { incr: true }, { score: 10, member: 'player1' } ); console.log(newScore); // Current score + 10 ``` ### Return changed count (CH) ```typescript theme={null} const result = await redis.zadd( 'leaderboard', { ch: true }, { score: 100, member: 'player1' }, { score: 85, member: 'player2' } ); console.log(result); // Number of elements added or updated ``` ## Related Commands * [ZINCRBY](/api/commands/zincrby) - Increment the score of a member * [ZSCORE](/api/commands/zscore) - Get the score of a member * [ZRANGE](/api/commands/zrange) - Return a range of members * [ZRANK](/api/commands/zrank) - Get the rank of a member # ZCARD Source: https://upstash-redis-js.mintlify.app/api/commands/zcard Get the number of members in a sorted set ## Method Signature ```typescript theme={null} zcard( key: string ): Promise ``` ## Parameters The key of the sorted set ## Response The cardinality (number of members) of the sorted set. Returns `0` if the key does not exist. ## Examples ### Get the number of members ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); // Add members to a leaderboard await redis.zadd( 'leaderboard', { score: 100, member: 'player1' }, { score: 85, member: 'player2' }, { score: 92, member: 'player3' } ); // Get the total number of players const count = await redis.zcard('leaderboard'); console.log(count); // 3 ``` ### Check if sorted set is empty ```typescript theme={null} const count = await redis.zcard('leaderboard'); if (count === 0) { console.log('Leaderboard is empty'); } else { console.log(`Leaderboard has ${count} players`); } ``` ### Get count for non-existing key ```typescript theme={null} const count = await redis.zcard('nonexistent'); console.log(count); // 0 ``` ### Track member count changes ```typescript theme={null} // Get initial count const initialCount = await redis.zcard('leaderboard'); console.log(`Initial count: ${initialCount}`); // Add new members await redis.zadd( 'leaderboard', { score: 78, member: 'player4' }, { score: 95, member: 'player5' } ); // Get updated count const newCount = await redis.zcard('leaderboard'); console.log(`New count: ${newCount}`); console.log(`Added ${newCount - initialCount} members`); ``` ### Calculate percentile rank ```typescript theme={null} const totalMembers = await redis.zcard('leaderboard'); const rank = await redis.zrank('leaderboard', 'player1'); if (rank !== null && totalMembers > 0) { const percentile = ((totalMembers - rank - 1) / totalMembers) * 100; console.log(`player1 is in the top ${percentile.toFixed(1)}%`); } ``` ### Validate sorted set size ```typescript theme={null} const maxMembers = 1000; const currentCount = await redis.zcard('leaderboard'); if (currentCount >= maxMembers) { console.log('Leaderboard is full'); // Maybe remove lowest scoring members await redis.zrange('leaderboard', 0, currentCount - maxMembers); } else { console.log(`Can add ${maxMembers - currentCount} more members`); } ``` ### Display sorted set statistics ```typescript theme={null} const count = await redis.zcard('leaderboard'); if (count > 0) { // Get score range const [lowest] = await redis.zrange('leaderboard', 0, 0, { withScores: true }); const [highest] = await redis.zrange('leaderboard', -1, -1, { withScores: true }); console.log('Leaderboard Statistics:'); console.log(` Total players: ${count}`); console.log(` Lowest score: ${lowest?.[1] ?? 'N/A'}`); console.log(` Highest score: ${highest?.[1] ?? 'N/A'}`); } ``` ### Monitor sorted set growth ```typescript theme={null} // Useful for metrics and monitoring const counts = await Promise.all([ redis.zcard('daily_leaderboard'), redis.zcard('weekly_leaderboard'), redis.zcard('monthly_leaderboard'), ]); console.log('Active players:'); console.log(` Today: ${counts[0]}`); console.log(` This week: ${counts[1]}`); console.log(` This month: ${counts[2]}`); ``` ## Related Commands * [ZADD](/api/commands/zadd) - Add members to a sorted set * [ZREM](/api/commands/zrem) - Remove members from a sorted set * [ZRANGE](/api/commands/zrange) - Return a range of members * [ZCOUNT](/api/commands/zcount) - Count members in a score range # ZCOUNT Source: https://upstash-redis-js.mintlify.app/api/commands/zcount Count the members in a sorted set within a score range ## Method Signature ```typescript theme={null} zcount( key: string, min: number | string, max: number | string ): Promise ``` ## Parameters The key of the sorted set The minimum score in the range: * `number` - Inclusive minimum score * `(number` - Exclusive minimum score (e.g., `(50` means greater than 50) * `-inf` - Negative infinity (all scores below max) The maximum score in the range: * `number` - Inclusive maximum score * `(number` - Exclusive maximum score (e.g., `(100` means less than 100) * `+inf` - Positive infinity (all scores above min) ## Response The number of members in the sorted set with scores in the specified range ## Examples ### Count members in a score range ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); // Add members to a leaderboard await redis.zadd( 'leaderboard', { score: 100, member: 'player1' }, { score: 85, member: 'player2' }, { score: 92, member: 'player3' }, { score: 78, member: 'player4' }, { score: 95, member: 'player5' } ); // Count players with scores between 80 and 95 (inclusive) const count = await redis.zcount('leaderboard', 80, 95); console.log(count); // 3 (player2: 85, player3: 92, player5: 95) ``` ### Count with exclusive bounds ```typescript theme={null} // Count players with scores greater than 80 and less than 95 (exclusive) const count = await redis.zcount('leaderboard', '(80', '(95'); console.log(count); // 2 (player2: 85, player3: 92) ``` ### Count all scores above a threshold ```typescript theme={null} // Count all players with scores above 90 const highScorers = await redis.zcount('leaderboard', 90, '+inf'); console.log(`${highScorers} players have scores above 90`); ``` ### Count all scores below a threshold ```typescript theme={null} // Count all players with scores below 85 const lowScorers = await redis.zcount('leaderboard', '-inf', 85); console.log(`${lowScorers} players have scores of 85 or below`); ``` ### Count all members ```typescript theme={null} // Count all members (equivalent to ZCARD) const totalCount = await redis.zcount('leaderboard', '-inf', '+inf'); console.log(`Total members: ${totalCount}`); ``` ### Grade distribution analysis ```typescript theme={null} // Count students in different grade ranges const [aGrades, bGrades, cGrades, dGrades, fGrades] = await Promise.all([ redis.zcount('exam:scores', 90, 100), // A: 90-100 redis.zcount('exam:scores', 80, '(90'), // B: 80-89 redis.zcount('exam:scores', 70, '(80'), // C: 70-79 redis.zcount('exam:scores', 60, '(70'), // D: 60-69 redis.zcount('exam:scores', 0, '(60'), // F: 0-59 ]); console.log('Grade Distribution:'); console.log(` A: ${aGrades}`); console.log(` B: ${bGrades}`); console.log(` C: ${cGrades}`); console.log(` D: ${dGrades}`); console.log(` F: ${fGrades}`); ``` ### Calculate percentile ```typescript theme={null} const score = 85; const totalMembers = await redis.zcard('leaderboard'); const countBelow = await redis.zcount('leaderboard', '-inf', `(${score}`); if (totalMembers > 0) { const percentile = (countBelow / totalMembers) * 100; console.log(`Score of ${score} is in the ${percentile.toFixed(1)}th percentile`); } ``` ### Monitor score ranges ```typescript theme={null} // Check if there are enough high-scoring players for a tournament const qualifyingScore = 90; const qualifiedCount = await redis.zcount('leaderboard', qualifyingScore, '+inf'); const requiredPlayers = 8; if (qualifiedCount >= requiredPlayers) { console.log(`${qualifiedCount} players qualified for the tournament!`); } else { console.log(`Need ${requiredPlayers - qualifiedCount} more qualified players`); } ``` ### Price range filtering ```typescript theme={null} // Count products in a price range const minPrice = 50; const maxPrice = 200; const productsInRange = await redis.zcount('products:by-price', minPrice, maxPrice); console.log(`${productsInRange} products between $${minPrice} and $${maxPrice}`); ``` ### Time-based analysis ```typescript theme={null} // Count events in a time window (using timestamps as scores) const now = Date.now(); const oneDayAgo = now - 24 * 60 * 60 * 1000; const recentEvents = await redis.zcount('events:timeline', oneDayAgo, now); console.log(`${recentEvents} events in the last 24 hours`); ``` ### Statistical analysis ```typescript theme={null} // Analyze score distribution const totalMembers = await redis.zcard('leaderboard'); if (totalMembers > 0) { const quartile1 = await redis.zcount('leaderboard', '-inf', 75); const quartile2 = await redis.zcount('leaderboard', '-inf', 85); const quartile3 = await redis.zcount('leaderboard', '-inf', 95); console.log('Score Distribution:'); console.log(` 0-75: ${quartile1} (${(quartile1/totalMembers*100).toFixed(1)}%)`); console.log(` 75-85: ${quartile2 - quartile1} (${((quartile2-quartile1)/totalMembers*100).toFixed(1)}%)`); console.log(` 85-95: ${quartile3 - quartile2} (${((quartile3-quartile2)/totalMembers*100).toFixed(1)}%)`); console.log(` 95+: ${totalMembers - quartile3} (${((totalMembers-quartile3)/totalMembers*100).toFixed(1)}%)`); } ``` ## Related Commands * [ZCARD](/api/commands/zcard) - Get the total number of members * [ZRANGE](/api/commands/zrange) - Return members in a range * [ZSCORE](/api/commands/zscore) - Get the score of a member * [ZADD](/api/commands/zadd) - Add members to a sorted set # ZINCRBY Source: https://upstash-redis-js.mintlify.app/api/commands/zincrby Increment the score of a member in a sorted set ## Method Signature ```typescript theme={null} zincrby( key: string, increment: number, member: TData ): Promise ``` ## Parameters The key of the sorted set The amount to increment the score by. Can be negative to decrement the score. The member whose score should be incremented. If the member does not exist, it is added to the sorted set with the increment as its score. ## Response The new score of the member after the increment operation ## Examples ### Increment a member's score ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); // Add initial member await redis.zadd('leaderboard', { score: 100, member: 'player1' }); // Increment player1's score by 10 const newScore = await redis.zincrby('leaderboard', 10, 'player1'); console.log(newScore); // 110 ``` ### Decrement a member's score ```typescript theme={null} // Use a negative increment to decrease the score const newScore = await redis.zincrby('leaderboard', -5, 'player1'); console.log(newScore); // 105 (if previous score was 110) ``` ### Add new member with increment ```typescript theme={null} // If member doesn't exist, it's created with the increment as its score const score = await redis.zincrby('leaderboard', 50, 'newplayer'); console.log(score); // 50 ``` ### Track points over time ```typescript theme={null} // Award points for completing tasks await redis.zincrby('leaderboard', 10, 'player1'); // Completed task 1 await redis.zincrby('leaderboard', 25, 'player1'); // Completed task 2 await redis.zincrby('leaderboard', 15, 'player1'); // Completed task 3 // Get final score const finalScore = await redis.zscore('leaderboard', 'player1'); console.log(`Total points: ${finalScore}`); // 50 (10 + 25 + 15) ``` ### Award bonus points ```typescript theme={null} const player = 'player1'; const bonusPoints = 100; const newScore = await redis.zincrby('leaderboard', bonusPoints, player); console.log(`${player} earned ${bonusPoints} bonus points!`); console.log(`New score: ${newScore}`); ``` ### Implement daily streaks ```typescript theme={null} // Increment streak count const streak = await redis.zincrby('user:streaks', 1, 'user123'); console.log(`Current streak: ${streak} days`); if (streak >= 7) { console.log('Achievement unlocked: 7-day streak!'); } ``` ### Track negative metrics ```typescript theme={null} // Track penalties or deductions const penalties = await redis.zincrby('penalties', 1, 'player1'); console.log(`Total penalties: ${penalties}`); if (penalties >= 3) { console.log('Warning: Too many penalties!'); } ``` ### Atomic counter with ranking ```typescript theme={null} // Increment view count and maintain ranking const views = await redis.zincrby('popular:posts', 1, 'post:123'); console.log(`Post has ${views} views`); // Get rank among all posts const rank = await redis.zrank('popular:posts', 'post:123'); const totalPosts = await redis.zcard('popular:posts'); if (rank !== null) { const position = totalPosts - rank; console.log(`Ranked #${position} of ${totalPosts} posts`); } ``` ### Implement voting system ```typescript theme={null} // Upvote const upvote = async (itemId: string) => { const newScore = await redis.zincrby('votes', 1, itemId); console.log(`${itemId} now has ${newScore} votes`); return newScore; }; // Downvote const downvote = async (itemId: string) => { const newScore = await redis.zincrby('votes', -1, itemId); console.log(`${itemId} now has ${newScore} votes`); return newScore; }; await upvote('post:1'); await upvote('post:1'); await downvote('post:1'); // Final score: 1 ``` ### Calculate running averages ```typescript theme={null} // Track cumulative scores const score = 85; const newTotal = await redis.zincrby('test:totals', score, 'student1'); // Track number of tests taken const testCount = await redis.zincrby('test:counts', 1, 'student1'); const average = newTotal / testCount; console.log(`Average score: ${average.toFixed(2)}`); ``` ## Related Commands * [ZADD](/api/commands/zadd) - Add or update members in a sorted set * [ZSCORE](/api/commands/zscore) - Get the score of a member * [ZRANK](/api/commands/zrank) - Get the rank of a member * [ZRANGE](/api/commands/zrange) - Return a range of members # ZRANGE Source: https://upstash-redis-js.mintlify.app/api/commands/zrange Return a range of members in a sorted set ## Method Signature ```typescript theme={null} // By index (default) zrange( key: string, min: number, max: number, options?: ZRangeCommandOptions ): Promise // By score zrange( key: string, min: number | `(${number}` | '-inf' | '+inf', max: number | `(${number}` | '-inf' | '+inf', options: { byScore: true } & ZRangeCommandOptions ): Promise // By lexicographical order zrange( key: string, min: `(${string}` | `[${string}` | '-' | '+', max: `(${string}` | `[${string}` | '-' | '+', options: { byLex: true } & ZRangeCommandOptions ): Promise ``` ## Parameters The key of the sorted set The minimum range value: * By index: 0-based index (use negative for reverse indexing, e.g., -1 for last element) * By score: number, `(number` (exclusive), `-inf`, or `+inf` * By lex: `[member` (inclusive), `(member` (exclusive), `-` (minimum), or `+` (maximum) The maximum range value (same format as min) Options to control the range query behavior ### ZRangeCommandOptions Query by score range instead of index. Mutually exclusive with `byLex`. Query by lexicographical range instead of index. All members must have the same score. Mutually exclusive with `byScore`. Reverse the ordering, so elements are returned from highest to lowest score Return scores along with members. The result will be a flat array alternating between members and scores. Skip this many elements before returning results. Must be used together with `count`. Return at most this many elements. Must be used together with `offset`. ## Response An array of members in the specified range. When `withScores` is true, returns a flat array alternating between member and score: `[member1, score1, member2, score2, ...]` ## Examples ### Get range by index ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); // Add members await redis.zadd( 'leaderboard', { score: 100, member: 'player1' }, { score: 85, member: 'player2' }, { score: 92, member: 'player3' }, { score: 78, member: 'player4' } ); // Get top 3 players (indices 0-2 in descending order) const top3 = await redis.zrange('leaderboard', 0, 2, { rev: true }); console.log(top3); // ['player1', 'player3', 'player2'] ``` ### Get all members ```typescript theme={null} // Get all members from lowest to highest score const all = await redis.zrange('leaderboard', 0, -1); console.log(all); // ['player4', 'player2', 'player3', 'player1'] ``` ### Get range with scores ```typescript theme={null} const withScores = await redis.zrange('leaderboard', 0, 2, { rev: true, withScores: true, }); console.log(withScores); // ['player1', 100, 'player3', 92, 'player2', 85] ``` ### Get members by score range ```typescript theme={null} // Get all players with scores between 80 and 95 const byScore = await redis.zrange('leaderboard', 80, 95, { byScore: true, }); console.log(byScore); // ['player2', 'player3'] ``` ### Get members by score range (exclusive) ```typescript theme={null} // Get players with scores greater than 80 and less than 95 (exclusive) const byScoreExclusive = await redis.zrange('leaderboard', '(80', '(95', { byScore: true, }); console.log(byScoreExclusive); // ['player2', 'player3'] ``` ### Get members with score infinity bounds ```typescript theme={null} // Get all players with scores greater than 90 const highScores = await redis.zrange('leaderboard', 90, '+inf', { byScore: true, }); console.log(highScores); // ['player3', 'player1'] ``` ### Get members by lexicographical range ```typescript theme={null} // Add members with same score for lex ordering await redis.zadd( 'names', { score: 0, member: 'alice' }, { score: 0, member: 'bob' }, { score: 0, member: 'charlie' }, { score: 0, member: 'diana' } ); // Get names from 'b' to 'd' (inclusive) const byLex = await redis.zrange('names', '[b', '[d', { byLex: true, }); console.log(byLex); // ['bob', 'charlie', 'diana'] ``` ### Use limit with offset and count ```typescript theme={null} // Get 2 members starting from the 3rd highest score const paginated = await redis.zrange('leaderboard', 0, '+inf', { byScore: true, rev: true, offset: 2, count: 2, }); console.log(paginated); // 2 members starting from rank 3 ``` ## Related Commands * [ZADD](/api/commands/zadd) - Add members to a sorted set * [ZRANK](/api/commands/zrank) - Get the rank of a member * [ZSCORE](/api/commands/zscore) - Get the score of a member * [ZCARD](/api/commands/zcard) - Get the number of members # ZRANK Source: https://upstash-redis-js.mintlify.app/api/commands/zrank Determine the index of a member in a sorted set ## Method Signature ```typescript theme={null} zrank( key: string, member: TData ): Promise ``` ## Parameters The key of the sorted set The member whose rank you want to retrieve ## Response The rank (0-based index) of the member in the sorted set, ordered from lowest to highest score. Returns `null` if the member does not exist in the sorted set. The member with the lowest score has rank 0. ## Examples ### Get rank of a member ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); // Add members to a leaderboard await redis.zadd( 'leaderboard', { score: 100, member: 'player1' }, { score: 85, member: 'player2' }, { score: 92, member: 'player3' }, { score: 78, member: 'player4' } ); // Get rank of player2 (score: 85) const rank = await redis.zrank('leaderboard', 'player2'); console.log(rank); // 1 (second lowest score) ``` ### Get rank and calculate position ```typescript theme={null} // Get rank (0-based) const rank = await redis.zrank('leaderboard', 'player1'); if (rank !== null) { const position = rank + 1; // Convert to 1-based position console.log(`player1 is in position ${position}`); } ``` ### Handle non-existing member ```typescript theme={null} const rank = await redis.zrank('leaderboard', 'nonexistent'); console.log(rank); // null if (rank === null) { console.log('Player not found in leaderboard'); } ``` ### Get reverse rank (highest to lowest) ```typescript theme={null} // To get rank from highest to lowest, calculate from total count const totalMembers = await redis.zcard('leaderboard'); const rank = await redis.zrank('leaderboard', 'player1'); if (rank !== null) { const reverseRank = totalMembers - rank - 1; console.log(`Reverse rank: ${reverseRank}`); // 0 for highest score } ``` ### Display leaderboard with ranks ```typescript theme={null} // Get all members with scores const members = await redis.zrange('leaderboard', 0, -1, { rev: true, withScores: true, }); // Display with calculated positions for (let i = 0; i < members.length; i += 2) { const member = members[i]; const score = members[i + 1]; const rank = await redis.zrank('leaderboard', member); const position = rank !== null ? rank + 1 : 'N/A'; console.log(`${position}. ${member}: ${score}`); } ``` ### Check if member is in top N ```typescript theme={null} const topN = 10; const totalMembers = await redis.zcard('leaderboard'); const rank = await redis.zrank('leaderboard', 'player1'); if (rank !== null) { const reverseRank = totalMembers - rank - 1; const isInTopN = reverseRank < topN; console.log(`In top ${topN}: ${isInTopN}`); } ``` ## Related Commands * [ZADD](/api/commands/zadd) - Add members to a sorted set * [ZSCORE](/api/commands/zscore) - Get the score of a member * [ZRANGE](/api/commands/zrange) - Return a range of members * [ZCARD](/api/commands/zcard) - Get the number of members # ZREM Source: https://upstash-redis-js.mintlify.app/api/commands/zrem Remove one or more members from a sorted set ## Method Signature ```typescript theme={null} zrem( key: string, ...members: TData[] ): Promise ``` ## Parameters The key of the sorted set One or more member values to remove from the sorted set ## Response The number of members removed from the sorted set, not including non-existing members ## Examples ### Remove a single member ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); // First, add some members await redis.zadd( 'leaderboard', { score: 100, member: 'player1' }, { score: 85, member: 'player2' }, { score: 92, member: 'player3' } ); // Remove a member const result = await redis.zrem('leaderboard', 'player2'); console.log(result); // 1 ``` ### Remove multiple members ```typescript theme={null} const result = await redis.zrem('leaderboard', 'player1', 'player2', 'player3'); console.log(result); // 3 (or fewer if some members don't exist) ``` ### Remove non-existing member ```typescript theme={null} const result = await redis.zrem('leaderboard', 'nonexistent'); console.log(result); // 0 ``` ### Remove and verify ```typescript theme={null} // Check member count before removal const countBefore = await redis.zcard('leaderboard'); console.log(countBefore); // e.g., 5 // Remove members const removed = await redis.zrem('leaderboard', 'player1', 'player2'); console.log(removed); // 2 // Verify count after removal const countAfter = await redis.zcard('leaderboard'); console.log(countAfter); // 3 ``` ## Related Commands * [ZADD](/api/commands/zadd) - Add members to a sorted set * [ZCARD](/api/commands/zcard) - Get the number of members * [ZRANGE](/api/commands/zrange) - Return a range of members * [ZRANK](/api/commands/zrank) - Get the rank of a member # ZSCORE Source: https://upstash-redis-js.mintlify.app/api/commands/zscore Get the score associated with a member in a sorted set ## Method Signature ```typescript theme={null} zscore( key: string, member: TData ): Promise ``` ## Parameters The key of the sorted set The member whose score you want to retrieve ## Response The score of the member as a number. Returns `null` if the member does not exist in the sorted set or the key does not exist. ## Examples ### Get score of a member ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); // Add members to a leaderboard await redis.zadd( 'leaderboard', { score: 100, member: 'player1' }, { score: 85, member: 'player2' }, { score: 92, member: 'player3' } ); // Get player1's score const score = await redis.zscore('leaderboard', 'player1'); console.log(score); // 100 ``` ### Handle non-existing member ```typescript theme={null} const score = await redis.zscore('leaderboard', 'nonexistent'); console.log(score); // null if (score === null) { console.log('Player not found in leaderboard'); } else { console.log(`Player score: ${score}`); } ``` ### Update score based on current value ```typescript theme={null} // Get current score const currentScore = await redis.zscore('leaderboard', 'player1'); if (currentScore !== null) { // Award bonus points const bonusPoints = 50; await redis.zadd('leaderboard', { score: currentScore + bonusPoints, member: 'player1', }); console.log(`New score: ${currentScore + bonusPoints}`); } ``` ### Get multiple scores ```typescript theme={null} const players = ['player1', 'player2', 'player3']; const scores = await Promise.all( players.map(player => redis.zscore('leaderboard', player)) ); players.forEach((player, index) => { console.log(`${player}: ${scores[index] ?? 'Not found'}`); }); ``` ### Calculate score difference ```typescript theme={null} const score1 = await redis.zscore('leaderboard', 'player1'); const score2 = await redis.zscore('leaderboard', 'player2'); if (score1 !== null && score2 !== null) { const difference = Math.abs(score1 - score2); console.log(`Score difference: ${difference}`); } ``` ### Display member with score and rank ```typescript theme={null} const member = 'player1'; const [score, rank] = await Promise.all([ redis.zscore('leaderboard', member), redis.zrank('leaderboard', member), ]); if (score !== null && rank !== null) { console.log(`${member}:`); console.log(` Score: ${score}`); console.log(` Rank: ${rank + 1}`); } else { console.log(`${member} not found`); } ``` ### Check if score meets threshold ```typescript theme={null} const threshold = 90; const score = await redis.zscore('leaderboard', 'player1'); if (score !== null && score >= threshold) { console.log('Player qualifies for next level!'); } else { const needed = threshold - (score ?? 0); console.log(`Need ${needed} more points to qualify`); } ``` ## Related Commands * [ZADD](/api/commands/zadd) - Add or update members in a sorted set * [ZINCRBY](/api/commands/zincrby) - Increment the score of a member * [ZRANK](/api/commands/zrank) - Get the rank of a member * [ZRANGE](/api/commands/zrange) - Return a range of members # Pipeline Source: https://upstash-redis-js.mintlify.app/api/pipeline Batch multiple Redis commands for better performance The `Pipeline` class allows you to batch multiple Redis commands into a single HTTP request, significantly improving performance when executing multiple operations. ## Overview Upstash REST API supports command pipelining to send multiple commands in batch instead of sending each command one by one. When using pipelines, several commands are sent using a single HTTP request, and a single JSON array response is returned. **Execution is not atomic**: Commands in a pipeline are executed in order, but commands from other clients can interleave. Use `multi()` for atomic transactions. ## Creating a Pipeline ### pipeline() Create a pipeline for batching commands (non-atomic). ```typescript theme={null} const pipeline = redis.pipeline(); ``` ### multi() Create a transaction for atomic execution of commands. ```typescript theme={null} const transaction = redis.multi(); ``` ## Methods ### exec() Execute all commands in the pipeline and return results. ```typescript theme={null} const pipeline = redis.pipeline(); pipeline.set('key1', 'value1'); pipeline.set('key2', 'value2'); pipeline.get('key1'); const results = await pipeline.exec(); // results: ['OK', 'OK', 'value1'] ``` Array of results corresponding to each command in the pipeline #### With Type Inference When commands are chained, TypeScript automatically infers the return types: ```typescript theme={null} const results = await redis.pipeline() .set('key', 'value') .get('key') .incr('counter') .exec(); // results: ['OK', 'value', 1] // TypeScript knows the types! ``` #### Manual Type Annotation You can manually specify return types when needed: ```typescript theme={null} interface User { name: string; age: number; } const p = redis.pipeline(); p.set('user:1', { name: 'Alice', age: 30 }); p.get('user:1'); const results = await p.exec<['OK', User]>(); ``` #### Error Handling By default, if any command fails, the entire pipeline throws an error: ```typescript theme={null} try { const results = await pipeline.exec(); } catch (error) { console.error('Pipeline failed:', error); } ``` #### Keep Errors Option To get individual errors for each command, use `keepErrors: true`: ```typescript theme={null} const p = redis.pipeline(); p.get('key1'); p.get('key2'); p.get('nonexistent'); const results = await p.exec({ keepErrors: true }); // results: [ // { result: 'value1', error: undefined }, // { result: 'value2', error: undefined }, // { result: null, error: undefined } // ] ``` Execution options If true, returns objects with `result` and `error` properties instead of throwing on errors When `keepErrors` is false (default): Array of command results When `keepErrors` is true: Array of objects with: * `result`: The command result * `error`: Error message if command failed, undefined otherwise ### length() Get the number of commands in the pipeline before execution. ```typescript theme={null} const p = redis.pipeline(); p.set('key1', 'value1'); p.set('key2', 'value2'); const count = p.length(); // count: 2 ``` Number of commands queued in the pipeline ## Command Methods All Redis commands available on the Redis client are also available on Pipeline. Commands are chainable: ### String Commands ```typescript theme={null} const p = redis.pipeline() .set('key', 'value') .get('key') .mset({ key1: 'value1', key2: 'value2' }) .mget('key1', 'key2') .incr('counter') .incrby('counter', 5); const results = await p.exec(); ``` ### Hash Commands ```typescript theme={null} const p = redis.pipeline() .hset('user:1', { name: 'Alice', age: 30 }) .hget('user:1', 'name') .hgetall('user:1') .hincrby('user:1', 'age', 1); const results = await p.exec(); ``` ### List Commands ```typescript theme={null} const p = redis.pipeline() .lpush('queue', 'job1', 'job2') .llen('queue') .lrange('queue', 0, -1) .lpop('queue'); const results = await p.exec(); ``` ### Set Commands ```typescript theme={null} const p = redis.pipeline() .sadd('tags', 'redis', 'database') .smembers('tags') .sismember('tags', 'redis'); const results = await p.exec(); ``` ### Sorted Set Commands ```typescript theme={null} const p = redis.pipeline() .zadd('leaderboard', { score: 100, member: 'player1' }) .zadd('leaderboard', { score: 200, member: 'player2' }) .zrange('leaderboard', 0, -1) .zrank('leaderboard', 'player1'); const results = await p.exec(); ``` ### JSON Commands ```typescript theme={null} const p = redis.pipeline() .json.set('user:1', '$', { name: 'Alice', age: 30 }) .json.get('user:1') .json.numincrby('user:1', '$.age', 1); const results = await p.exec(); ``` ## Usage Patterns ### Basic Pipelining ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = Redis.fromEnv(); const pipeline = redis.pipeline(); pipeline.set('key1', 'value1'); pipeline.set('key2', 'value2'); pipeline.get('key1'); pipeline.get('key2'); const [set1, set2, get1, get2] = await pipeline.exec(); // set1: 'OK' // set2: 'OK' // get1: 'value1' // get2: 'value2' ``` ### Chaining Commands ```typescript theme={null} const results = await redis.pipeline() .set('user:1:name', 'Alice') .set('user:1:email', 'alice@example.com') .get('user:1:name') .get('user:1:email') .exec(); ``` ### Atomic Transactions ```typescript theme={null} // Use multi() for atomic execution const transaction = redis.multi(); transaction.set('balance', 100); transaction.decrby('balance', 30); transaction.incrby('balance', 50); const results = await transaction.exec(); // All commands execute atomically // results: ['OK', 70, 120] ``` ### Complex Operations ```typescript theme={null} interface User { name: string; email: string; score: number; } const p = redis.pipeline(); // Create multiple users p.hset('user:1', { name: 'Alice', email: 'alice@example.com' }); p.hset('user:2', { name: 'Bob', email: 'bob@example.com' }); // Add to leaderboard p.zadd('leaderboard', { score: 100, member: 'user:1' }); p.zadd('leaderboard', { score: 150, member: 'user:2' }); // Get data p.hgetall('user:1'); p.zrange('leaderboard', 0, -1, { withScores: true }); const results = await p.exec(); ``` ### Error Handling with keepErrors ```typescript theme={null} const p = redis.pipeline(); p.set('key1', 'value1'); p.get('key1'); p.lpop('nonexistent-list'); // Might return null, not an error p.get('key2'); const results = await p.exec({ keepErrors: true }); results.forEach((result, index) => { if (result.error) { console.error(`Command ${index} failed:`, result.error); } else { console.log(`Command ${index} result:`, result.result); } }); ``` ## Performance Benefits Pipelining can dramatically improve performance by reducing round-trip time: ```typescript theme={null} // Without pipeline: 4 round trips await redis.set('key1', 'value1'); await redis.set('key2', 'value2'); await redis.get('key1'); await redis.get('key2'); // Total time: ~400ms (100ms per request) // With pipeline: 1 round trip const results = await redis.pipeline() .set('key1', 'value1') .set('key2', 'value2') .get('key1') .get('key2') .exec(); // Total time: ~100ms ``` ## TypeScript Examples ### Type-Safe Pipeline ```typescript theme={null} interface User { id: string; name: string; email: string; } interface Product { id: string; name: string; price: number; } const results = await redis.pipeline() .set('user:1', { id: '1', name: 'Alice', email: 'alice@example.com' }) .set('product:1', { id: '1', name: 'Widget', price: 9.99 }) .get('user:1') .get('product:1') .exec(); // results are properly typed: // ['OK', 'OK', User | null, Product | null] ``` ### Mixing Command Types ```typescript theme={null} const p = redis.pipeline(); // String operations p.set('counter', '0'); p.incr('counter'); // Hash operations p.hset('user:1', { name: 'Alice' }); p.hget('user:1', 'name'); // List operations p.lpush('queue', 'job1'); p.llen('queue'); // Execute all const [ setResult, // 'OK' incrResult, // 1 hsetResult, // 1 hgetResult, // 'Alice' lpushResult, // 1 llenResult // 1 ] = await p.exec(); ``` ## Comparison: pipeline() vs multi() | Feature | pipeline() | multi() | | ----------------- | ---------------------------- | ------------------------------------- | | **Atomicity** | No - commands can interleave | Yes - all commands execute atomically | | **Use Case** | Performance optimization | Transactions requiring consistency | | **Redis Command** | Sends multiple commands | Wraps commands in MULTI/EXEC | | **Rollback** | Not supported | All or nothing execution | ```typescript theme={null} // Pipeline: Non-atomic, for performance const pipeline = redis.pipeline(); pipeline.set('key1', 'value1'); pipeline.set('key2', 'value2'); await pipeline.exec(); // Other clients can modify keys between commands // Transaction: Atomic, for consistency const transaction = redis.multi(); transaction.set('key1', 'value1'); transaction.set('key2', 'value2'); await transaction.exec(); // All commands execute as a single atomic operation ``` # Redis Source: https://upstash-redis-js.mintlify.app/api/redis Main Redis client class for interacting with Upstash Redis The `Redis` class is the main entry point for interacting with Upstash Redis. It provides methods for all Redis commands and supports both direct instantiation and environment-based configuration. ## Constructor ### With Configuration Object Create a new Redis client by providing connection credentials. ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: '', token: '' }); ``` Configuration object for the Redis client UPSTASH\_REDIS\_REST\_URL - Get it from your [Upstash Console](https://console.upstash.com/redis) UPSTASH\_REDIS\_REST\_TOKEN - Get it from your [Upstash Console](https://console.upstash.com/redis) Automatically deserialize returned data using `JSON.parse` Enable telemetry data collection. Can be disabled by setting `UPSTASH_DISABLE_TELEMETRY` environment variable Enable automatic pipelining of commands for better performance Enable latency logging for commands When enabled, subsequent commands are guaranteed to observe effects of all earlier writes submitted by the same client Configure retry behavior for network errors Number of retries to attempt before giving up Backoff function that returns milliseconds to wait before retrying. Default: `Math.exp(retryCount) * 50` Encode responses as base64 before sending. Disable if you're sure your data is valid UTF-8 Configure cache behavior for requests AbortSignal to cancel requests. See [MDN AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) HTTP(S) agent for connection pooling (Node.js only). Not supported in edge runtimes ```typescript theme={null} import https from 'https'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, agent: new https.Agent({ keepAlive: true }) }); ``` Enable HTTP keep-alive for connection reuse ### With Custom Requester Create a Redis client with a custom HTTP requester implementation. ```typescript theme={null} import { Redis, Requester, UpstashRequest, UpstashResponse } from '@upstash/redis'; const customRequester: Requester = { request: async (req: UpstashRequest): Promise> => { // Custom request implementation // ... } }; const redis = new Redis(customRequester); ``` Custom requester implementation Function that handles HTTP requests to Upstash Redis REST API Enable read-your-writes consistency Sync token for maintaining consistency ## Static Methods ### fromEnv() Create a Redis instance from environment variables. ```typescript theme={null} const redis = Redis.fromEnv(); ``` This method automatically loads connection credentials from: * `UPSTASH_REDIS_REST_URL` or `KV_REST_API_URL` (fallback for Vercel KV) * `UPSTASH_REDIS_REST_TOKEN` or `KV_REST_API_TOKEN` (fallback for Vercel KV) Optional configuration (all options except `url` and `token`) A configured Redis instance ```typescript theme={null} const redis = Redis.fromEnv({ enableAutoPipelining: false, latencyLogging: true }); ``` ## Properties ### readYourWritesSyncToken Get or set the sync token for read-your-writes consistency. ```typescript theme={null} // Get current sync token const token = redis.readYourWritesSyncToken; // Set sync token redis.readYourWritesSyncToken = 'your-sync-token'; ``` The current sync token ## Instance Methods ### pipeline() Create a new pipeline to batch multiple commands. ```typescript theme={null} const pipeline = redis.pipeline(); pipeline.set('key1', 'value1'); pipeline.set('key2', 'value2'); pipeline.get('key1'); const results = await pipeline.exec(); // results: ['OK', 'OK', 'value1'] ``` A new Pipeline instance for batching commands See [Pipeline](/api/pipeline) for full documentation. ### multi() Create a transaction (MULTI/EXEC) to execute commands atomically. ```typescript theme={null} const transaction = redis.multi(); transaction.set('key1', 'value1'); transaction.set('key2', 'value2'); transaction.get('key1'); const results = await transaction.exec(); // All commands executed atomically ``` A new Pipeline instance configured for atomic execution ### createScript() Create a Lua script that can be executed efficiently using EVALSHA. ```typescript theme={null} const script = redis.createScript( 'return ARGV[1];' ); const result = await script.eval([], ['Hello World']); // result: 'Hello World' ``` The Lua script to execute Script options If true, returns a ScriptRO instance that uses EVALSHA\_RO/EVAL\_RO A Script or ScriptRO instance depending on the readonly option See [Script](/api/script) for full documentation. ### use() Wrap middleware around the HTTP client for custom request/response handling. ```typescript theme={null} redis.use(async (request, next) => { console.log('Making request:', request); const response = await next(request); console.log('Received response:', response); return response; }); ``` Middleware function to wrap around requests ## Command Methods The Redis class provides methods for all Redis commands. Here are some commonly used examples: ### String Commands ```typescript theme={null} // SET await redis.set('key', 'value'); await redis.set('key', 'value', { ex: 60 }); // with expiration // GET const value = await redis.get('key'); // MGET const values = await redis.mget('key1', 'key2', 'key3'); // MSET await redis.mset({ key1: 'value1', key2: 'value2' }); // INCR await redis.incr('counter'); // INCRBY await redis.incrby('counter', 5); ``` ### Hash Commands ```typescript theme={null} // HSET await redis.hset('user:1', { name: 'John', age: 30 }); // HGET const name = await redis.hget('user:1', 'name'); // HGETALL const user = await redis.hgetall('user:1'); // HMGET const [name, age] = await redis.hmget('user:1', 'name', 'age'); ``` ### List Commands ```typescript theme={null} // LPUSH await redis.lpush('list', 'item1', 'item2'); // RPUSH await redis.rpush('list', 'item3'); // LRANGE const items = await redis.lrange('list', 0, -1); // LPOP const item = await redis.lpop('list'); ``` ### Set Commands ```typescript theme={null} // SADD await redis.sadd('set', 'member1', 'member2'); // SMEMBERS const members = await redis.smembers('set'); // SISMEMBER const isMember = await redis.sismember('set', 'member1'); ``` ### Sorted Set Commands ```typescript theme={null} // ZADD await redis.zadd('leaderboard', { score: 100, member: 'player1' }); // ZRANGE const topPlayers = await redis.zrange('leaderboard', 0, 9); // ZRANK const rank = await redis.zrank('leaderboard', 'player1'); ``` ### JSON Commands ```typescript theme={null} // JSON.SET await redis.json.set('user:1', '$', { name: 'John', age: 30 }); // JSON.GET const user = await redis.json.get('user:1', '$'); // JSON.NUMINCRBY await redis.json.numincrby('user:1', '$.age', 1); ``` ### Key Management ```typescript theme={null} // DEL await redis.del('key'); // EXISTS const exists = await redis.exists('key'); // EXPIRE await redis.expire('key', 60); // TTL const ttl = await redis.ttl('key'); // KEYS (use with caution in production) const keys = await redis.keys('user:*'); ``` ## Complete Example ```typescript theme={null} import { Redis } from '@upstash/redis'; // Initialize client const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); // Or use fromEnv const redis = Redis.fromEnv(); // Simple operations await redis.set('greeting', 'Hello, World!'); const greeting = await redis.get('greeting'); // Pipelining const pipeline = redis.pipeline(); pipeline.set('user:1', { name: 'Alice' }); pipeline.set('user:2', { name: 'Bob' }); pipeline.get('user:1'); const results = await pipeline.exec(); // Transactions const transaction = redis.multi(); transaction.set('balance', 100); transaction.incrby('balance', 50); transaction.get('balance'); const [setResult, incrResult, balance] = await transaction.exec(); // Scripts const myScript = redis.createScript( 'return redis.call("INCR", KEYS[1])' ); const counter = await myScript.exec(['mycounter'], []); ``` ## TypeScript Support The Redis client is fully typed with TypeScript generics: ```typescript theme={null} interface User { name: string; age: number; } // Type-safe operations await redis.set('user:1', { name: 'John', age: 30 }); const user = await redis.get('user:1'); // user is typed as User | null // JSON operations await redis.json.set('user:2', '$', { name: 'Jane', age: 25 }); const userData = await redis.json.get('user:2'); ``` # Script / ScriptRO Source: https://upstash-redis-js.mintlify.app/api/script Execute Lua scripts efficiently with automatic SHA-1 caching The `Script` and `ScriptRO` classes allow you to execute Lua scripts efficiently by using Redis's `EVALSHA` command, which caches scripts on the server for faster execution. ## Overview Scripts provide an optimized way to execute Lua code in Redis: 1. First execution attempts `EVALSHA` with the script's SHA-1 hash 2. If the script isn't cached, falls back to `EVAL` to load and execute it 3. Subsequent executions use the cached version via `EVALSHA` This approach minimizes bandwidth and improves performance for frequently used scripts. ## Creating Scripts ### Script (Read-Write) Create a script that can modify data: ```typescript theme={null} const script = redis.createScript( 'return ARGV[1];' ); ``` ### ScriptRO (Read-Only) Create a read-only script that uses `EVAL_RO` and `EVALSHA_RO`: ```typescript theme={null} const script = redis.createScript( 'return ARGV[1];', { readonly: true } ); ``` The Lua script to execute Script configuration options If true, returns a `ScriptRO` instance that uses read-only EVAL commands. Use this for scripts that only read data and don't modify it. A Script or ScriptRO instance based on the readonly option ## Script Class ### Properties #### script The Lua script source code. ```typescript theme={null} const myScript = redis.createScript('return ARGV[1];'); console.log(myScript.script); // 'return ARGV[1];' ``` The Lua script source code #### sha1 **Deprecated**: This property is initialized asynchronously and may be an empty string immediately after construction. It's exposed for backwards compatibility only. The SHA-1 hash of the script. ```typescript theme={null} const myScript = redis.createScript('return ARGV[1];'); // Wait for initialization before accessing await new Promise(resolve => setTimeout(resolve, 100)); console.log(myScript.sha1); // SHA-1 hash ``` The SHA-1 hash of the script (may be empty if not yet initialized) ### Methods #### eval() Execute the script using the `EVAL` command (sends full script to Redis). ```typescript theme={null} const script = redis.createScript( 'return redis.call("INCR", KEYS[1])' ); const result = await script.eval(['counter'], []); // result: 1 ``` Array of Redis keys that the script will access Array of additional arguments to pass to the script The script execution result #### evalsha() Execute the script using the `EVALSHA` command (uses cached SHA-1 hash). ```typescript theme={null} const script = redis.createScript( 'return "Hello, " .. ARGV[1]' ); // First load the script await redis.scriptLoad(script.script); // Then execute by SHA-1 const result = await script.evalsha([], ['World']); // result: 'Hello, World' ``` Array of Redis keys that the script will access Array of additional arguments to pass to the script The script execution result If the script is not cached on the server, this will throw a NOSCRIPT error. Use `exec()` for automatic fallback. #### exec() Optimistically execute the script with automatic fallback. 1. First attempts `EVALSHA` (fast, uses cached script) 2. If script not cached (NOSCRIPT error), falls back to `EVAL` 3. Subsequent calls use the cached version ```typescript theme={null} const script = redis.createScript( ` local count = redis.call("INCR", KEYS[1]) redis.call("EXPIRE", KEYS[1], ARGV[1]) return count ` ); // First call: uses EVAL (script not cached) const count1 = await script.exec(['page:views'], ['3600']); // Subsequent calls: uses EVALSHA (faster) const count2 = await script.exec(['page:views'], ['3600']); ``` Array of Redis keys that the script will access. In Lua, access via `KEYS[1]`, `KEYS[2]`, etc. Array of additional arguments. In Lua, access via `ARGV[1]`, `ARGV[2]`, etc. The script execution result ## ScriptRO Class The `ScriptRO` class is identical to `Script` but uses read-only commands (`EVAL_RO` and `EVALSHA_RO`). This is useful for scripts that only read data and can be executed on read replicas. ### Methods #### evalRo() Execute the script using `EVAL_RO`. ```typescript theme={null} const script = redis.createScript( 'return redis.call("LRANGE", KEYS[1], 0, -1)', { readonly: true } ); const items = await script.evalRo(['mylist'], []); ``` Array of Redis keys to read Array of additional arguments The script execution result #### evalshaRo() Execute the script using `EVALSHA_RO`. ```typescript theme={null} const script = redis.createScript( 'return redis.call("GET", KEYS[1])', { readonly: true } ); const value = await script.evalshaRo(['mykey'], []); ``` Array of Redis keys to read Array of additional arguments The script execution result #### exec() Execute with automatic fallback (tries `EVALSHA_RO`, falls back to `EVAL_RO`). ```typescript theme={null} const script = redis.createScript( 'return redis.call("GET", KEYS[1])', { readonly: true } ); const value = await script.exec(['mykey'], []); ``` Array of Redis keys to read Array of additional arguments The script execution result ## Usage Examples ### Basic Script Execution ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = Redis.fromEnv(); const greetingScript = redis.createScript( 'return "Hello, " .. ARGV[1] .. "!"' ); const message = await greetingScript.exec([], ['World']); // message: 'Hello, World!' ``` ### Atomic Counter with TTL ```typescript theme={null} const incrementCounter = redis.createScript( ` local count = redis.call("INCR", KEYS[1]) local ttl = redis.call("TTL", KEYS[1]) if ttl == -1 then redis.call("EXPIRE", KEYS[1], ARGV[1]) end return count ` ); // Increment counter and set 1 hour expiry if not set const count = await incrementCounter.exec( ['page:views:homepage'], ['3600'] ); ``` ### Conditional Operations ```typescript theme={null} const setIfHigher = redis.createScript( ` local current = redis.call("GET", KEYS[1]) local new = tonumber(ARGV[1]) if current == false or tonumber(current) < new then redis.call("SET", KEYS[1], new) return new end return tonumber(current) ` ); const highScore = await setIfHigher.exec( ['user:123:highscore'], ['1500'] ); ``` ### Read-Only Script for Analytics ```typescript theme={null} interface Stats { views: number; likes: number; ratio: number; } const getPostStats = redis.createScript( ` local views = tonumber(redis.call("GET", KEYS[1] .. ":views") or "0") local likes = tonumber(redis.call("GET", KEYS[1] .. ":likes") or "0") local ratio = 0 if views > 0 then ratio = likes / views end return {views, likes, ratio} `, { readonly: true } ); const stats = await getPostStats.exec(['post:123'], []); // stats: { views: 1000, likes: 150, ratio: 0.15 } ``` ### Batch Operations ```typescript theme={null} const batchIncrement = redis.createScript( ` local results = {} for i = 1, #KEYS do results[i] = redis.call("INCR", KEYS[i]) end return results ` ); const counters = await batchIncrement.exec( ['counter:1', 'counter:2', 'counter:3'], [] ); // counters: [1, 1, 1] ``` ### Rate Limiting ```typescript theme={null} const checkRateLimit = redis.createScript<[number, number]>( ` local key = KEYS[1] local limit = tonumber(ARGV[1]) local window = tonumber(ARGV[2]) local current = redis.call("INCR", key) if current == 1 then redis.call("EXPIRE", key, window) end local ttl = redis.call("TTL", key) return {current, ttl} ` ); const [requests, ttl] = await checkRateLimit.exec( ['ratelimit:user:123'], ['100', '60'] // 100 requests per 60 seconds ); if (requests > 100) { throw new Error(`Rate limit exceeded. Try again in ${ttl} seconds.`); } ``` ### Complex Data Structure Operations ```typescript theme={null} const updateUserActivity = redis.createScript( ` local userId = ARGV[1] local activity = ARGV[2] local timestamp = ARGV[3] -- Update last activity redis.call("HSET", "user:" .. userId, "last_activity", activity) redis.call("HSET", "user:" .. userId, "last_seen", timestamp) -- Add to activity log redis.call("ZADD", "user:" .. userId .. ":activity", timestamp, activity) -- Keep only last 100 activities redis.call("ZREMRANGEBYRANK", "user:" .. userId .. ":activity", 0, -101) ` ); await updateUserActivity.exec( [], ['123', 'logged_in', Date.now().toString()] ); ``` ## TypeScript Support Scripts support full TypeScript type inference: ```typescript theme={null} // Return type is inferred const script = redis.createScript<{ count: number; timestamp: number }>( ` local count = redis.call("INCR", KEYS[1]) local time = redis.call("TIME") return {count, time[1]} ` ); const result = await script.exec(['counter'], []); // result is typed as { count: number; timestamp: number } ``` ## Best Practices ### 1. Use TypeScript Generics ```typescript theme={null} interface ScriptResult { success: boolean; value: number; } const script = redis.createScript( '...' ); ``` ### 2. Prefer exec() Over eval() ```typescript theme={null} // Good: Automatic caching await script.exec(keys, args); // Avoid: Always sends full script await script.eval(keys, args); ``` ### 3. Use Read-Only Scripts When Possible ```typescript theme={null} // For read-only operations const readScript = redis.createScript( 'return redis.call("GET", KEYS[1])', { readonly: true } ); ``` ### 4. Handle Errors Properly ```typescript theme={null} try { const result = await script.exec(keys, args); } catch (error) { if (error instanceof Error) { console.error('Script execution failed:', error.message); } } ``` ### 5. Document Complex Scripts ```typescript theme={null} /** * Atomically increment a counter and maintain top N leaderboard * * @param KEYS[1] - Counter key * @param KEYS[2] - Sorted set key for leaderboard * @param ARGV[1] - User ID * @param ARGV[2] - Maximum leaderboard size */ const updateLeaderboard = redis.createScript( `...` ); ``` # Auto-pipeline Source: https://upstash-redis-js.mintlify.app/concepts/auto-pipeline Automatically batch Redis commands for optimal performance without manual pipeline management ## Overview Auto-pipelining automatically batches multiple Redis commands into a single HTTP request, providing the performance benefits of [manual pipelining](/concepts/pipeline) without requiring explicit pipeline management. ## How it works When auto-pipelining is enabled (the default), commands issued in the same event loop tick are automatically batched: ```typescript theme={null} import { Redis } from "@upstash/redis"; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, enableAutoPipelining: true, // enabled by default }); // These three commands execute in the same tick // They're automatically batched into a single HTTP request const promise1 = redis.set("key1", "value1"); const promise2 = redis.set("key2", "value2"); const promise3 = redis.get("key1"); // Wait for all results const [result1, result2, result3] = await Promise.all([promise1, promise2, promise3]); console.log(result1); // "OK" console.log(result2); // "OK" console.log(result3); // "value1" ``` Internally, the SDK: 1. Collects all commands issued in the current tick 2. Waits for the next tick (using `Promise.resolve().then()`) 3. Sends all collected commands in a single batch 4. Returns individual results to each promise ## Enabling auto-pipelining Auto-pipelining is enabled by default. To explicitly enable or disable: ```typescript theme={null} // Enabled (default) const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, enableAutoPipelining: true, }); // Disabled const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, enableAutoPipelining: false, }); ``` When disabled, each command executes as an individual HTTP request. ## Usage patterns ### Parallel operations Execute multiple independent commands efficiently: ```typescript theme={null} // All three commands are batched automatically const [user, settings, posts] = await Promise.all([ redis.get("user:123"), redis.hgetall("user:123:settings"), redis.lrange("user:123:posts", 0, 10), ]); ``` ### Sequential with batching Issue commands sequentially in the same tick: ```typescript theme={null} async function updateUserStats(userId: string) { // All issued in the same tick, automatically batched const incrPromise = redis.incr(`user:${userId}:visits`); const setPromise = redis.set(`user:${userId}:last-seen`, Date.now()); const saddPromise = redis.sadd("active-users", userId); // Wait for all to complete await Promise.all([incrPromise, setPromise, saddPromise]); } ``` ### JSON operations JSON commands are also auto-pipelined: ```typescript theme={null} // Batched automatically const [setResult, getName, getScores] = await Promise.all([ redis.json.set("user:1", "$", { name: "Alice", scores: [10, 20, 30] }), redis.json.get("user:1", "$.name"), redis.json.get("user:1", "$.scores"), ]); ``` ## When commands are batched Commands are batched when issued in the same event loop tick: ```typescript theme={null} // ✅ Batched - same tick const p1 = redis.set("a", 1); const p2 = redis.set("b", 2); await Promise.all([p1, p2]); // ❌ Not batched - different ticks await redis.set("a", 1); await redis.set("b", 2); // ✅ Batched - both issued before awaiting const p3 = redis.set("c", 3); await somethingElse(); // Some other async operation const p4 = redis.set("d", 4); await Promise.all([p3, p4]); // Still batched if in same tick ``` ## Performance comparison ### Without auto-pipelining ```typescript theme={null} const redis = new Redis({ url, token, enableAutoPipelining: false, }); // 3 separate HTTP requests await redis.set("key1", "value1"); // Request 1: ~50ms await redis.set("key2", "value2"); // Request 2: ~50ms await redis.get("key1"); // Request 3: ~50ms // Total: ~150ms ``` ### With auto-pipelining ```typescript theme={null} const redis = new Redis({ url, token, enableAutoPipelining: true, // default }); // 1 batched HTTP request const p1 = redis.set("key1", "value1"); const p2 = redis.set("key2", "value2"); const p3 = redis.get("key1"); await Promise.all([p1, p2, p3]); // Total: ~50ms ``` ## Monitoring pipeline batches Track how many pipeline batches have been executed: ```typescript theme={null} const redis = new Redis({ url, token, enableAutoPipelining: true, }) as Redis & { pipelineCounter: number }; // Issue some commands const p1 = redis.set("a", 1); const p2 = redis.set("b", 2); await Promise.all([p1, p2]); console.log(redis.pipelineCounter); // 1 // Issue more commands const p3 = redis.set("c", 3); const p4 = redis.set("d", 4); await Promise.all([p3, p4]); console.log(redis.pipelineCounter); // 2 ``` ## Error handling Each command's promise resolves or rejects individually: ```typescript theme={null} const p1 = redis.set("key1", "value1"); const p2 = redis.incr("key1"); // Will fail: not an integer const p3 = redis.get("key2"); try { await p1; // Succeeds console.log("Set succeeded"); } catch (error) { console.error("Set failed", error); } try { await p2; // Fails console.log("Incr succeeded"); } catch (error) { console.error("Incr failed", error); // This will be caught } try { await p3; // Succeeds console.log("Get succeeded"); } catch (error) { console.error("Get failed", error); } ``` ## Excluded commands Some commands cannot be auto-pipelined and always execute individually: * `scan`, `hscan`, `sscan`, `zscan` - Cursor-based iteration * `keys` - Returns all keys * `hgetall`, `hkeys` - May return large datasets * `lrange` - May return large ranges * `smembers` - Returns all set members * `zrange` - May return large ranges * `xrange`, `xrevrange` - Stream ranges * `flushdb`, `flushall` - Destructive operations * `dbsize` - Database-wide operation * `exec` - Transaction execution These commands execute as individual requests regardless of auto-pipelining settings. ## Auto-pipeline vs manual pipeline | Feature | Auto-pipeline | Manual pipeline | | -------------- | ------------------- | ---------------------- | | Setup | Automatic | Explicit `.pipeline()` | | Syntax | Natural | Chained or sequential | | Type inference | Full | Full (when chained) | | Control | Implicit batching | Explicit batching | | Error handling | Individual promises | Array or keepErrors | | Use case | General development | Fine-grained control | ### When to use auto-pipeline * Default choice for most applications * Serverless functions with multiple commands * Simplified code without manual pipeline management * When batching can happen naturally via `Promise.all()` ### When to use manual pipeline * Need explicit control over batch boundaries * Want to see exactly which commands are batched * Building complex batch operations * Need transaction semantics with `multi()` ## Real-world example Serverless function with auto-pipelining: ```typescript theme={null} import { Redis } from "@upstash/redis"; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, enableAutoPipelining: true, }); export async function handler(event: { userId: string; action: string }) { const { userId, action } = event; // All these commands are automatically batched into a single request const incrementPromise = redis.incr(`user:${userId}:actions`); const timestampPromise = redis.set(`user:${userId}:last-action`, Date.now()); const logPromise = redis.lpush(`user:${userId}:log`, action); const addActivePromise = redis.sadd("active-users", userId); // Wait for all operations await Promise.all([ incrementPromise, timestampPromise, logPromise, addActivePromise, ]); return { success: true }; } // Total latency: 1 HTTP request instead of 4 ``` ## Best practices 1. **Issue commands before awaiting**: Let the SDK collect multiple commands ```typescript theme={null} // Good const p1 = redis.set("a", 1); const p2 = redis.set("b", 2); await Promise.all([p1, p2]); // Bad - not batched await redis.set("a", 1); await redis.set("b", 2); ``` 2. **Use Promise.all() for parallel operations** ```typescript theme={null} const results = await Promise.all([ redis.get("key1"), redis.get("key2"), redis.get("key3"), ]); ``` 3. **Handle errors individually** ```typescript theme={null} const promises = [ redis.set("a", 1).catch(e => ({ error: e })), redis.set("b", 2).catch(e => ({ error: e })), ]; const results = await Promise.all(promises); ``` 4. **Monitor pipeline efficiency in development** ```typescript theme={null} if (process.env.NODE_ENV === "development") { const redis = new Redis({ url, token }) as Redis & { pipelineCounter: number }; // Log pipeline count periodically } ``` # Client configuration Source: https://upstash-redis-js.mintlify.app/concepts/client-configuration Configure the Upstash Redis client with connection credentials, retry behavior, and advanced options ## Overview The Upstash Redis client provides several configuration options to customize behavior for your environment and use case. Configuration varies slightly between platforms, but all platforms share common options. ## Basic configuration Create a Redis client with your Upstash credentials: ```typescript theme={null} import { Redis } from "@upstash/redis"; const redis = new Redis({ url: "", token: "", }); ``` Get your credentials from the [Upstash Console](https://console.upstash.com/redis) under your database details. ## Configuration from environment variables Load credentials automatically from environment variables: ```typescript theme={null} import { Redis } from "@upstash/redis"; const redis = Redis.fromEnv(); ``` This method reads from: * `UPSTASH_REDIS_REST_URL` or `KV_REST_API_URL` for the URL * `UPSTASH_REDIS_REST_TOKEN` or `KV_REST_API_TOKEN` for the token The `KV_*` variables provide compatibility with Vercel KV and other platforms that use different naming conventions. ## Configuration options ### Connection credentials The REST URL for your Upstash Redis database. Find this in the Upstash Console under `UPSTASH_REDIS_REST_URL`. The authentication token for your database. Find this in the Upstash Console under `UPSTASH_REDIS_REST_TOKEN`. ### HTTP and retry options Configure retry behavior for network errors. ```typescript theme={null} const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, retry: { retries: 3, backoff: (retryCount) => Math.exp(retryCount) * 100, }, }); ``` Set to `false` to disable retries: ```typescript theme={null} const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, retry: false, }); ``` **RetryConfig properties:** * `retries` (number, default: 5): Maximum number of retry attempts * `backoff` (function): Function that returns wait time in milliseconds given the retry count Controls how response data is encoded for transfer. By default, the server base64-encodes responses to safely handle non-UTF8 data like emojis. For very large responses with guaranteed UTF8 data, you can disable this: ```typescript theme={null} const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, responseEncoding: false, }); ``` Only disable base64 encoding if you're certain your data is valid UTF8. Invalid data will cause parsing errors. Configure HTTP cache behavior. Options: `"default"`, `"force-cache"`, `"no-cache"`, `"no-store"`, `"only-if-cached"`, `"reload"`. ```typescript theme={null} const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, cache: "no-store", }); ``` Provide an AbortSignal to cancel in-flight requests. ```typescript theme={null} const controller = new AbortController(); const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, signal: controller.signal, }); // Cancel all future requests controller.abort(); ``` ### Redis client options Automatically deserialize JSON responses from Redis using `JSON.parse`. ```typescript theme={null} const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, automaticDeserialization: true, }); await redis.set("user", { name: "Alice", age: 30 }); const user = await redis.get("user"); // Returns { name: "Alice", age: 30 } ``` Enable automatic command pipelining for improved performance. See [auto-pipeline](/concepts/auto-pipeline) for details. ```typescript theme={null} const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, enableAutoPipelining: true, }); ``` Ensure read-your-writes consistency. See [read-your-writes](/concepts/read-your-writes) for details. ```typescript theme={null} const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, readYourWrites: true, }); ``` Log the latency of each command to the console for debugging. ```typescript theme={null} const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, latencyLogging: true, }); await redis.get("key"); // Console output: Latency for GET: 45.23 ms ``` Send anonymous telemetry data to help improve the SDK. Collected data includes SDK version, platform, and runtime version. Disable telemetry: ```typescript theme={null} const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, enableTelemetry: false, }); ``` Or set the `UPSTASH_DISABLE_TELEMETRY` environment variable: ```bash theme={null} UPSTASH_DISABLE_TELEMETRY=1 ``` ### Node.js specific options Provide a custom HTTP agent for connection reuse (Node.js only). ```typescript theme={null} import https from "https"; import { Redis } from "@upstash/redis"; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, agent: new https.Agent({ keepAlive: true }), }); ``` This option is not supported in edge runtimes like Vercel Edge Functions or Cloudflare Workers. Enable HTTP keep-alive for connection reuse. ```typescript theme={null} const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, keepAlive: true, }); ``` ## Platform-specific imports ### Cloudflare Workers Import from the Cloudflare-specific entrypoint: ```typescript theme={null} import { Redis } from "@upstash/redis/cloudflare"; export default { async fetch(request: Request, env: Env) { const redis = new Redis({ url: env.UPSTASH_REDIS_REST_URL, token: env.UPSTASH_REDIS_REST_TOKEN, }); // Or use fromEnv with module workers const redis2 = Redis.fromEnv(env); // Use redis... }, }; ``` ### Node.js Use the standard import: ```typescript theme={null} import { Redis } from "@upstash/redis"; ``` ## Advanced: custom requester For advanced use cases, provide a custom `Requester` implementation: ```typescript theme={null} import { Redis, Requester, UpstashRequest, UpstashResponse } from "@upstash/redis"; const customRequester: Requester = { request: async (req: UpstashRequest): Promise> => { // Custom request implementation // ... }, }; const redis = new Redis(customRequester); ``` # Connection model Source: https://upstash-redis-js.mintlify.app/concepts/connection Understand the HTTP-based connectionless architecture and how it differs from traditional TCP connections ## Overview Upstash Redis uses an HTTP/REST-based connection model instead of traditional TCP connections. This fundamental difference makes it ideal for serverless and edge environments. ## HTTP-based architecture Unlike traditional Redis clients that maintain persistent TCP connections, the Upstash Redis SDK communicates with Redis over HTTP: ```typescript theme={null} import { Redis } from "@upstash/redis"; const redis = new Redis({ url: "https://your-redis.upstash.io", // HTTP endpoint token: "your-token", }); // Each command is an independent HTTP request await redis.set("key", "value"); // POST https://your-redis.upstash.io await redis.get("key"); // POST https://your-redis.upstash.io ``` ## Why HTTP instead of TCP? The connectionless HTTP model provides several advantages for modern applications: ### No connection management You don't need to worry about connection pooling, timeouts, or reconnection logic: ```typescript theme={null} // No connection setup required const redis = new Redis({ url, token }); // Immediately ready to use await redis.get("key"); // No cleanup or connection closing needed ``` ### Perfect for serverless Serverless functions have short lifecycles and unpredictable scaling. HTTP connections work seamlessly: ```typescript theme={null} // AWS Lambda handler export const handler = async (event) => { const redis = new Redis({ url, token }); // No connection overhead const value = await redis.get("counter"); return { statusCode: 200, body: value }; // No cleanup needed - function terminates }; ``` **Benefits for serverless:** * **No cold start overhead**: No connection establishment delay * **No lingering connections**: No idle connections consuming resources * **Infinite scaling**: Each invocation is independent * **No connection limits**: Not constrained by connection pool size ### Edge runtime compatible Many edge runtimes (Cloudflare Workers, Vercel Edge, Deno Deploy) don't support TCP connections. HTTP works everywhere: ```typescript theme={null} // Cloudflare Workers import { Redis } from "@upstash/redis/cloudflare"; export default { async fetch(request: Request, env: Env) { const redis = Redis.fromEnv(env); await redis.incr("requests"); return new Response("OK"); }, }; ``` ### Globally distributed HTTP requests can be routed through CDNs and edge networks for lower latency: ```typescript theme={null} // Automatically routed to the nearest edge location const redis = new Redis({ url, token }); await redis.get("user:123"); ``` ## How it works Each Redis command becomes an HTTP POST request: ### Single command ```typescript theme={null} await redis.set("greeting", "hello"); ``` Translates to: ```http theme={null} POST https://your-redis.upstash.io Authorization: Bearer YOUR_TOKEN Content-Type: application/json ["SET", "greeting", "hello"] ``` ### Response format ```typescript theme={null} const value = await redis.get("greeting"); // Returns: "hello" ``` HTTP response: ```json theme={null} { "result": "aGVsbG8=" // base64 encoded by default } ``` By default, responses are base64-encoded to safely handle non-UTF8 data. The SDK automatically decodes them. See [client configuration](/concepts/client-configuration) to customize this. ## Batch operations with pipelining To reduce latency when executing multiple commands, use pipelining to send them in a single HTTP request: ```typescript theme={null} // Manual pipeline - single HTTP request const pipeline = redis.pipeline(); pipeline.set("key1", "value1"); pipeline.set("key2", "value2"); pipeline.get("key1"); const results = await pipeline.exec(); // ["OK", "OK", "value1"] ``` See [pipeline](/concepts/pipeline) and [auto-pipeline](/concepts/auto-pipeline) for details. ## Performance considerations ### Latency HTTP adds some overhead compared to raw TCP, but this is minimal: * **Single commands**: \~1-2ms additional latency * **Pipelined commands**: Overhead amortized across all commands * **Global read regions**: Can reduce latency significantly for reads ### Optimization strategies **Use pipelining for multiple commands:** ```typescript theme={null} // Bad - 3 HTTP requests await redis.incr("counter"); await redis.incr("requests"); await redis.set("timestamp", Date.now()); // Good - 1 HTTP request const p = redis.pipeline(); p.incr("counter"); p.incr("requests"); p.set("timestamp", Date.now()); await p.exec(); ``` **Enable auto-pipelining:** ```typescript theme={null} const redis = new Redis({ url, token, enableAutoPipelining: true, // default }); // These are automatically batched into a single request await redis.incr("counter"); await redis.incr("requests"); await redis.set("timestamp", Date.now()); ``` **Use keep-alive for connection reuse:** ```typescript theme={null} const redis = new Redis({ url, token, keepAlive: true, // default }); ``` ## Comparison with TCP Redis clients | Feature | HTTP (Upstash) | TCP (Traditional) | | -------------------- | -------------- | ---------------------- | | Connection setup | None | Required | | Serverless friendly | Yes | No | | Edge runtime support | Yes | Limited | | Connection pooling | Not needed | Required | | Cold start overhead | None | Significant | | Horizontal scaling | Unlimited | Limited by connections | | Latency (single cmd) | \~1-2ms higher | Lower | | Latency (pipelined) | Comparable | Comparable | | State management | Stateless | Stateful | ## Authentication HTTP connections use Bearer token authentication: ```typescript theme={null} const redis = new Redis({ url: "https://your-redis.upstash.io", token: "your-secret-token", // Sent as Authorization: Bearer header }); ``` Never expose your token in client-side code. The token provides full access to your database. ## Request timeout and cancellation Use `AbortSignal` to cancel long-running requests: ```typescript theme={null} const controller = new AbortController(); const redis = new Redis({ url, token, signal: controller.signal, }); // Cancel after 5 seconds setTimeout(() => controller.abort(), 5000); try { await redis.get("key"); } catch (error) { // Request was aborted } ``` ## Error handling HTTP errors are transparently handled: ```typescript theme={null} try { await redis.get("key"); } catch (error) { // Network errors, auth failures, etc. console.error(error); } ``` The SDK automatically retries on transient failures. See [client configuration](/concepts/client-configuration) for retry options. # Pipeline Source: https://upstash-redis-js.mintlify.app/concepts/pipeline Batch multiple Redis commands into a single HTTP request for improved performance ## Overview Pipelining allows you to send multiple Redis commands in a single HTTP request instead of making separate requests for each command. This dramatically reduces network latency when executing multiple operations. ## Why use pipelines? Without pipelining, each command requires a separate HTTP round-trip: ```typescript theme={null} // 3 separate HTTP requests - slow await redis.set("key1", "value1"); // Request 1 await redis.set("key2", "value2"); // Request 2 await redis.get("key1"); // Request 3 ``` With pipelining, all commands are sent together: ```typescript theme={null} // 1 HTTP request - fast const pipeline = redis.pipeline(); pipeline.set("key1", "value1"); pipeline.set("key2", "value2"); pipeline.get("key1"); const results = await pipeline.exec(); // ["OK", "OK", "value1"] ``` ## Basic usage Create a pipeline, add commands, and execute: ```typescript theme={null} import { Redis } from "@upstash/redis"; const redis = new Redis({ url, token }); // Create pipeline const pipeline = redis.pipeline(); // Add commands pipeline.set("user:1:name", "Alice"); pipeline.set("user:1:email", "alice@example.com"); pipeline.set("user:1:age", 30); pipeline.get("user:1:name"); // Execute all commands const results = await pipeline.exec(); console.log(results); // ["OK", "OK", "OK", "Alice"] ``` ## Chaining syntax You can chain commands for a more concise syntax: ```typescript theme={null} const results = await redis.pipeline() .set("key1", "value1") .set("key2", "value2") .get("key1") .incr("counter") .exec(); console.log(results); // ["OK", "OK", "value1", 1] ``` ## Type inference When you chain all commands, TypeScript automatically infers the return types: ```typescript theme={null} const results = await redis.pipeline() .set("greeting", { message: "hello" }) .get<{ message: string }>("greeting") .exec(); // results is typed as ["OK", { message: string }] const greeting = results[1]; console.log(greeting.message); // TypeScript knows this is a string ``` For manual type specification: ```typescript theme={null} const p = redis.pipeline(); p.set("key", { greeting: "hello" }); p.get("key"); const results = await p.exec<["OK", { greeting: string }]>(); const data = results[1]; console.log(data.greeting); // TypeScript knows the structure ``` ## All Redis commands supported Pipelines support all Redis commands: ```typescript theme={null} const results = await redis.pipeline() // Strings .set("key", "value") .get("key") .incr("counter") // Hashes .hset("user:1", { name: "Alice", age: 30 }) .hget("user:1", "name") // Lists .lpush("queue", "task1") .lpush("queue", "task2") .lrange("queue", 0, -1) // Sets .sadd("tags", "redis") .sadd("tags", "database") .smembers("tags") // Sorted sets .zadd("leaderboard", { score: 100, member: "player1" }) .zrange("leaderboard", 0, 10) .exec(); ``` ## JSON commands in pipelines JSON commands work seamlessly in pipelines: ```typescript theme={null} const results = await redis.pipeline() .json.set("user:1", "$", { name: "Alice", scores: [10, 20, 30] }) .json.get("user:1", "$.name") .json.arrappend("user:1", "$.scores", 40) .json.get("user:1") .exec(); console.log(results); // ["OK", ["Alice"], 4, { name: "Alice", scores: [10, 20, 30, 40] }] ``` ## Error handling By default, if any command fails, the entire pipeline throws an error: ```typescript theme={null} try { const results = await redis.pipeline() .set("key1", "value1") .incr("key1") // Error: not an integer .get("key2") .exec(); } catch (error) { console.error("Pipeline failed:", error); // Error: Command 2 [ INCR ] failed: ... } ``` ### Keep errors To get individual errors for each command, use `keepErrors`: ```typescript theme={null} const results = await redis.pipeline() .set("key1", "value1") .incr("key1") // This will error .get("key2") .exec({ keepErrors: true }); // Check each result for (const [index, result] of results.entries()) { if (result.error) { console.error(`Command ${index} failed:`, result.error); } else { console.log(`Command ${index} result:`, result.result); } } // Access specific results const setResult = results[0]; if (!setResult.error) { console.log(setResult.result); // "OK" } const incrResult = results[1]; if (incrResult.error) { console.error(incrResult.error); // Error message } ``` ## Pipeline length Get the number of commands in a pipeline before execution: ```typescript theme={null} const pipeline = redis.pipeline(); pipeline.set("key1", "value1"); pipeline.set("key2", "value2"); pipeline.get("key1"); console.log(pipeline.length()); // 3 await pipeline.exec(); ``` ## Multi-exec (transactions) For atomic execution of commands, use `multi()` instead of `pipeline()`: ```typescript theme={null} const results = await redis.multi() .set("balance", 100) .decrby("balance", 30) .get("balance") .exec(); console.log(results); // ["OK", 70, 70] ``` `multi()` wraps commands in Redis `MULTI`/`EXEC`, guaranteeing atomic execution. Regular `pipeline()` commands may interleave with other clients' commands. ## Important notes ### Non-atomic execution Pipeline commands are **not atomic**. Other clients can execute commands between your pipelined commands: ```typescript theme={null} // These commands execute in order, but are NOT atomic const results = await redis.pipeline() .get("counter") // Returns 10 .incr("counter") // Another client might increment here .get("counter") // Might not be 11 if another client modified it .exec(); ``` Use `multi()` for atomic transactions. ### Excluded commands Some commands that return large amounts of data or require iteration cannot be pipelined: * `scan`, `hscan`, `sscan`, `zscan` - Cursor-based iteration * `keys` - Returns all keys * `hgetall`, `hkeys`, `lrange` - May return large datasets * `smembers` - Returns all set members * `zrange` - May return large ranges Use these commands individually outside of pipelines. ## Performance benefits Pipelining reduces latency by eliminating network round-trips: ```typescript theme={null} // Without pipeline: 3 commands × 50ms = 150ms total await redis.set("a", 1); await redis.set("b", 2); await redis.set("c", 3); // With pipeline: 1 request = ~50ms total await redis.pipeline() .set("a", 1) .set("b", 2) .set("c", 3) .exec(); ``` **When to use pipelines:** * Executing multiple independent commands * Batch operations (bulk writes, bulk reads) * Reducing latency in serverless environments * Optimizing high-throughput scenarios **When to use individual commands:** * Commands depend on previous results * Executing only one or two commands * Commands that return large datasets ## Advanced example Batch user creation: ```typescript theme={null} async function createUsers(users: Array<{ id: string; name: string; email: string }>) { const pipeline = redis.pipeline(); for (const user of users) { pipeline.hset(`user:${user.id}`, { name: user.name, email: user.email, createdAt: Date.now(), }); pipeline.sadd("users:all", user.id); pipeline.zadd("users:by-creation", { score: Date.now(), member: user.id, }); } const results = await pipeline.exec(); console.log(`Created ${users.length} users in a single request`); return results; } await createUsers([ { id: "1", name: "Alice", email: "alice@example.com" }, { id: "2", name: "Bob", email: "bob@example.com" }, { id: "3", name: "Charlie", email: "charlie@example.com" }, ]); ``` # Read-your-writes consistency Source: https://upstash-redis-js.mintlify.app/concepts/read-your-writes Ensure your Redis reads reflect your own writes with Upstash's read-your-writes consistency model ## Overview Read-your-writes consistency guarantees that any read operation from a client will always reflect the effects of all previous write operations from that same client. This is especially important in globally distributed databases where replication lag can cause stale reads. ## The problem: eventual consistency Upstash Redis supports global read replicas for low-latency reads worldwide. However, replication takes time: ```typescript theme={null} // Without read-your-writes const redis = new Redis({ url, token, readYourWrites: false }); await redis.set("user:123:name", "Alice"); // This might return null or old value if reading from a replica // that hasn't replicated the write yet const name = await redis.get("user:123:name"); // Potentially: null (stale read) ``` This can lead to confusing application behavior: 1. User updates their profile name to "Alice" 2. Application writes to primary: `SET user:123:name Alice` 3. Application immediately reads from replica: `GET user:123:name` 4. Replica hasn't replicated yet, returns old value: "Bob" 5. User sees their old name despite just updating it ## The solution: sync tokens Upstash implements read-your-writes using sync tokens. When enabled (the default), each write response includes a sync token that represents the replication state: ```typescript theme={null} const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, readYourWrites: true, // enabled by default }); await redis.set("user:123:name", "Alice"); // Response includes: upstash-sync-token: abc123 const name = await redis.get("user:123:name"); // Request includes: upstash-sync-token: abc123 // Server ensures replica has replicated at least to abc123 // Returns: "Alice" (guaranteed fresh) ``` ## How it works 1. **Client tracks sync token**: The SDK maintains a `upstashSyncToken` that's updated after each write 2. **Write operations**: Server includes current sync token in response headers 3. **Read operations**: Client sends latest sync token in request headers 4. **Server waits**: If reading from replica, server waits until replica has caught up to the sync token 5. **Consistent read**: Client receives data reflecting all previous writes ## Enabling read-your-writes Read-your-writes is enabled by default: ```typescript theme={null} // Explicitly enabled (default behavior) const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, readYourWrites: true, }); ``` To disable: ```typescript theme={null} const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, readYourWrites: false, }); ``` Disabling read-your-writes can provide lower latency for read-heavy workloads where stale reads are acceptable. ## Using sync tokens ### Automatic sync token management The SDK automatically manages sync tokens: ```typescript theme={null} const redis = new Redis({ url, token, readYourWrites: true }); // Write - sync token updated automatically await redis.set("counter", 0); // Read - sync token sent automatically const value = await redis.get("counter"); // Guaranteed to see 0 // Another write - sync token updated await redis.incr("counter"); // Read reflects the increment const newValue = await redis.get("counter"); // Guaranteed to see 1 ``` ### Manual sync token management For advanced use cases, you can manually access and set sync tokens: ```typescript theme={null} const redis = new Redis({ url, token, readYourWrites: true }); await redis.set("key", "value"); // Get current sync token const syncToken = redis.readYourWritesSyncToken; console.log(syncToken); // "abc123..." // Save sync token (e.g., to session, database, etc.) await saveToSession(userId, syncToken); // Later, restore sync token in a new request const redis2 = new Redis({ url, token, readYourWrites: true }); const savedToken = await loadFromSession(userId); redis2.readYourWritesSyncToken = savedToken; // This read will reflect all writes from the previous session const value = await redis2.get("key"); ``` ## Cross-request consistency In serverless environments, each request typically creates a new Redis client. To maintain consistency across requests: ### Using session storage ```typescript theme={null} import { Redis } from "@upstash/redis"; export async function handler(request: Request) { const redis = new Redis({ url, token, readYourWrites: true }); const session = await getSession(request); // Restore sync token from previous request if (session.syncToken) { redis.readYourWritesSyncToken = session.syncToken; } // Perform operations await redis.set("user:profile", { name: "Alice" }); const profile = await redis.get("user:profile"); // Consistent read // Save sync token for next request session.syncToken = redis.readYourWritesSyncToken; await saveSession(session); return new Response(JSON.stringify(profile)); } ``` ### Using cookies ```typescript theme={null} import { Redis } from "@upstash/redis"; export async function handler(request: Request) { const redis = new Redis({ url, token, readYourWrites: true }); // Restore from cookie const syncToken = request.headers.get("cookie") ?.match(/sync-token=([^;]+)/)?.[1]; if (syncToken) { redis.readYourWritesSyncToken = decodeURIComponent(syncToken); } // Perform operations await redis.incr("visits"); const visits = await redis.get("visits"); // Return sync token in cookie return new Response(JSON.stringify({ visits }), { headers: { "Set-Cookie": `sync-token=${encodeURIComponent(redis.readYourWritesSyncToken!)}; Path=/; HttpOnly`, }, }); } ``` ## Performance considerations ### Latency impact Read-your-writes can add latency to reads if replicas are behind: ```typescript theme={null} // With read-your-writes (may wait for replication) const start = Date.now(); const value = await redis.get("key"); const latency = Date.now() - start; // Latency: 10-100ms (depends on replication lag) // Without read-your-writes (immediate read from nearest replica) const redis2 = new Redis({ url, token, readYourWrites: false }); const start2 = Date.now(); const value2 = await redis2.get("key"); const latency2 = Date.now() - start2; // Latency: 5-20ms (faster, but may be stale) ``` ### When to disable Consider disabling read-your-writes for: * **Analytics and metrics**: Stale data is acceptable * **Caching scenarios**: Eventual consistency is fine * **Read-heavy workloads**: Lower latency is more important than consistency * **Public data**: Data that doesn't change frequently ```typescript theme={null} const redis = new Redis({ url, token, readYourWrites: false, // Accept stale reads for lower latency }); // These reads are fast but may be slightly stale const pageViews = await redis.get("stats:page-views"); const popularPosts = await redis.zrange("posts:popular", 0, 10); ``` ### When to keep enabled * **User-facing data**: Profile updates, settings, etc. * **After write operations**: Immediately reading what you just wrote * **Financial operations**: Ensuring consistency is critical * **Default choice**: When in doubt, keep it enabled ## Consistency guarantees ### What read-your-writes guarantees ✅ Your reads reflect your own writes ```typescript theme={null} await redis.set("key", "value"); const result = await redis.get("key"); // Guaranteed: result === "value" ``` ✅ Monotonic reads (won't see older data after newer data) ```typescript theme={null} await redis.set("counter", 1); const value1 = await redis.get("counter"); // 1 await redis.set("counter", 2); const value2 = await redis.get("counter"); // 2 (never goes back to 1) ``` ### What it doesn't guarantee ❌ Reading other clients' writes immediately ```typescript theme={null} // Client A await redisA.set("shared-key", "from-A"); // Client B (different instance, different sync token) const value = await redisB.get("shared-key"); // May still see old value - read-your-writes only applies to same client ``` ❌ Strong consistency across all clients ```typescript theme={null} // This is eventual consistency, not strong consistency // Other clients may see different values temporarily ``` ## Best practices 1. **Keep read-your-writes enabled by default** ```typescript theme={null} const redis = new Redis({ url, token, readYourWrites: true, // default, recommended }); ``` 2. **Persist sync tokens for cross-request consistency** ```typescript theme={null} // Save to session, cookie, or database session.syncToken = redis.readYourWritesSyncToken; ``` 3. **Disable only when appropriate** ```typescript theme={null} // Only for analytics, caching, or read-heavy public data const redis = new Redis({ url, token, readYourWrites: false }); ``` 4. **Don't share Redis instances across users** ```typescript theme={null} // Bad - sync tokens from different users mix const globalRedis = new Redis({ url, token }); // Good - each request gets its own instance app.get("/api/user", async (req) => { const redis = new Redis({ url, token }); // ... }); ``` ## Example: user profile update ```typescript theme={null} import { Redis } from "@upstash/redis"; // User updates profile export async function updateProfile(userId: string, name: string) { const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, readYourWrites: true, // Ensure user sees their update }); // Write profile await redis.hset(`user:${userId}`, { name, updatedAt: Date.now() }); // Immediately read back - guaranteed to see the update const profile = await redis.hgetall(`user:${userId}`); return profile; // User sees their updated name immediately } // User views profile in another request export async function getProfile(userId: string, syncToken?: string) { const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, readYourWrites: true, }); // Restore sync token from previous request if (syncToken) { redis.readYourWritesSyncToken = syncToken; } // Read profile - reflects all previous writes from this user's session const profile = await redis.hgetall(`user:${userId}`); return { profile, syncToken: redis.readYourWritesSyncToken, // Return for next request }; } ``` # AWS Lambda Source: https://upstash-redis-js.mintlify.app/examples/aws-lambda Deploy Upstash Redis with AWS Lambda functions This guide demonstrates how to use Upstash Redis in AWS Lambda functions. The HTTP-based connection makes it perfect for serverless environments where traditional TCP connections are problematic. ## Why Upstash Redis for Lambda? * **Connectionless**: HTTP-based, no connection pooling needed * **Fast cold starts**: No connection overhead * **Pay-per-request**: Aligned with Lambda's pricing model * **Global**: Low latency from any AWS region ## Prerequisites Sign up for an [AWS account](https://aws.amazon.com/) if you don't have one [Set up and configure AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html) with your credentials Create a Redis database using [Upstash Console](https://console.upstash.com) or [Upstash CLI](https://github.com/upstash/cli) ## Quick Start ### Project Setup ```bash theme={null} mkdir my-lambda-function cd my-lambda-function ``` ```bash theme={null} npm init -y npm install @upstash/redis ``` Create `index.js` with your Lambda handler code ## Example: Counter Function This example implements a simple counter that increments on each Lambda invocation. ```javascript index.js theme={null} const { Redis } = require('@upstash/redis'); const redis = Redis.fromEnv(); exports.handler = async (event) => { const count = await redis.incr("counter"); return { statusCode: 200, body: JSON.stringify('Counter: ' + count), }; }; ``` ```json package.json theme={null} { "dependencies": { "@upstash/redis": "latest" } } ``` ## Deployment ### Create Deployment Package ```bash theme={null} npm install ``` ```bash theme={null} zip -r my_deployment_package.zip . ``` This creates a ZIP file containing your code and `node_modules`. ### Deploy with AWS CLI ```bash theme={null} aws lambda create-function --function-name counterFunction \ --runtime nodejs20.x --handler index.handler \ --role \ --zip-file fileb://my_deployment_package.zip \ --region us-east-1 \ --environment "Variables={UPSTASH_REDIS_REST_URL=,UPSTASH_REDIS_REST_TOKEN=}" ``` Replace `` with your Lambda execution role ARN. This role must have basic Lambda execution permissions. ### Get Your Upstash Credentials From your [Upstash Console](https://console.upstash.com): 1. Select your Redis database 2. Click on the **REST API** tab 3. Copy the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` ## Advanced Examples ### Session Storage ```javascript theme={null} const { Redis } = require('@upstash/redis'); const redis = Redis.fromEnv(); exports.handler = async (event) => { const sessionId = event.headers['session-id']; if (!sessionId) { return { statusCode: 400, body: JSON.stringify({ error: 'Session ID required' }) }; } // Get or create session let session = await redis.get(`session:${sessionId}`); if (!session) { session = { id: sessionId, createdAt: Date.now(), visits: 0 }; } // Update session session.visits++; session.lastVisit = Date.now(); // Store with 1 hour expiration await redis.set(`session:${sessionId}`, session, { ex: 3600 }); return { statusCode: 200, body: JSON.stringify(session) }; }; ``` ### Rate Limiting ```javascript theme={null} const { Redis } = require('@upstash/redis'); const redis = Redis.fromEnv(); const RATE_LIMIT = 10; // requests per minute const WINDOW = 60; // seconds exports.handler = async (event) => { const ip = event.requestContext.identity.sourceIp; const key = `rate_limit:${ip}`; // Increment request count const requests = await redis.incr(key); // Set expiration on first request if (requests === 1) { await redis.expire(key, WINDOW); } // Check if limit exceeded if (requests > RATE_LIMIT) { return { statusCode: 429, body: JSON.stringify({ error: 'Rate limit exceeded', retryAfter: await redis.ttl(key) }) }; } return { statusCode: 200, body: JSON.stringify({ message: 'Request successful', remaining: RATE_LIMIT - requests }) }; }; ``` ### Caching API Responses ```javascript theme={null} const { Redis } = require('@upstash/redis'); const redis = Redis.fromEnv(); exports.handler = async (event) => { const userId = event.pathParameters.userId; const cacheKey = `user:${userId}`; // Try to get from cache const cached = await redis.get(cacheKey); if (cached) { return { statusCode: 200, headers: { 'X-Cache': 'HIT' }, body: JSON.stringify(cached) }; } // Fetch from database (simulated) const userData = { id: userId, name: 'John Doe', email: 'john@example.com', fetchedAt: new Date().toISOString() }; // Store in cache for 5 minutes await redis.set(cacheKey, userData, { ex: 300 }); return { statusCode: 200, headers: { 'X-Cache': 'MISS' }, body: JSON.stringify(userData) }; }; ``` ## Best Practices ### Use Environment Variables Always use environment variables for credentials, never hardcode them: ```javascript theme={null} const redis = Redis.fromEnv(); // This automatically reads UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN ``` ### Error Handling ```javascript theme={null} const { Redis } = require('@upstash/redis'); const redis = Redis.fromEnv(); exports.handler = async (event) => { try { const count = await redis.incr("counter"); return { statusCode: 200, body: JSON.stringify({ count }) }; } catch (error) { console.error('Redis error:', error); return { statusCode: 500, body: JSON.stringify({ error: 'Internal server error' }) }; } }; ``` ### Optimize Cold Starts Initialize the Redis client outside the handler to reuse connections across warm starts: ```javascript theme={null} const { Redis } = require('@upstash/redis'); // Initialize outside handler const redis = Redis.fromEnv(); exports.handler = async (event) => { // Handler code uses redis client const result = await redis.get('key'); return { statusCode: 200, body: JSON.stringify(result) }; }; ``` ## Infrastructure as Code ### AWS SAM For AWS SAM examples, see the [aws-sam example directory](https://github.com/upstash/upstash-redis/tree/main/examples/aws-sam). ### AWS CDK For AWS CDK examples (TypeScript and Python), see: * [AWS CDK TypeScript example](https://github.com/upstash/upstash-redis/tree/main/examples/aws-cdk-typescript) * [AWS CDK Python example](https://github.com/upstash/upstash-redis/tree/main/examples/aws-cdk-python) ### Terraform For Terraform deployment examples, see the [Terraform example directory](https://github.com/upstash/upstash-redis/tree/main/examples/terraform). ## Testing Locally ### Invoke Function Locally ```bash theme={null} # Set environment variables export UPSTASH_REDIS_REST_URL="your-url" export UPSTASH_REDIS_REST_TOKEN="your-token" # Run with Node.js node -e "require('./index').handler({}).then(console.log)" ``` ### Using AWS SAM CLI ```bash theme={null} sam local invoke counterFunction --env-vars env.json ``` ## Monitoring ### CloudWatch Logs Add logging to track Redis operations: ```javascript theme={null} exports.handler = async (event) => { console.log('Processing request:', JSON.stringify(event)); const start = Date.now(); const count = await redis.incr("counter"); const duration = Date.now() - start; console.log(`Redis operation completed in ${duration}ms`); return { statusCode: 200, body: JSON.stringify({ count }) }; }; ``` ## Troubleshooting ### Common Issues Ensure your Lambda function has internet access. If in a VPC, verify NAT Gateway or VPC endpoints are configured. Make sure `node_modules` is included in your deployment package. Run `npm install` before creating the ZIP file. Verify environment variables are set in Lambda configuration: ```bash theme={null} aws lambda get-function-configuration --function-name counterFunction ``` ## Next Steps Deploy with Cloudflare Workers Use Redis in Next.js applications Learn more Redis operations Browse all examples on GitHub # Basic Usage Source: https://upstash-redis-js.mintlify.app/examples/basic-usage Common Redis operations with the Upstash Redis SDK This guide covers the fundamental Redis operations using the Upstash Redis SDK. Learn how to work with strings, hashes, lists, sets, sorted sets, and JSON data. ## Installation ```bash npm theme={null} npm install @upstash/redis ``` ```bash pnpm theme={null} pnpm add @upstash/redis ``` ```bash yarn theme={null} yarn add @upstash/redis ``` ```bash bun theme={null} bun add @upstash/redis ``` ## Initialize the Client ```typescript theme={null} import { Redis } from "@upstash/redis" const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, }) ``` Alternatively, use `fromEnv()` to automatically load credentials from environment variables: ```typescript theme={null} import { Redis } from "@upstash/redis" const redis = Redis.fromEnv() ``` ## Strings Strings are the most basic Redis data type, representing a sequence of bytes. ### Set and Get ```typescript theme={null} // Set a simple string value await redis.set('key', 'value') // Get the value const data = await redis.get('key') console.log(data) // 'value' ``` ### Set with Expiration ```typescript theme={null} // Set with expiration in seconds await redis.set('key', 'value', { ex: 60 }) // Set with expiration in milliseconds await redis.set('key', 'value', { px: 60000 }) // Alternative: setex command await redis.setex('key', 60, 'value') ``` ### Increment and Decrement ```typescript theme={null} // Increment a counter const count = await redis.incr('counter') console.log('Counter:', count) // Increment by a specific amount await redis.incrby('counter', 5) // Decrement await redis.decr('counter') await redis.decrby('counter', 3) ``` ### Multiple Keys ```typescript theme={null} // Set multiple keys at once await redis.mset({ key1: 'value1', key2: 'value2', key3: 'value3' }) // Get multiple keys const values = await redis.mget('key1', 'key2', 'key3') console.log(values) // ['value1', 'value2', 'value3'] ``` ## Hashes Hashes are maps between string fields and string values, perfect for representing objects. ### Set and Get Hash Fields ```typescript theme={null} // Set hash fields await redis.hset('user:1000', { name: 'John Doe', email: 'john@example.com', age: '30' }) // Get a single field const name = await redis.hget('user:1000', 'name') console.log(name) // 'John Doe' // Get all fields and values const user = await redis.hgetall('user:1000') console.log(user) // { name: 'John Doe', email: 'john@example.com', age: '30' } ``` ### Get Multiple Fields ```typescript theme={null} // Get specific fields const fields = await redis.hmget('user:1000', 'name', 'email') console.log(fields) // ['John Doe', 'john@example.com'] ``` ### Hash Operations ```typescript theme={null} // Increment a numeric field await redis.hincrby('user:1000', 'age', 1) // Get all field names const keys = await redis.hkeys('user:1000') console.log(keys) // ['name', 'email', 'age'] // Get all values const values = await redis.hvals('user:1000') // Check if field exists const exists = await redis.hexists('user:1000', 'name') // Delete fields await redis.hdel('user:1000', 'age') ``` ## Lists Lists are ordered collections of strings, sorted by insertion order. ### Push and Pop ```typescript theme={null} // Push elements to the left (beginning) of the list await redis.lpush('mylist', 'first') await redis.lpush('mylist', 'second', 'third') // Push elements to the right (end) of the list await redis.rpush('mylist', 'last') // Pop from the left const first = await redis.lpop('mylist') // Pop from the right const last = await redis.rpop('mylist') ``` ### Range Operations ```typescript theme={null} // Get elements in a range (0-based index) const elements = await redis.lrange('mylist', 0, -1) // Get all elements console.log(elements) // Get specific range const subset = await redis.lrange('mylist', 0, 2) // First 3 elements // Get list length const length = await redis.llen('mylist') ``` ### Other List Operations ```typescript theme={null} // Get element at index const element = await redis.lindex('mylist', 0) // Set element at index await redis.lset('mylist', 0, 'new-value') // Trim list to specified range await redis.ltrim('mylist', 0, 99) // Keep first 100 elements ``` ## Sets Sets are unordered collections of unique strings. ### Add and Remove Members ```typescript theme={null} // Add members to a set await redis.sadd('tags', 'redis', 'database', 'nosql') // Check if member exists const isMember = await redis.sismember('tags', 'redis') console.log(isMember) // 1 (true) // Get all members const members = await redis.smembers('tags') console.log(members) // ['redis', 'database', 'nosql'] // Remove members await redis.srem('tags', 'nosql') ``` ### Set Operations ```typescript theme={null} // Get set size const size = await redis.scard('tags') // Pop random member const random = await redis.spop('tags') // Get random member without removing const randomMember = await redis.srandmember('tags') // Set operations await redis.sadd('set1', 'a', 'b', 'c') await redis.sadd('set2', 'b', 'c', 'd') // Intersection const intersection = await redis.sinter('set1', 'set2') // ['b', 'c'] // Union const union = await redis.sunion('set1', 'set2') // ['a', 'b', 'c', 'd'] // Difference const diff = await redis.sdiff('set1', 'set2') // ['a'] ``` ## Sorted Sets Sorted sets are similar to sets but each member has an associated score used for ordering. ### Add Members with Scores ```typescript theme={null} // Add members with scores await redis.zadd('leaderboard', { score: 100, member: 'player1' }) await redis.zadd('leaderboard', { score: 85, member: 'player2' }, { score: 95, member: 'player3' } ) ``` ### Query Sorted Sets ```typescript theme={null} // Get range by rank (0-based, lowest to highest score) const topPlayers = await redis.zrange('leaderboard', 0, 2) console.log(topPlayers) // ['player2', 'player3', 'player1'] // Get range with scores const withScores = await redis.zrange('leaderboard', 0, -1, { withScores: true }) console.log(withScores) // ['player2', 85, 'player3', 95, 'player1', 100] // Get range in reverse order (highest to lowest) const reversed = await redis.zrange('leaderboard', 0, -1, { rev: true }) ``` ### Sorted Set Operations ```typescript theme={null} // Get score of a member const score = await redis.zscore('leaderboard', 'player1') // Get rank of a member (0-based) const rank = await redis.zrank('leaderboard', 'player1') // Increment score await redis.zincrby('leaderboard', 10, 'player1') // Get count const count = await redis.zcard('leaderboard') // Remove members await redis.zrem('leaderboard', 'player2') // Remove by rank range await redis.zremrangebyrank('leaderboard', 0, 0) // Remove lowest ranked ``` ## JSON The JSON data type allows you to store, update, and retrieve JSON values. ### Set and Get JSON ```typescript theme={null} // Set a JSON value await redis.json.set('user:2000', '$', { name: 'Jane Smith', email: 'jane@example.com', preferences: { theme: 'dark', notifications: true }, tags: ['premium', 'verified'] }) // Get the entire JSON value const user = await redis.json.get('user:2000', '$') console.log(user) // [{ name: 'Jane Smith', ... }] ``` ### Update JSON Fields ```typescript theme={null} // Update a specific field await redis.json.set('user:2000', '$.preferences.theme', 'light') // Update nested fields await redis.json.set('user:2000', '$.email', 'jane.smith@example.com') // Get specific fields const theme = await redis.json.get('user:2000', '$.preferences.theme') console.log(theme) // ['light'] ``` ### Array Operations ```typescript theme={null} // Append to array await redis.json.arrappend('user:2000', '$.tags', 'early-adopter') // Get array length const length = await redis.json.arrlen('user:2000', '$.tags') // Get array element by index const tags = await redis.json.get('user:2000', '$.tags') ``` ### Numeric Operations ```typescript theme={null} // Store a JSON document with numbers await redis.json.set('stats', '$', { views: 100, likes: 50 }) // Increment a number await redis.json.numincrby('stats', '$.views', 1) await redis.json.numincrby('stats', '$.likes', 5) // Get the updated values const stats = await redis.json.get('stats', '$') console.log(stats) // [{ views: 101, likes: 55 }] ``` ## Key Management ### Check Existence and Delete ```typescript theme={null} // Check if key exists const exists = await redis.exists('mykey') console.log(exists) // 1 if exists, 0 if not // Delete keys await redis.del('key1', 'key2', 'key3') ``` ### Set Expiration ```typescript theme={null} // Set expiration in seconds await redis.expire('mykey', 60) // Set expiration in milliseconds await redis.pexpire('mykey', 60000) // Set expiration at specific timestamp const timestamp = Math.floor(Date.now() / 1000) + 3600 await redis.expireat('mykey', timestamp) // Get TTL (time to live) const ttl = await redis.ttl('mykey') console.log(ttl) // seconds until expiration ``` ### Get Key Information ```typescript theme={null} // Get key type const type = await redis.type('mykey') console.log(type) // 'string', 'list', 'set', 'zset', 'hash', or 'ReJSON-RL' // Pattern matching const keys = await redis.keys('user:*') // Get all keys matching pattern ``` ## Next Steps Deploy Redis with AWS Lambda functions Use Redis in Cloudflare Workers Integrate Redis with Next.js applications Explore all available Redis commands # Cloudflare Workers Source: https://upstash-redis-js.mintlify.app/examples/cloudflare-workers Use Upstash Redis with Cloudflare Workers at the edge This guide shows how to use Upstash Redis with Cloudflare Workers. The HTTP-based SDK is specifically designed for edge computing environments where traditional TCP connections aren't available. ## Why Upstash Redis for Cloudflare Workers? * **Edge-native**: HTTP-based connection perfect for Workers * **Global low latency**: Access Redis from 300+ cities worldwide * **No cold starts**: Instant execution at the edge * **Automatic scaling**: Handles traffic spikes seamlessly ## Prerequisites Sign up for a [Cloudflare account](https://dash.cloudflare.com/sign-up/workers) Install the Cloudflare Workers CLI: ```bash theme={null} npm install -g wrangler ``` Create a Redis database on [Upstash Console](https://console.upstash.com/redis) ## Quick Start ### Project Setup ```bash theme={null} git clone https://github.com/upstash/upstash-redis.git cd upstash-redis/examples/cloudflare-workers ``` Or create a new project: ```bash theme={null} mkdir my-worker cd my-worker npm init -y ``` ```bash theme={null} npm install @upstash/redis npm install -D wrangler ``` ## Example: Counter Worker This example implements a simple counter that increments on each request. ```javascript index.js theme={null} import { Redis } from "@upstash/redis/cloudflare"; export default { async fetch(_request, env) { const redis = Redis.fromEnv(env); const count = await redis.incr("cloudflare-workers-count"); return new Response(JSON.stringify({ count })); }, }; ``` ```typescript index.ts theme={null} import { Redis } from "@upstash/redis/cloudflare"; export interface Env { UPSTASH_REDIS_REST_URL: string; UPSTASH_REDIS_REST_TOKEN: string; } export default { async fetch(request: Request, env: Env): Promise { const redis = Redis.fromEnv(env); const count = await redis.incr("cloudflare-workers-count"); return new Response(JSON.stringify({ count }), { headers: { "Content-Type": "application/json" }, }); }, }; ``` ```toml wrangler.toml theme={null} name = "upstash-redis" main = "index.js" compatibility_date = "2024-01-01" # Set variables here or in Cloudflare dashboard # [vars] # UPSTASH_REDIS_REST_URL = "https://your-endpoint.upstash.io" # UPSTASH_REDIS_REST_TOKEN = "your-token" ``` ```json package.json theme={null} { "name": "cloudflare-workers", "version": "1.0.0", "description": "Cloudflare Workers with Upstash Redis", "scripts": { "start": "wrangler dev", "publish": "wrangler deploy" }, "devDependencies": { "wrangler": "^3.0.0" }, "dependencies": { "@upstash/redis": "latest" } } ``` Notice the import path: `@upstash/redis/cloudflare` - This is important for Cloudflare Workers compatibility. ## Configuration ### Add Environment Variables You can configure environment variables in two ways: #### Option 1: wrangler.toml (Not Recommended for Production) ```toml wrangler.toml theme={null} name = "my-worker" main = "index.js" compatibility_date = "2024-01-01" [vars] UPSTASH_REDIS_REST_URL = "https://your-endpoint.upstash.io" UPSTASH_REDIS_REST_TOKEN = "your-token" ``` #### Option 2: Cloudflare Dashboard (Recommended) Navigate to your [Cloudflare Workers dashboard](https://dash.cloudflare.com/) Click on your Worker or create a new one Go to **Settings** > **Variables** and add: * `UPSTASH_REDIS_REST_URL` * `UPSTASH_REDIS_REST_TOKEN` #### Option 3: Wrangler CLI Secrets (Most Secure) ```bash theme={null} wrangler secret put UPSTASH_REDIS_REST_URL wrangler secret put UPSTASH_REDIS_REST_TOKEN ``` ## Development ### Run Locally ```bash theme={null} npm run start # or wrangler dev ``` Open your browser at [localhost:8787](http://localhost:8787) ### Deploy to Cloudflare ```bash theme={null} npm run publish # or wrangler deploy ``` Your Worker will be deployed to Cloudflare's global network and accessible via a `*.workers.dev` URL. ## Advanced Examples ### REST API with Routing ```typescript theme={null} import { Redis } from "@upstash/redis/cloudflare"; export interface Env { UPSTASH_REDIS_REST_URL: string; UPSTASH_REDIS_REST_TOKEN: string; } export default { async fetch(request: Request, env: Env): Promise { const redis = Redis.fromEnv(env); const url = new URL(request.url); const path = url.pathname; // GET /counter - Get current count if (path === "/counter" && request.method === "GET") { const count = await redis.get("counter") || 0; return new Response(JSON.stringify({ count }), { headers: { "Content-Type": "application/json" }, }); } // POST /counter/increment - Increment counter if (path === "/counter/increment" && request.method === "POST") { const count = await redis.incr("counter"); return new Response(JSON.stringify({ count }), { headers: { "Content-Type": "application/json" }, }); } // POST /counter/reset - Reset counter if (path === "/counter/reset" && request.method === "POST") { await redis.set("counter", 0); return new Response(JSON.stringify({ count: 0 }), { headers: { "Content-Type": "application/json" }, }); } return new Response("Not found", { status: 404 }); }, }; ``` ### Edge Caching ```typescript theme={null} import { Redis } from "@upstash/redis/cloudflare"; export interface Env { UPSTASH_REDIS_REST_URL: string; UPSTASH_REDIS_REST_TOKEN: string; } export default { async fetch(request: Request, env: Env): Promise { const redis = Redis.fromEnv(env); const url = new URL(request.url); const cacheKey = `cache:${url.pathname}`; // Try cache first const cached = await redis.get(cacheKey); if (cached) { return new Response(JSON.stringify(cached), { headers: { "Content-Type": "application/json", "X-Cache": "HIT", }, }); } // Simulate fetching data const data = { message: "Hello from the edge!", timestamp: new Date().toISOString(), location: request.cf?.city || "Unknown", }; // Cache for 1 minute await redis.set(cacheKey, data, { ex: 60 }); return new Response(JSON.stringify(data), { headers: { "Content-Type": "application/json", "X-Cache": "MISS", }, }); }, }; ``` ### Rate Limiting at the Edge ```typescript theme={null} import { Redis } from "@upstash/redis/cloudflare"; export interface Env { UPSTASH_REDIS_REST_URL: string; UPSTASH_REDIS_REST_TOKEN: string; } const RATE_LIMIT = 10; // requests per minute const WINDOW = 60; // seconds export default { async fetch(request: Request, env: Env): Promise { const redis = Redis.fromEnv(env); // Use CF-Connecting-IP header for client IP const ip = request.headers.get("CF-Connecting-IP") || "unknown"; const key = `rate_limit:${ip}`; // Get current count const current = await redis.incr(key); // Set expiration on first request if (current === 1) { await redis.expire(key, WINDOW); } // Check if limit exceeded if (current > RATE_LIMIT) { const ttl = await redis.ttl(key); return new Response( JSON.stringify({ error: "Rate limit exceeded", retryAfter: ttl, }), { status: 429, headers: { "Content-Type": "application/json", "Retry-After": ttl.toString(), }, } ); } // Process request return new Response( JSON.stringify({ message: "Success", remaining: RATE_LIMIT - current, }), { headers: { "Content-Type": "application/json", "X-RateLimit-Limit": RATE_LIMIT.toString(), "X-RateLimit-Remaining": (RATE_LIMIT - current).toString(), }, } ); }, }; ``` ### Session Management ```typescript theme={null} import { Redis } from "@upstash/redis/cloudflare"; interface Session { id: string; userId?: string; createdAt: number; lastActivity: number; data: Record; } export interface Env { UPSTASH_REDIS_REST_URL: string; UPSTASH_REDIS_REST_TOKEN: string; } function generateSessionId(): string { return crypto.randomUUID(); } export default { async fetch(request: Request, env: Env): Promise { const redis = Redis.fromEnv(env); // Get or create session let sessionId = request.headers.get("X-Session-ID"); let session: Session | null = null; if (sessionId) { session = await redis.get(`session:${sessionId}`); } if (!session) { sessionId = generateSessionId(); session = { id: sessionId, createdAt: Date.now(), lastActivity: Date.now(), data: {}, }; } // Update session activity session.lastActivity = Date.now(); // Store session with 1 hour expiration await redis.set(`session:${sessionId}`, session, { ex: 3600 }); return new Response( JSON.stringify({ sessionId, session, }), { headers: { "Content-Type": "application/json", "X-Session-ID": sessionId, }, } ); }, }; ``` ## TypeScript Support For full TypeScript support, use the TypeScript template: ```bash theme={null} wrangler init my-worker --type typescript ``` Define your environment interface: ```typescript theme={null} export interface Env { UPSTASH_REDIS_REST_URL: string; UPSTASH_REDIS_REST_TOKEN: string; // Add other environment variables here } ``` ## Best Practices ### Initialize Redis Client Inside Handler Unlike traditional environments, Workers don't maintain state between requests. Initialize the client in each request: ```typescript theme={null} export default { async fetch(request: Request, env: Env): Promise { const redis = Redis.fromEnv(env); // Initialize per request // Use redis... }, }; ``` ### Error Handling ```typescript theme={null} export default { async fetch(request: Request, env: Env): Promise { try { const redis = Redis.fromEnv(env); const data = await redis.get("key"); return new Response(JSON.stringify({ data }), { headers: { "Content-Type": "application/json" }, }); } catch (error) { console.error("Redis error:", error); return new Response( JSON.stringify({ error: "Internal server error" }), { status: 500, headers: { "Content-Type": "application/json" }, } ); } }, }; ``` ### Use Edge Location Data Cloudflare provides request context with location data: ```typescript theme={null} const location = { country: request.cf?.country, city: request.cf?.city, timezone: request.cf?.timezone, latitude: request.cf?.latitude, longitude: request.cf?.longitude, }; // Store location-specific data await redis.set(`visitor:${ip}`, location); ``` ## Testing ### Local Testing with Wrangler ```bash theme={null} wrangler dev ``` Test endpoints: ```bash theme={null} curl http://localhost:8787/counter curl -X POST http://localhost:8787/counter/increment ``` ### Unit Testing For unit testing Workers, see [Cloudflare Workers testing documentation](https://developers.cloudflare.com/workers/testing/). ## Monitoring ### View Logs ```bash theme={null} wrangler tail ``` Or view logs in the [Cloudflare Dashboard](https://dash.cloudflare.com/). ### Add Custom Logging ```typescript theme={null} export default { async fetch(request: Request, env: Env): Promise { console.log("Request:", request.url); const start = Date.now(); const redis = Redis.fromEnv(env); const result = await redis.get("key"); const duration = Date.now() - start; console.log(`Redis GET completed in ${duration}ms`); return new Response(JSON.stringify(result)); }, }; ``` ## Troubleshooting Make sure you're using the correct import path: ```typescript theme={null} import { Redis } from "@upstash/redis/cloudflare"; ``` Not: ```typescript theme={null} import { Redis } from "@upstash/redis"; // Wrong for Workers! ``` Ensure variables are: 1. Set in `wrangler.toml` \[vars] section, OR 2. Set in Cloudflare Dashboard, OR 3. Set as secrets via `wrangler secret put` Access them via the `env` parameter: ```typescript theme={null} Redis.fromEnv(env) // Pass the env object ``` Check your `wrangler.toml` configuration: * `name` must be unique * `main` must point to your entry file * `compatibility_date` should be recent ## Next Steps Deploy Redis with AWS Lambda Integrate Redis with Next.js Learn more Redis operations Cloudflare Workers documentation # Next.js Source: https://upstash-redis-js.mintlify.app/examples/next-js Integrate Upstash Redis with Next.js applications This guide demonstrates how to use Upstash Redis in Next.js applications, covering both App Router and Pages Router patterns, Server Components, API Routes, and Server Actions. ## Why Upstash Redis for Next.js? * **Edge-ready**: Works in Edge Runtime and Serverless Functions * **Server Components**: Native support for React Server Components * **Fast**: HTTP-based for quick cold starts on Vercel * **Simple**: No connection pooling required * **Flexible**: Use in API routes, Server Actions, or Server Components ## Prerequisites ```bash theme={null} npx create-next-app@latest my-app cd my-app ``` ```bash theme={null} npm install @upstash/redis ``` Create a database on [Upstash Console](https://console.upstash.com) Create `.env.local` file: ```bash theme={null} UPSTASH_REDIS_REST_URL=your_url_here UPSTASH_REDIS_REST_TOKEN=your_token_here ``` ## App Router (Next.js 13+) ### Server Component Example Server Components can directly access Redis without API routes. ```tsx app/page.tsx theme={null} import { Redis } from "@upstash/redis"; const redis = Redis.fromEnv(); export default async function Home() { const count = await redis.incr("counter"); return (

Counter: {count}

) } ``` ```tsx app/layout.tsx theme={null} export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( {children} ) } ``` ```bash .env.local theme={null} UPSTASH_REDIS_REST_URL=https://your-endpoint.upstash.io UPSTASH_REDIS_REST_TOKEN=your-token ```
### API Route Example ```typescript app/api/hello/route.ts theme={null} import { Redis } from "@upstash/redis"; import { NextResponse } from "next/server"; const redis = Redis.fromEnv(); export async function GET() { const count = await redis.incr("counter"); return NextResponse.json({ count }); } export const dynamic = 'force-dynamic' ``` ```typescript app/api/user/[id]/route.ts theme={null} import { Redis } from "@upstash/redis"; import { NextResponse } from "next/server"; const redis = Redis.fromEnv(); interface User { id: string; name: string; email: string; } export async function GET( request: Request, { params }: { params: { id: string } } ) { const user = await redis.get(`user:${params.id}`); if (!user) { return NextResponse.json( { error: "User not found" }, { status: 404 } ); } return NextResponse.json(user); } export async function POST( request: Request, { params }: { params: { id: string } } ) { const body = await request.json(); await redis.set(`user:${params.id}`, body, { ex: 3600 }); return NextResponse.json({ success: true }); } ``` ### Server Actions Server Actions provide a simple way to mutate data from Client Components. ```tsx app/actions.ts theme={null} "use server" import { Redis } from "@upstash/redis"; import { revalidatePath } from "next/cache"; const redis = Redis.fromEnv(); export async function incrementCounter() { const count = await redis.incr("counter"); revalidatePath("/"); return count; } export async function resetCounter() { await redis.set("counter", 0); revalidatePath("/"); return 0; } export async function addTodo(text: string) { const id = Date.now().toString(); await redis.hset(`todo:${id}`, { id, text, completed: false, createdAt: new Date().toISOString(), }); revalidatePath("/todos"); return id; } ``` ```tsx app/page.tsx theme={null} "use client" import { incrementCounter, resetCounter } from "./actions"; import { useState } from "react"; export default function Home() { const [count, setCount] = useState(0); async function handleIncrement() { const newCount = await incrementCounter(); setCount(newCount); } async function handleReset() { const newCount = await resetCounter(); setCount(newCount); } return (

Counter: {count}

); } ```
## Pages Router ### API Routes ```typescript pages/api/hello.ts theme={null} import type { NextApiRequest, NextApiResponse } from "next"; import { Redis } from "@upstash/redis"; const redis = Redis.fromEnv(); type Data = { count: number; }; export default async function handler( req: NextApiRequest, res: NextApiResponse ) { const count = await redis.incr("counter"); res.status(200).json({ count }); } ``` ```typescript pages/api/cache/[key].ts theme={null} import type { NextApiRequest, NextApiResponse } from "next"; import { Redis } from "@upstash/redis"; const redis = Redis.fromEnv(); export default async function handler( req: NextApiRequest, res: NextApiResponse ) { const { key } = req.query; if (typeof key !== "string") { return res.status(400).json({ error: "Invalid key" }); } if (req.method === "GET") { const value = await redis.get(key); return res.status(200).json({ value }); } if (req.method === "POST") { const { value, ttl } = req.body; await redis.set(key, value, ttl ? { ex: ttl } : {}); return res.status(200).json({ success: true }); } if (req.method === "DELETE") { await redis.del(key); return res.status(200).json({ success: true }); } res.status(405).json({ error: "Method not allowed" }); } ``` ### Server-Side Rendering (SSR) ```typescript pages/index.tsx theme={null} import { Redis } from "@upstash/redis"; import type { GetServerSideProps } from "next"; const redis = Redis.fromEnv(); interface Props { count: number; } export default function Home({ count }: Props) { return (

Counter: {count}

); } export const getServerSideProps: GetServerSideProps = async () => { const count = await redis.get("counter") || 0; return { props: { count, }, }; }; ``` ## Advanced Examples ### Caching with Revalidation ```typescript app/posts/[id]/page.tsx theme={null} import { Redis } from "@upstash/redis"; import { notFound } from "next/navigation"; const redis = Redis.fromEnv(); interface Post { id: string; title: string; content: string; author: string; } async function getPost(id: string): Promise { const cacheKey = `post:${id}`; // Try cache first const cached = await redis.get(cacheKey); if (cached) { return cached; } // Fetch from database (simulated) const post: Post = { id, title: "Example Post", content: "This is an example post", author: "John Doe", }; // Cache for 5 minutes await redis.set(cacheKey, post, { ex: 300 }); return post; } export default async function PostPage({ params, }: { params: { id: string }; }) { const post = await getPost(params.id); if (!post) { notFound(); } return (

{post.title}

By {post.author}

{post.content}
); } // Revalidate every 60 seconds export const revalidate = 60; ``` ### Rate Limiting Middleware ```typescript middleware.ts theme={null} import { Redis } from "@upstash/redis"; import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); const RATE_LIMIT = 10; // requests per minute const WINDOW = 60; // seconds export async function middleware(request: NextRequest) { // Get client IP const ip = request.ip || "unknown"; const key = `rate_limit:${ip}`; // Increment counter const requests = await redis.incr(key); // Set expiration on first request if (requests === 1) { await redis.expire(key, WINDOW); } // Check if limit exceeded if (requests > RATE_LIMIT) { return new NextResponse( JSON.stringify({ error: "Rate limit exceeded" }), { status: 429, headers: { "Content-Type": "application/json", "Retry-After": WINDOW.toString(), }, } ); } // Add rate limit headers const response = NextResponse.next(); response.headers.set("X-RateLimit-Limit", RATE_LIMIT.toString()); response.headers.set("X-RateLimit-Remaining", (RATE_LIMIT - requests).toString()); return response; } export const config = { matcher: "/api/:path*", }; ``` ### Session Management ```typescript lib/session.ts theme={null} import { Redis } from "@upstash/redis"; import { cookies } from "next/headers"; const redis = Redis.fromEnv(); interface Session { userId: string; email: string; createdAt: number; } export async function getSession(): Promise { const sessionId = cookies().get("sessionId")?.value; if (!sessionId) { return null; } const session = await redis.get(`session:${sessionId}`); return session; } export async function createSession(userId: string, email: string): Promise { const sessionId = crypto.randomUUID(); const session: Session = { userId, email, createdAt: Date.now(), }; // Store session for 7 days await redis.set(`session:${sessionId}`, session, { ex: 60 * 60 * 24 * 7 }); cookies().set("sessionId", sessionId, { httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "lax", maxAge: 60 * 60 * 24 * 7, // 7 days }); return sessionId; } export async function deleteSession(): Promise { const sessionId = cookies().get("sessionId")?.value; if (sessionId) { await redis.del(`session:${sessionId}`); cookies().delete("sessionId"); } } ``` ```typescript app/api/auth/login/route.ts theme={null} import { createSession } from "@/lib/session"; import { NextResponse } from "next/server"; export async function POST(request: Request) { const { email, password } = await request.json(); // Validate credentials (implement your own logic) const userId = "user123"; // Retrieved from your auth system await createSession(userId, email); return NextResponse.json({ success: true }); } ``` ### Real-time View Counter ```typescript app/blog/[slug]/page.tsx theme={null} import { Redis } from "@upstash/redis"; import ViewCounter from "./ViewCounter"; const redis = Redis.fromEnv(); interface Props { params: { slug: string }; } export default async function BlogPost({ params }: Props) { const viewKey = `views:${params.slug}`; // Increment view count const views = await redis.incr(viewKey); return (

Blog Post

{/* Post content */}
); } ``` ```typescript app/blog/[slug]/ViewCounter.tsx theme={null} "use client" export default function ViewCounter({ views }: { views: number }) { return (
{views.toLocaleString()} views
); } ``` ## Best Practices ### 1. Initialize Client Once Create a shared Redis instance: ```typescript lib/redis.ts theme={null} import { Redis } from "@upstash/redis"; export const redis = Redis.fromEnv(); ``` Use it throughout your app: ```typescript theme={null} import { redis } from "@/lib/redis"; const data = await redis.get("key"); ``` ### 2. Type Safety Use TypeScript generics for type-safe operations: ```typescript theme={null} interface User { id: string; name: string; email: string; } const user = await redis.get("user:123"); // user is typed as User | null ``` ### 3. Error Handling ```typescript theme={null} import { redis } from "@/lib/redis"; import { NextResponse } from "next/server"; export async function GET() { try { const data = await redis.get("key"); return NextResponse.json({ data }); } catch (error) { console.error("Redis error:", error); return NextResponse.json( { error: "Failed to fetch data" }, { status: 500 } ); } } ``` ### 4. Caching Strategy Implement cache-aside pattern: ```typescript theme={null} async function getData(id: string) { const cacheKey = `data:${id}`; // Try cache const cached = await redis.get(cacheKey); if (cached) return cached; // Fetch from source const data = await fetchFromDatabase(id); // Store in cache await redis.set(cacheKey, data, { ex: 300 }); return data; } ``` ## Deployment ### Vercel In your Vercel project settings, add: * `UPSTASH_REDIS_REST_URL` * `UPSTASH_REDIS_REST_TOKEN` ```bash theme={null} vercel deploy ``` ### Other Platforms Upstash Redis works on any platform that supports Next.js: * Netlify * AWS Amplify * Railway * Render Just add the environment variables to your platform's settings. ## Next Steps Deploy with AWS Lambda Use Redis at the edge Learn more Redis operations More Next.js examples # Installation Source: https://upstash-redis-js.mintlify.app/installation Install Upstash Redis SDK for your platform and package manager Install the Upstash Redis SDK for your platform using your preferred package manager. ## Package managers ```bash theme={null} npm install @upstash/redis ``` ```bash theme={null} yarn add @upstash/redis ``` ```bash theme={null} pnpm add @upstash/redis ``` ```bash theme={null} bun add @upstash/redis ``` ## Platform-specific imports The SDK provides optimized entry points for different platforms. Choose the right import for your environment. ### Node.js The default export is optimized for Node.js: ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, }); ``` Supports Node.js 14 and above. The SDK automatically detects your Node.js version and adjusts accordingly. ### Cloudflare Workers Use the Cloudflare-specific import for Workers: ```typescript theme={null} import { Redis } from '@upstash/redis/cloudflare'; export interface Env { UPSTASH_REDIS_REST_URL: string; UPSTASH_REDIS_REST_TOKEN: string; } export default { async fetch(request: Request, env: Env): Promise { const redis = Redis.fromEnv(env); const count = await redis.incr('counter'); return new Response(JSON.stringify({ count })); }, }; ``` The Cloudflare import is optimized for Workers and removes Node.js-specific code for smaller bundle sizes. ### Fastly Compute\@Edge Use the Fastly-specific import: ```javascript theme={null} import { Redis } from '@upstash/redis/fastly'; const redis = new Redis({ url: 'your_upstash_redis_rest_url', token: 'your_upstash_redis_rest_token', }); const result = await redis.get('key'); ``` ### Deno Import directly from esm.sh: ```typescript theme={null} import { Redis } from 'https://esm.sh/@upstash/redis'; const redis = new Redis({ url: Deno.env.get('UPSTASH_REDIS_REST_URL')!, token: Deno.env.get('UPSTASH_REDIS_REST_TOKEN')!, }); const value = await redis.get('key'); ``` Alternatively, use deno.land/x: ```typescript theme={null} import { Redis } from 'https://deno.land/x/upstash_redis/mod.ts'; ``` ## Environment variables The SDK automatically reads credentials from environment variables when using `Redis.fromEnv()`. ### Standard environment variables Set these variables in your `.env` file: ```bash .env theme={null} UPSTASH_REDIS_REST_URL="https://your-redis-url.upstash.io" UPSTASH_REDIS_REST_TOKEN="your_token_here" ``` ### Vercel KV compatibility The SDK also supports Vercel KV environment variable names: ```bash .env theme={null} KV_REST_API_URL="https://your-redis-url.upstash.io" KV_REST_API_TOKEN="your_token_here" ``` If both sets of variables are present, `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` take precedence. ### Using fromEnv() ```typescript Node.js theme={null} import { Redis } from '@upstash/redis'; // Automatically reads from process.env const redis = Redis.fromEnv(); ``` ```typescript Cloudflare Workers theme={null} import { Redis } from '@upstash/redis/cloudflare'; export interface Env { UPSTASH_REDIS_REST_URL: string; UPSTASH_REDIS_REST_TOKEN: string; } export default { async fetch(request: Request, env: Env) { // Pass the env object in Cloudflare Workers const redis = Redis.fromEnv(env); // Your code here }, }; ``` ## Configuration options When creating a Redis client, you can configure various options: ```typescript theme={null} interface RedisConfigNodejs { /** * UPSTASH_REDIS_REST_URL */ url: string; /** * UPSTASH_REDIS_REST_TOKEN */ token: string; /** * Enable automatic pipelining to batch commands * @default true */ enableAutoPipelining?: boolean; /** * Enable read-your-writes consistency * @default false */ readYourWrites?: boolean; /** * Automatically deserialize JSON responses * @default true */ automaticDeserialization?: boolean; /** * Enable telemetry for analytics * @default true */ enableTelemetry?: boolean; /** * Configure retry behavior */ retry?: { retries: number; backoff: (retryCount: number) => number; }; /** * HTTP agent for connection pooling (Node.js only) */ agent?: unknown; /** * Enable HTTP keep-alive (Node.js only) */ keepAlive?: boolean; /** * AbortSignal for request cancellation */ signal?: AbortSignal; } ``` ### Example with options ```typescript theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, // Performance optimizations enableAutoPipelining: true, keepAlive: true, // Consistency readYourWrites: true, // Privacy enableTelemetry: false, // Retry configuration retry: { retries: 3, backoff: (retryCount) => Math.exp(retryCount) * 100, }, }); ``` ## TypeScript configuration The SDK is written in TypeScript and includes type definitions. No additional `@types` package is needed. If you're using TypeScript, ensure your `tsconfig.json` includes: ```json tsconfig.json theme={null} { "compilerOptions": { "target": "ES2020", "module": "ESNext", "moduleResolution": "node", "esModuleInterop": true, "strict": true } } ``` ## CommonJS vs ES Modules The SDK supports both CommonJS and ES Modules: ```javascript CommonJS theme={null} const { Redis } = require('@upstash/redis'); const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, }); ``` ```javascript ES Modules theme={null} import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, }); ``` ## Bundler configuration The SDK works with all major bundlers without additional configuration. ### Webpack No configuration needed. The SDK uses standard exports that Webpack handles automatically. ### Vite No configuration needed. Vite will tree-shake unused code automatically. ### esbuild No configuration needed. The SDK includes proper exports for esbuild. ### Rollup No configuration needed. The SDK is compatible with Rollup's ES module handling. ## Disabling telemetry The SDK sends anonymous telemetry data by default. To disable it: ### Via environment variable ```bash .env theme={null} UPSTASH_DISABLE_TELEMETRY=1 ``` ### Via configuration ```typescript theme={null} const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, enableTelemetry: false, }); ``` Telemetry helps improve the SDK by collecting: * SDK version * Platform (Cloudflare, Vercel, AWS, etc.) * Runtime version (e.g., [node@18.x](mailto:node@18.x)) No personal data or Redis commands are collected. ## Verify installation Test your installation with this simple script: ```typescript test.ts theme={null} import { Redis } from '@upstash/redis'; const redis = Redis.fromEnv(); async function test() { const result = await redis.ping(); console.log('Ping result:', result); // Should print: Ping result: PONG } test().catch(console.error); ``` Run it: ```bash Node.js theme={null} node test.ts ``` ```bash Bun theme={null} bun test.ts ``` ```bash Deno theme={null} deno run --allow-env --allow-net test.ts ``` If you see `Ping result: PONG`, your installation is successful! ## Troubleshooting Make sure you've installed the package: ```bash theme={null} npm install @upstash/redis ``` If using Cloudflare or Fastly, use the correct import: ```typescript theme={null} import { Redis } from '@upstash/redis/cloudflare'; // or import { Redis } from '@upstash/redis/fastly'; ``` Ensure your `.env` file is in the project root and you're loading it correctly: ```bash theme={null} npm install dotenv ``` ```typescript theme={null} import 'dotenv/config'; import { Redis } from '@upstash/redis'; const redis = Redis.fromEnv(); ``` Make sure you're using TypeScript 4.5 or higher: ```bash theme={null} npm install -D typescript@latest ``` Update your `tsconfig.json`: ```json theme={null} { "compilerOptions": { "moduleResolution": "node", "esModuleInterop": true } } ``` Use the Cloudflare-specific import and ensure you're passing the `env` object: ```typescript theme={null} import { Redis } from '@upstash/redis/cloudflare'; export default { async fetch(request: Request, env: Env) { const redis = Redis.fromEnv(env); // Pass env here // ... }, }; ``` ## Next steps Create your first Redis client Explore all available commands # Upstash Redis SDK Source: https://upstash-redis-js.mintlify.app/introduction HTTP-based Redis client for serverless and edge environments Upstash Redis is an HTTP/REST based Redis client for TypeScript, built on top of the Upstash REST API. It's the only connectionless (HTTP-based) Redis client, designed specifically for modern serverless and edge computing platforms. ## Why Upstash Redis? Traditional Redis clients use TCP connections, which don't work well in serverless environments. Upstash Redis solves this by using HTTP/REST, making it perfect for: * **Serverless functions** - AWS Lambda, Google Cloud Functions, Azure Functions * **Edge runtimes** - Cloudflare Workers, Fastly Compute\@Edge, Vercel Edge Functions * **Modern frameworks** - Next.js, Remix, SvelteKit, and more * **Client-side applications** - Web and mobile apps * **WebAssembly** - Any environment where HTTP is preferred over TCP Get a working Redis client in under 5 minutes Complete Redis command reference Platform-specific examples and code samples Setup guides for Cloudflare, Vercel, AWS, and more ## Key features ### Connectionless architecture No need to manage connection pools or worry about connection limits. Every command is a simple HTTP request. ### Full Redis compatibility Supports all major Redis data structures and commands: * **Strings** - `get`, `set`, `incr`, `decr` * **Hashes** - `hget`, `hset`, `hgetall` * **Lists** - `lpush`, `rpush`, `lrange` * **Sets** - `sadd`, `smembers`, `sinter` * **Sorted sets** - `zadd`, `zrange`, `zrank` * **JSON** - Native JSON support with `json.get`, `json.set` * **Streams** - `xadd`, `xread`, `xrange` ### TypeScript-first Fully typed with generic support for your data structures: ```typescript theme={null} interface User { name: string; email: string; } const user = await redis.hgetall('user:123'); // user is typed as User | null ``` ### Auto-pipelining Automatically batches commands for optimal performance: ```typescript theme={null} const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, enableAutoPipelining: true, }); // These commands are automatically batched const [user, posts, count] = await Promise.all([ redis.get('user:123'), redis.lrange('posts:123', 0, 10), redis.incr('views:123'), ]); ``` ### Read-your-writes consistency Ensure strong consistency across globally distributed databases: ```typescript theme={null} const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, readYourWrites: true, }); ``` ## Platform support Full support for Node.js 14+ Optimized for Cloudflare Workers First-class Vercel integration Native Deno support via ESM Compute\@Edge compatible Works seamlessly with Bun ## Production ready This project is in General Availability (GA) stage. It receives regular updates and bug fixes, with full support from the Upstash team. The SDK is battle-tested and used in production by thousands of developers. It includes: * Automatic retries with exponential backoff * Comprehensive error handling * Built-in telemetry (can be disabled) * Environment variable support * Custom middleware support ## Next steps Create your first Redis client Install for your platform # Cloudflare Workers Source: https://upstash-redis-js.mintlify.app/platforms/cloudflare-workers Use Upstash Redis SDK in Cloudflare Workers The Upstash Redis SDK is optimized for Cloudflare Workers with a dedicated platform-specific build. ## Installation ```bash theme={null} npm install @upstash/redis ``` ## Quick Start ```typescript theme={null} import { Redis } from "@upstash/redis/cloudflare"; ``` Always use `@upstash/redis/cloudflare` when deploying to Cloudflare Workers. The default import is optimized for Node.js and may not work correctly. ```typescript theme={null} export interface Env { UPSTASH_REDIS_REST_URL: string; UPSTASH_REDIS_REST_TOKEN: string; } ``` ```typescript theme={null} import { Redis } from "@upstash/redis/cloudflare"; export interface Env { UPSTASH_REDIS_REST_URL: string; UPSTASH_REDIS_REST_TOKEN: string; } export default { async fetch(_request: Request, env: Env, _ctx: ExecutionContext): Promise { // Pass the env object to fromEnv() const redis = Redis.fromEnv(env); const count = await redis.incr("cloudflare-workers-count"); return new Response(JSON.stringify({ count })); }, }; ``` ```typescript theme={null} import { Redis } from "@upstash/redis/cloudflare"; export default { async fetch(_request, env) { const redis = Redis.fromEnv(env); const count = await redis.incr("cloudflare-workers-count"); return new Response(JSON.stringify({ count })); }, }; ``` ```typescript theme={null} import { Redis } from "@upstash/redis/cloudflare"; export default { async fetch(_request: Request, env: Env): Promise { const redis = new Redis({ url: env.UPSTASH_REDIS_REST_URL, token: env.UPSTASH_REDIS_REST_TOKEN, }); const count = await redis.incr("counter"); return new Response(JSON.stringify({ count })); }, }; ``` ## Configuration Options ```typescript theme={null} const redis = new Redis({ url: env.UPSTASH_REDIS_REST_URL, token: env.UPSTASH_REDIS_REST_TOKEN, // Enable keep-alive connections keepAlive: true, // Abort requests using AbortSignal signal: abortController.signal, // Read-your-writes consistency readYourWrites: true, // Automatic deserialization of JSON values automaticDeserialization: true, // Enable telemetry (default: true) enableTelemetry: true, // Configure retry behavior retry: { retries: 3, backoff: (retryCount) => Math.exp(retryCount) * 50, }, // Response encoding responseEncoding: "base64", }); ``` ### Using fromEnv() with Options ```typescript theme={null} const redis = Redis.fromEnv(env, { keepAlive: true, automaticDeserialization: true, }); ``` ## Environment Variables ```bash theme={null} wrangler secret put UPSTASH_REDIS_REST_URL wrangler secret put UPSTASH_REDIS_REST_TOKEN ``` ```toml theme={null} name = "my-worker" main = "src/index.ts" compatibility_date = "2023-01-01" [vars] UPSTASH_REDIS_REST_URL = "https://your-redis-url.upstash.io" UPSTASH_REDIS_REST_TOKEN = "your-token-here" ``` Do not commit secrets to `wrangler.toml`. Use Wrangler secrets for production. ## Environment Binding Cloudflare Workers can bind environment variables in two ways: ### 1. Module Workers (Recommended) Pass the `env` object to `Redis.fromEnv()`: ```typescript theme={null} export default { async fetch(_request: Request, env: Env, _ctx: ExecutionContext) { const redis = Redis.fromEnv(env); // ... }, }; ``` ### 2. Global Variables If you don't pass the `env` object, the SDK will attempt to read from global variables: ```typescript theme={null} // Only works if UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN // are available as global variables const redis = Redis.fromEnv(); ``` Module workers with explicit `env` passing is the recommended approach for better type safety and clarity. ## Complete Example ```typescript theme={null} import { Redis } from "@upstash/redis/cloudflare"; export interface Env { UPSTASH_REDIS_REST_URL: string; UPSTASH_REDIS_REST_TOKEN: string; } export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { const redis = Redis.fromEnv(env); // Increment counter const count = await redis.incr("page-views"); // Set a value with expiration await redis.set("last-visit", new Date().toISOString(), { ex: 60 }); // Get a value const lastVisit = await redis.get("last-visit"); return new Response( JSON.stringify({ count, lastVisit, }), { headers: { "Content-Type": "application/json" }, } ); }, }; ``` ## Telemetry The SDK automatically reports `"cloudflare"` as the platform in telemetry data. To disable telemetry: ```typescript theme={null} const redis = Redis.fromEnv(env, { enableTelemetry: false, }); ``` Or set the `UPSTASH_DISABLE_TELEMETRY` environment variable in your `env` object. ## Related * [Cloudflare Workers Example](https://github.com/upstash/redis-js/tree/main/examples/cloudflare-workers) - Complete example on GitHub * [Cloudflare Workers TypeScript Example](https://github.com/upstash/redis-js/tree/main/examples/cloudflare-workers-with-typescript) - TypeScript example * [Configuration](/concepts/client-configuration) - Complete configuration reference # Deno Source: https://upstash-redis-js.mintlify.app/platforms/deno Use Upstash Redis SDK in Deno and Deno Deploy The Upstash Redis SDK works seamlessly with Deno and Deno Deploy without any additional configuration. ## Installation Import directly from a CDN: ```typescript theme={null} import { Redis } from "https://esm.sh/@upstash/redis@latest"; ``` ```typescript theme={null} import { Redis } from "https://esm.sh/@upstash/redis@1.30.2"; ``` ```typescript theme={null} import { Redis } from "https://deno.land/x/upstash_redis/mod.ts"; ``` Using `@latest` ensures you always get the newest version. For production, consider pinning to a specific version. ## Quick Start ```typescript theme={null} import { serve } from "https://deno.land/std@0.224.0/http/server.ts"; import { Redis } from "https://esm.sh/@upstash/redis@latest"; ``` ```typescript theme={null} const redis = Redis.fromEnv(); ``` This reads from: * `UPSTASH_REDIS_REST_URL` or `KV_REST_API_URL` * `UPSTASH_REDIS_REST_TOKEN` or `KV_REST_API_TOKEN` ```typescript theme={null} const redis = new Redis({ url: "", token: "", }); ``` ```typescript theme={null} serve(async (_req: Request) => { const redis = Redis.fromEnv(); const counter = await redis.incr("deno-deploy-counter"); return new Response(JSON.stringify({ counter }), { status: 200 }); }); ``` ## Configuration Options Deno uses the Node.js implementation, supporting all standard configuration options: ```typescript theme={null} const redis = new Redis({ url: "", token: "", // Enable keep-alive connections keepAlive: true, // Abort requests using AbortSignal signal: abortController.signal, // Enable latency logging latencyLogging: true, // Automatic deserialization of JSON values automaticDeserialization: true, // Read-your-writes consistency readYourWrites: true, // Enable telemetry (default: true) enableTelemetry: true, // Configure retry behavior retry: { retries: 3, backoff: (retryCount) => Math.exp(retryCount) * 50, }, // Response encoding responseEncoding: "base64", // Cache control cache: "no-store", }); ``` ### Using fromEnv() with Options ```typescript theme={null} const redis = Redis.fromEnv({ keepAlive: true, latencyLogging: true, automaticDeserialization: true, }); ``` ## Complete Example ```typescript theme={null} import { serve } from "https://deno.land/std@0.224.0/http/server.ts"; import { Redis } from "https://esm.sh/@upstash/redis@latest"; serve(async (req: Request) => { const redis = Redis.fromEnv(); const url = new URL(req.url); // Increment page view counter const counter = await redis.incr("deno-deploy-counter"); // Store visit information await redis.lpush("recent-visits", { path: url.pathname, timestamp: Date.now(), }); // Get recent visits const recentVisits = await redis.lrange("recent-visits", 0, 9); return new Response( JSON.stringify({ counter, path: url.pathname, recentVisits, }), { status: 200, headers: { "Content-Type": "application/json" }, } ); }); ``` ## Environment Variables Set environment variables for your Deno Deploy project: 1. Go to your project settings 2. Navigate to **Environment Variables** 3. Add: * `UPSTASH_REDIS_REST_URL`: Your Redis REST URL * `UPSTASH_REDIS_REST_TOKEN`: Your Redis REST token Create a `.env` file: ```bash theme={null} UPSTASH_REDIS_REST_URL=https://your-redis-url.upstash.io UPSTASH_REDIS_REST_TOKEN=your-token-here ``` Load it using: ```bash theme={null} deno run --allow-net --allow-env --env .env your-script.ts ``` ```bash theme={null} deno run --allow-net --allow-env \ --env=UPSTASH_REDIS_REST_URL=https://... \ --env=UPSTASH_REDIS_REST_TOKEN=... \ your-script.ts ``` ## Permissions Deno requires explicit permissions. Your script needs: ```bash theme={null} deno run --allow-net --allow-env your-script.ts ``` * `--allow-net`: Required for HTTP requests to Upstash Redis * `--allow-env`: Required for reading environment variables (when using `fromEnv()`) ## Deployment to Deno Deploy ```bash theme={null} deno install --allow-read --allow-write --allow-env --allow-net --allow-run \ --no-check -r -f https://deno.land/x/deploy/deployctl.ts ``` ```bash theme={null} deployctl deploy --project=your-project-name your-script.ts ``` Use the Deno Deploy dashboard to add your `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`. ## Type Safety The SDK includes TypeScript definitions. Deno will automatically use them: ```typescript theme={null} import type { Redis } from "https://esm.sh/@upstash/redis@latest"; const redis: Redis = Redis.fromEnv(); // Type-safe operations const count: number | null = await redis.get("counter"); const list: string[] = await redis.lrange("mylist", 0, -1); ``` ## Testing Example test file: ```typescript theme={null} import { assertEquals } from "https://deno.land/std@0.224.0/testing/asserts.ts"; import { Redis } from "https://esm.sh/@upstash/redis@latest"; Deno.test("Redis operations", async () => { const redis = new Redis({ url: Deno.env.get("UPSTASH_REDIS_REST_URL")!, token: Deno.env.get("UPSTASH_REDIS_REST_TOKEN")!, }); await redis.set("test-key", "test-value"); const value = await redis.get("test-key"); assertEquals(value, "test-value"); await redis.del("test-key"); }); ``` Run tests: ```bash theme={null} deno test --allow-net --allow-env ``` ## Platform Detection The SDK detects Deno and reports appropriate telemetry. To disable: ```typescript theme={null} const redis = Redis.fromEnv({ enableTelemetry: false, }); ``` Or set the environment variable: ```bash theme={null} UPSTASH_DISABLE_TELEMETRY=1 ``` ## Related * [Deno Example](https://github.com/upstash/redis-js/tree/main/examples/deno) - Complete example on GitHub * [Deno Deploy Documentation](https://deno.com/deploy/docs) - Official Deno Deploy docs * [Configuration](/concepts/client-configuration) - Complete configuration reference # Fastly Compute@Edge Source: https://upstash-redis-js.mintlify.app/platforms/fastly Use Upstash Redis SDK with Fastly Compute@Edge The Upstash Redis SDK works seamlessly with Fastly Compute\@Edge, allowing you to use Redis at the edge. ## Installation ```bash theme={null} npm install @upstash/redis ``` ## Quick Start ```javascript theme={null} import { Redis } from "@upstash/redis/fastly"; ``` Always use `@upstash/redis/fastly` when deploying to Fastly Compute\@Edge. Create or update your `fastly.toml` file to define your Upstash Redis backend: ```toml theme={null} # fastly.toml manifest_version = 2 name = "your-app-name" description = "Your Fastly Compute@Edge app" authors = ["you@example.com"] language = "javascript" [local_server.backends.upstash-db] url = "" ``` The backend name (e.g., `upstash-db`) must match the `backend` parameter in your Redis configuration. ```javascript theme={null} const redis = new Redis({ url: "", token: "", backend: "upstash-db", // Must match the backend name in fastly.toml }); ``` ```javascript theme={null} addEventListener("fetch", (event) => event.respondWith(handleRequest(event))); async function handleRequest(_event) { try { const count = await redis.incr("fastly-counter"); return new Response(JSON.stringify({ count }), { headers: { "Content-Type": "application/json" }, }); } catch (err) { return new Response(err.message, { status: 500 }); } } ``` ## Configuration Options The `backend` parameter is **required** for Fastly Compute\@Edge and must match a backend defined in your `fastly.toml` file. ```javascript theme={null} const redis = new Redis({ // Required: Your Upstash Redis REST URL url: "", // Required: Your Upstash Redis REST token token: "", // Required: Backend name from fastly.toml backend: "upstash-db", // Optional: Enable keep-alive connections keepAlive: true, // Optional: Read-your-writes consistency readYourWrites: true, // Optional: Automatic deserialization of JSON values automaticDeserialization: true, // Optional: Configure retry behavior retry: { retries: 3, backoff: (retryCount) => Math.exp(retryCount) * 50, }, // Optional: Response encoding responseEncoding: "base64", }); ``` ## Complete Example ### Project Structure ``` my-fastly-app/ ├── src/ │ └── index.js ├── fastly.toml ├── package.json └── webpack.config.js ``` ### fastly.toml ```toml theme={null} manifest_version = 2 name = "upstash-redis-fastly" description = "Example of using Upstash with Fastly Compute@Edge" authors = ["you@example.com"] language = "javascript" service_id = "" [local_server.backends.upstash-db] url = "" ``` ### src/index.js ```javascript theme={null} import { Redis } from "@upstash/redis/fastly"; const redis = new Redis({ url: "", token: "", backend: "upstash-db", }); addEventListener("fetch", (event) => event.respondWith(handleRequest(event))); async function handleRequest(event) { try { const url = new URL(event.request.url); // Increment page view counter const views = await redis.incr(`views:${url.pathname}`); // Store visitor data await redis.lpush("recent-visits", { path: url.pathname, timestamp: Date.now(), }); // Get recent visits const recentVisits = await redis.lrange("recent-visits", 0, 9); return new Response( JSON.stringify({ path: url.pathname, views, recentVisits, }), { headers: { "Content-Type": "application/json" }, } ); } catch (err) { return new Response(err.message, { status: 500 }); } } ``` ## Backend Configuration Fastly requires you to define backends for external services. Backends can be configured: ### 1. In fastly.toml (Local Development) ```toml theme={null} [local_server.backends.upstash-db] url = "" ``` ### 2. Via Fastly CLI ```bash theme={null} fastly backend create --name upstash-db --address your-redis-url.upstash.io ``` ### 3. Via Fastly Web Interface 1. Go to your service in the Fastly web interface 2. Navigate to **Origins** > **Hosts** 3. Click **Create a host** 4. Set the name to match your `backend` parameter (e.g., `upstash-db`) 5. Set the address to your Upstash Redis URL ## Environment Variables Fastly Compute\@Edge doesn't have built-in environment variable support like other platforms. You have several options: ### 1. Use Fastly Config Stores (Recommended) Config stores allow you to manage configuration separately from your code. ### 2. Use Fastly Edge Dictionaries Edge dictionaries provide key-value storage for configuration. ### 3. Build-time Environment Variables Use webpack or your bundler to inject values at build time: ```javascript theme={null} // webpack.config.js const webpack = require('webpack'); module.exports = { plugins: [ new webpack.DefinePlugin({ 'process.env.UPSTASH_REDIS_REST_URL': JSON.stringify(process.env.UPSTASH_REDIS_REST_URL), 'process.env.UPSTASH_REDIS_REST_TOKEN': JSON.stringify(process.env.UPSTASH_REDIS_REST_TOKEN), }), ], }; ``` ## Deployment ```bash theme={null} npm run build ``` ```bash theme={null} fastly compute publish ``` ## Telemetry The SDK automatically reports `"fastly"` as the platform in telemetry data. Telemetry cannot be disabled via environment variables in Fastly. Set `enableAutoPipelining: false` in the Redis constructor if needed. ## Related * [Fastly Example](https://github.com/upstash/redis-js/tree/main/examples/fastly) - Complete example on GitHub * [Fastly Guide](https://blog.upstash.com/fastly-compute-edge-with-redis) - Step-by-step tutorial * [Configuration](/concepts/client-configuration) - Complete configuration reference # Node.js Source: https://upstash-redis-js.mintlify.app/platforms/nodejs Use Upstash Redis SDK in Node.js applications The Upstash Redis SDK works seamlessly in Node.js environments, including serverless functions, AWS Lambda, and traditional Node.js applications. ## Installation ```bash theme={null} npm install @upstash/redis ``` ## Quick Start The default export is optimized for Node.js: ```typescript theme={null} import { Redis } from "@upstash/redis"; ``` You can also use the explicit Node.js import: ```typescript theme={null} import { Redis } from "@upstash/redis/node"; ``` Use `fromEnv()` to automatically load credentials from environment variables: ```typescript theme={null} const redis = Redis.fromEnv(); ``` This will read from: * `UPSTASH_REDIS_REST_URL` or `KV_REST_API_URL` * `UPSTASH_REDIS_REST_TOKEN` or `KV_REST_API_TOKEN` The fallback variables (`KV_REST_API_URL` and `KV_REST_API_TOKEN`) provide compatibility with Vercel KV and other platforms. ```typescript theme={null} const redis = new Redis({ url: "", token: "", }); ``` ```typescript theme={null} const count = await redis.incr("counter"); console.log("Counter:", count); ``` ## Configuration Options The Node.js SDK supports several configuration options: ```typescript theme={null} const redis = new Redis({ url: "", token: "", // Node.js specific: HTTP(S) agent for connection pooling agent: new https.Agent({ keepAlive: true }), // Or use the simplified keepAlive option keepAlive: true, // Abort requests using AbortSignal signal: abortController.signal, // Enable latency logging latencyLogging: true, // Automatic deserialization of JSON values automaticDeserialization: true, // Read-your-writes consistency readYourWrites: true, // Enable telemetry (default: true) enableTelemetry: true, // Configure retry behavior retry: { retries: 3, backoff: (retryCount) => Math.exp(retryCount) * 50, }, // Response encoding responseEncoding: "base64", // Cache control cache: "no-store", }); ``` ### Connection Pooling with Agent For optimal performance in Node.js applications with multiple sequential requests, use an HTTP(S) agent: ```typescript theme={null} import https from "https"; import { Redis } from "@upstash/redis"; const redis = new Redis({ url: "", token: "", agent: new https.Agent({ keepAlive: true }), }); ``` The `agent` option is Node.js specific and not supported in edge runtimes like Vercel Edge Functions or Cloudflare Workers. ### Using fromEnv() with Options You can combine `fromEnv()` with additional configuration: ```typescript theme={null} const redis = Redis.fromEnv({ keepAlive: true, latencyLogging: true, automaticDeserialization: true, }); ``` ## Example Application ```typescript theme={null} import { Redis } from "@upstash/redis"; const redis = Redis.fromEnv(); async function run() { const count = await redis.incr("counter"); console.log("Counter:", count); } run(); ``` ## Platform Detection The SDK automatically detects the runtime environment and sets appropriate telemetry: * **Node.js**: Reports as `node@` (e.g., `node@18.17.0`) * **Edge Runtime**: Reports as `edge-light` when running in Vercel Edge Functions * **Platform**: Detects Vercel, AWS, or Upstash Console environments ## Environment Variables When using `Redis.fromEnv()`, the SDK looks for these environment variables via `process.env`: | Variable | Fallback | Description | | --------------------------- | ------------------- | ------------------------ | | `UPSTASH_REDIS_REST_URL` | `KV_REST_API_URL` | Your Redis REST URL | | `UPSTASH_REDIS_REST_TOKEN` | `KV_REST_API_TOKEN` | Your Redis REST token | | `UPSTASH_DISABLE_TELEMETRY` | - | Set to disable telemetry | If `process.env` is not available (e.g., in Cloudflare Workers), use the platform-specific import instead: `@upstash/redis/cloudflare` ## Related * [Vercel Guide](/platforms/vercel) - Using Redis with Vercel Functions * [Configuration](/concepts/client-configuration) - Complete configuration reference * [Examples](https://github.com/upstash/redis-js/tree/main/examples/nodejs) - Node.js examples on GitHub # Vercel Source: https://upstash-redis-js.mintlify.app/platforms/vercel Use Upstash Redis SDK with Vercel Functions and Edge Functions The Upstash Redis SDK works seamlessly with Vercel, supporting both Vercel Functions (Node.js runtime) and Vercel Edge Functions. ## Installation ```bash theme={null} npm install @upstash/redis ``` ## Quick Start ### API Route (Edge Runtime) ```typescript theme={null} // app/api/hello/route.ts import { Redis } from "@upstash/redis"; import { NextResponse } from "next/server"; const redis = Redis.fromEnv(); export async function GET() { const count = await redis.incr("counter"); return NextResponse.json({ count }); } export const runtime = "edge"; ``` ### API Route (Node.js Runtime) ```typescript theme={null} // app/api/hello/route.ts import { Redis } from "@upstash/redis"; import { NextResponse } from "next/server"; const redis = Redis.fromEnv(); export async function GET() { const count = await redis.incr("counter"); return NextResponse.json({ count }); } // Node.js runtime (default) ``` ### API Route ```typescript theme={null} // pages/api/hello.ts import { Redis } from "@upstash/redis"; import type { NextApiRequest, NextApiResponse } from "next"; const redis = Redis.fromEnv(); export default async function handler( req: NextApiRequest, res: NextApiResponse ) { const count = await redis.incr("counter"); res.status(200).json({ count }); } ``` ## Choosing the Right Import The Upstash Redis SDK automatically works with both Node.js and Edge runtimes: ```typescript theme={null} import { Redis } from "@upstash/redis"; ``` This single import works for: * **Vercel Functions** (Node.js runtime) * **Vercel Edge Functions** (Edge runtime) * The SDK automatically detects the runtime and adjusts accordingly You don't need different imports for different runtimes when using Vercel. The default import handles both! ## Configuration ### Using fromEnv() (Recommended) The easiest way to configure Redis is using `fromEnv()`, which automatically reads from environment variables: ```typescript theme={null} const redis = Redis.fromEnv(); ``` This reads from: * `UPSTASH_REDIS_REST_URL` or `KV_REST_API_URL` * `UPSTASH_REDIS_REST_TOKEN` or `KV_REST_API_TOKEN` ### Using Vercel KV Integration If you're using the [Vercel KV integration](https://vercel.com/docs/storage/vercel-kv), the environment variables are automatically set as: * `KV_REST_API_URL` * `KV_REST_API_TOKEN` The SDK automatically falls back to these, so `Redis.fromEnv()` works out of the box! ### Manual Configuration ```typescript theme={null} const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); ``` ### With Options ```typescript theme={null} const redis = Redis.fromEnv({ automaticDeserialization: true, latencyLogging: true, }); ``` ## Environment Variables 1. Go to your project settings in Vercel 2. Navigate to **Environment Variables** 3. Add: * `UPSTASH_REDIS_REST_URL`: Your Redis REST URL * `UPSTASH_REDIS_REST_TOKEN`: Your Redis REST token ```bash theme={null} # .env.local UPSTASH_REDIS_REST_URL=https://your-redis-url.upstash.io UPSTASH_REDIS_REST_TOKEN=your-token-here ``` Never commit `.env.local` to version control. Add it to `.gitignore`. The [Vercel KV integration](https://vercel.com/integrations/upstash) automatically sets up the required environment variables. ## Configuration Options ### Node.js Runtime (Vercel Functions) ```typescript theme={null} const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, // Connection pooling (Node.js only) keepAlive: true, // Latency logging latencyLogging: true, // Automatic deserialization automaticDeserialization: true, // Read-your-writes consistency readYourWrites: true, // Cache control cache: "no-store", // Retry configuration retry: { retries: 3, backoff: (retryCount) => Math.exp(retryCount) * 50, }, }); ``` ### Edge Runtime (Vercel Edge Functions) ```typescript theme={null} const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, // Keep-alive (Edge compatible) keepAlive: true, // Automatic deserialization automaticDeserialization: true, // Read-your-writes consistency readYourWrites: true, // Retry configuration retry: { retries: 3, backoff: (retryCount) => Math.exp(retryCount) * 50, }, }); ``` The `agent` option is **not supported** in Edge Runtime. Use `keepAlive: true` instead. ## Complete Examples ### Next.js App Router with Edge Runtime ```typescript theme={null} // app/api/counter/route.ts import { Redis } from "@upstash/redis"; import { NextResponse } from "next/server"; const redis = Redis.fromEnv(); export async function GET() { const count = await redis.incr("page-views"); return NextResponse.json({ count }); } export async function POST(request: Request) { const { key, value } = await request.json(); await redis.set(key, value); return NextResponse.json({ success: true }); } export const runtime = "edge"; ``` ### Next.js App Router with Node.js Runtime ```typescript theme={null} // app/api/data/route.ts import { Redis } from "@upstash/redis"; import { NextResponse } from "next/server"; const redis = Redis.fromEnv({ keepAlive: true, }); export async function GET() { const count = await redis.incr("counter"); const data = await redis.get("mydata"); return NextResponse.json({ count, data }); } ``` ### Next.js Pages Router ```typescript theme={null} // pages/api/hello.ts import { Redis } from "@upstash/redis"; import type { NextApiRequest, NextApiResponse } from "next"; const redis = Redis.fromEnv(); export default async function handler( req: NextApiRequest, res: NextApiResponse ) { if (req.method === "GET") { const count = await redis.incr("counter"); res.status(200).json({ count }); } else if (req.method === "POST") { const { key, value } = req.body; await redis.set(key, value); res.status(200).json({ success: true }); } else { res.status(405).json({ error: "Method not allowed" }); } } ``` ## Runtime Detection The SDK automatically detects the Vercel runtime: * **Edge Runtime**: Reports as `edge-light` in telemetry * **Node.js Runtime**: Reports as `node@` in telemetry * **Platform**: Automatically detected as `vercel` ## Performance Tips ### 1. Enable Keep-Alive ```typescript theme={null} const redis = Redis.fromEnv({ keepAlive: true }); ``` ### 2. Use Edge Runtime for Global Performance Edge Functions run closer to your users: ```typescript theme={null} export const runtime = "edge"; ``` ### 3. Enable Auto-Pipelining ```typescript theme={null} const redis = Redis.fromEnv({ enableAutoPipelining: true }); ``` ## Troubleshooting ### "Unable to find environment variable" Make sure environment variables are set in your Vercel project settings or `.env.local` file. ### Edge Runtime Errors If using the `agent` option, remove it for Edge Runtime: ```typescript theme={null} // ❌ Don't use in Edge Runtime const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, agent: new https.Agent({ keepAlive: true }), // Not supported in Edge }); // ✅ Use this instead const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, keepAlive: true, }); ``` ## Related * [Vercel App Router Example](https://github.com/upstash/redis-js/tree/main/examples/vercel-functions-app-router) - Complete example * [Vercel Pages Router Example](https://github.com/upstash/redis-js/tree/main/examples/vercel-functions-pages-router) - Complete example * [Vercel KV Documentation](https://vercel.com/docs/storage/vercel-kv) - Official Vercel KV docs * [Configuration](/concepts/client-configuration) - Complete configuration reference # Quick Start Source: https://upstash-redis-js.mintlify.app/quickstart Get started with Upstash Redis in under 5 minutes Get up and running with Upstash Redis in under 5 minutes. This guide will walk you through creating a Redis database, installing the SDK, and executing your first commands. ## Prerequisites Before you begin, you'll need: * A free [Upstash account](https://console.upstash.com) * Node.js 14 or higher (or Bun/Deno) * A code editor ## Step 1: Create a Redis database Go to [console.upstash.com](https://console.upstash.com) and create a free account. Click **Create Database** in the Upstash Console. * Choose a name for your database * Select a region close to your users * Click **Create** After creation, you'll see your database details. Copy: * **UPSTASH\_REDIS\_REST\_URL** - Your database REST URL * **UPSTASH\_REDIS\_REST\_TOKEN** - Your authentication token Click the `.env` button in the console to copy both values in the correct format. ## Step 2: Install the SDK Install the Upstash Redis SDK using your preferred package manager: ```bash npm theme={null} npm install @upstash/redis ``` ```bash pnpm theme={null} pnpm add @upstash/redis ``` ```bash yarn theme={null} yarn add @upstash/redis ``` ```bash bun theme={null} bun add @upstash/redis ``` ## Step 3: Set up environment variables Create a `.env` file in your project root and add your credentials: ```bash .env theme={null} UPSTASH_REDIS_REST_URL="your_rest_url_here" UPSTASH_REDIS_REST_TOKEN="your_token_here" ``` Never commit your `.env` file to version control. Add it to your `.gitignore` file. ## Step 4: Write your first code Create a new file `index.ts` (or `index.js`) and add: ```typescript index.ts theme={null} import { Redis } from '@upstash/redis'; // Create a Redis client from environment variables const redis = Redis.fromEnv(); async function main() { // Set a value await redis.set('greeting', 'Hello, Upstash!'); // Get the value const greeting = await redis.get('greeting'); console.log(greeting); // Output: Hello, Upstash! // Increment a counter const views = await redis.incr('page-views'); console.log(`Page views: ${views}`); // Set a value with expiration (60 seconds) await redis.set('session', { userId: '123' }, { ex: 60 }); // Work with hashes await redis.hset('user:1', { name: 'Alice', email: 'alice@example.com', age: '30' }); const user = await redis.hgetall('user:1'); console.log('User:', user); } main().catch(console.error); ``` ## Step 5: Run your code Execute your script: ```bash Node.js with tsx theme={null} npx tsx index.ts ``` ```bash Node.js theme={null} node index.js ``` ```bash Bun theme={null} bun index.ts ``` ```bash Deno theme={null} deno run --allow-env --allow-net index.ts ``` You should see output similar to: ``` Hello, Upstash! Page views: 1 User: { name: 'Alice', email: 'alice@example.com', age: '30' } ``` Congratulations! You've successfully connected to Upstash Redis and executed your first commands. ## Common patterns ### TypeScript support The SDK is fully typed. Use generics for type safety: ```typescript theme={null} interface User { name: string; email: string; age: number; } // Type-safe operations await redis.set('user:1', { name: 'Bob', email: 'bob@example.com', age: 25 }); const user = await redis.get('user:1'); // user is typed as User | null ``` ### Working with JSON Use the built-in JSON support for complex objects: ```typescript theme={null} await redis.json.set('product:1', '$', { id: 1, name: 'Laptop', price: 999.99, specs: { cpu: 'Intel i7', ram: '16GB' }, tags: ['electronics', 'computers'] }); // Get the entire object const product = await redis.json.get('product:1', '$'); // Update nested fields await redis.json.set('product:1', '$.price', 899.99); // Append to arrays await redis.json.arrappend('product:1', '$.tags', 'sale'); ``` ### Batch operations with pipelines Execute multiple commands efficiently: ```typescript theme={null} const pipeline = redis.pipeline(); pipeline.set('user:1', 'Alice'); pipeline.set('user:2', 'Bob'); pipeline.set('user:3', 'Charlie'); pipeline.get('user:1'); pipeline.get('user:2'); const results = await pipeline.exec(); console.log(results); // ['OK', 'OK', 'OK', 'Alice', 'Bob'] ``` ### Auto-pipelining Enable auto-pipelining for automatic batching: ```typescript theme={null} const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, enableAutoPipelining: true }); // These commands are automatically batched const [user, posts, count] = await Promise.all([ redis.get('user:123'), redis.lrange('posts:123', 0, 10), redis.incr('views:123') ]); ``` ## Platform-specific setup ### Cloudflare Workers ```typescript theme={null} import { Redis } from '@upstash/redis/cloudflare'; export interface Env { UPSTASH_REDIS_REST_URL: string; UPSTASH_REDIS_REST_TOKEN: string; } export default { async fetch(request: Request, env: Env): Promise { const redis = Redis.fromEnv(env); const count = await redis.incr('counter'); return new Response(`Count: ${count}`); } }; ``` ### Vercel Edge Functions ```typescript theme={null} import { Redis } from '@upstash/redis'; export const config = { runtime: 'edge', }; export default async function handler() { const redis = Redis.fromEnv(); const value = await redis.get('key'); return new Response(JSON.stringify({ value }), { headers: { 'content-type': 'application/json' } }); } ``` ### Next.js App Router ```typescript app/api/data/route.ts theme={null} import { Redis } from '@upstash/redis'; import { NextResponse } from 'next/server'; const redis = Redis.fromEnv(); export async function GET() { const data = await redis.get('mykey'); return NextResponse.json({ data }); } export async function POST(request: Request) { const body = await request.json(); await redis.set('mykey', body); return NextResponse.json({ success: true }); } ``` ## What's next? Learn about client configuration, pipelines, and advanced features Explore all available Redis commands and methods Platform-specific setup for Cloudflare, Vercel, AWS, and more See real-world examples and best practices ## Troubleshooting If you're getting connection errors: 1. Verify your credentials in the Upstash Console 2. Check that your environment variables are loaded correctly 3. Ensure your URL starts with `https://` 4. Make sure your database region is accessible from your deployment region For TypeScript errors: 1. Ensure you have TypeScript 4.5 or higher 2. Add `"moduleResolution": "node"` to your `tsconfig.json` 3. Install the latest SDK version: `npm install @upstash/redis@latest` If your data isn't persisting: 1. Check if you set an expiration time that's too short 2. Verify you're not using a different database/environment 3. Make sure you're awaiting async operations ## Get help Need assistance? We're here to help: * Join our [Discord community](https://upstash.com/discord) * Check the [GitHub repository](https://github.com/upstash/redis-js) * Read the [full documentation](/introduction) * Contact [Upstash support](https://upstash.com/support)