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
Create Cloudflare Account
Install Wrangler CLI
Install the Cloudflare Workers CLI:
Create Upstash Redis Database
Quick Start
Project Setup
Clone Example or Create New Project
git clone https://github.com/upstash/upstash-redis.git
cd upstash-redis/examples/cloudflare-workers
Or create a new project: mkdir my-worker
cd my-worker
npm init -y
Install Dependencies
npm install @upstash/redis
npm install -D wrangler
Example: Counter Worker
This example implements a simple counter that increments on each request.
index.js
index.ts
wrangler.toml
package.json
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 }));
} ,
} ;
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)
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)
Select Your Worker
Click on your Worker or create a new one
Add Environment Variables
Go to Settings > Variables and add:
UPSTASH_REDIS_REST_URL
UPSTASH_REDIS_REST_TOKEN
Option 3: Wrangler CLI Secrets (Most Secure)
wrangler secret put UPSTASH_REDIS_REST_URL
wrangler secret put UPSTASH_REDIS_REST_TOKEN
Development
Run Locally
Start Development Server
npm run start
# or
wrangler dev
Deploy to Cloudflare
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
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 < Response > {
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 < number >( "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
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 < Response > {
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
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 < Response > {
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
import { Redis } from "@upstash/redis/cloudflare" ;
interface Session {
id : string ;
userId ?: string ;
createdAt : number ;
lastActivity : number ;
data : Record < string , any >;
}
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 < Response > {
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 >( `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:
wrangler init my-worker --type typescript
Define your environment interface:
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:
export default {
async fetch ( request : Request , env : Env ) : Promise < Response > {
const redis = Redis . fromEnv ( env ); // Initialize per request
// Use redis...
} ,
} ;
Error Handling
export default {
async fetch ( request : Request , env : Env ) : Promise < Response > {
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:
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
Test endpoints:
curl http://localhost:8787/counter
curl -X POST http://localhost:8787/counter/increment
Unit Testing
For unit testing Workers, see Cloudflare Workers testing documentation .
Monitoring
View Logs
Or view logs in the Cloudflare Dashboard .
Add Custom Logging
export default {
async fetch ( request : Request , env : Env ) : Promise < Response > {
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
Module not found: @upstash/redis/cloudflare
Make sure you’re using the correct import path: import { Redis } from "@upstash/redis/cloudflare" ;
Not: import { Redis } from "@upstash/redis" ; // Wrong for Workers!
Environment variables not accessible
Ensure variables are:
Set in wrangler.toml [vars] section, OR
Set in Cloudflare Dashboard, OR
Set as secrets via wrangler secret put
Access them via the env parameter: 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
AWS Lambda Deploy Redis with AWS Lambda
Next.js Integrate Redis with Next.js
Basic Usage Learn more Redis operations
Cloudflare Docs Cloudflare Workers documentation