Claude Code for Upstash Redis — Workflow Guide

Written by Michael Lip · Solo founder of Zovo · $400K+ on Upwork · 100% JSS Join 50+ builders · More at zovo.one

The Setup

You are adding serverless Redis for caching, rate limiting, or session storage using Upstash. Unlike traditional Redis that needs a persistent connection, Upstash works over HTTP — making it ideal for serverless environments like Vercel, Cloudflare Workers, and AWS Lambda. Claude Code can set up caching patterns and rate limiters, but it defaults to the wrong Redis client and connection model.

What Claude Code Gets Wrong By Default

  1. Uses the redis or ioredis npm package. Claude installs the standard Redis client that needs TCP connections. Upstash requires @upstash/redis which communicates over HTTP REST, working in serverless where TCP connections are not available.

  2. Creates persistent connection pools. Claude writes createClient() with connection pooling logic. Upstash is stateless HTTP — every request is independent. There are no connections to pool or manage.

  3. Misses the REST token authentication. Claude tries to connect with redis://host:port URLs. Upstash uses UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN — a URL and bearer token pair, not a connection string.

  4. Ignores Upstash-specific rate limiting. Claude builds custom rate limiters with MULTI/EXEC transactions. Upstash has @upstash/ratelimit with pre-built sliding window, fixed window, and token bucket algorithms.

The CLAUDE.md Configuration

# Upstash Redis Serverless Project

## Architecture
- Cache: Upstash Redis (HTTP-based, serverless)
- Client: @upstash/redis (NOT ioredis or redis packages)
- Rate limiting: @upstash/ratelimit
- Framework: Next.js on Vercel (serverless functions)

## Upstash Rules
- Use Redis.fromEnv() to initialize from UPSTASH_REDIS_REST_URL/TOKEN
- All operations are async/await (HTTP calls, not TCP)
- No connection management needed — stateless HTTP
- Use pipeline() for batching multiple commands in one request
- TTL is in seconds for set() with { ex: seconds } option
- Rate limiter: new Ratelimit({ redis, limiter: Ratelimit.slidingWindow() })

## Conventions
- Redis client singleton in lib/redis.ts
- Cache keys use colon namespacing: "user:123:profile"
- Rate limiter in lib/ratelimit.ts
- All cached data has explicit TTL — never cache without expiration
- Never store secrets or tokens in Redis
- Use JSON.stringify/parse for object storage

Workflow Example

You want to add API rate limiting to protect your endpoints. Prompt Claude Code:

“Add rate limiting to the /api/generate endpoint using Upstash Ratelimit. Allow 10 requests per 10-second sliding window per IP address. Return a 429 with retry-after header when limited.”

Claude Code should create a rate limiter instance using @upstash/ratelimit with Ratelimit.slidingWindow(10, '10 s'), check the limit using the client IP as the identifier, and return appropriate headers including X-RateLimit-Remaining and Retry-After when the limit is exceeded.

Common Pitfalls

  1. Not using pipeline for multi-step operations. Claude makes individual Redis calls in loops. Upstash charges per request and each is an HTTP round-trip. Use redis.pipeline() to batch commands into a single HTTP request.

  2. Serialization assumptions. Claude stores objects with redis.set('key', myObject). Upstash auto-serializes to JSON, but when reading back, Claude forgets that the data is already parsed — no need for an extra JSON.parse() on the result.

  3. Rate limiter identifier collisions. Claude uses req.ip directly, but behind Vercel’s proxy this can be undefined. Use req.headers.get('x-forwarded-for') or Vercel’s req.ip with a fallback to avoid null identifiers that share a single rate limit bucket.