- Published on
Upstash — Serverless Redis, Kafka, and QStash for Event-Driven Architecture
- Authors

- Name
- Sanjeev Sharma
- @webcoderspeed1
Introduction
Serverless platforms (Vercel, Netlify, AWS Lambda) need primitives: caching, messaging, job queues. Traditional Redis and Kafka require always-on infrastructure. Upstash solves this with serverless alternatives: Redis, Kafka, and QStash, all with per-request pricing.
Pay only for what you use. No idle cost. Manage a few environment variables instead of database infrastructure.
- Upstash Redis for Serverless (Per-Request Pricing, No Idle Cost)
- Upstash Kafka for Managed Kafka Without Ops
- QStash for HTTP-Based Message Queuing (Schedule, Retry, Delay)
- QStash for Scheduled Jobs (Replaces Cron on Vercel)
- Combining Upstash Redis + QStash for Background Job Processing
- Upstash Vector for Serverless Vector Search
- Integrating Upstash With Vercel, Netlify, Cloudflare
- Cost Model at Different Scales
- When to Use Upstash
- Checklist
- Conclusion
Upstash Redis for Serverless (Per-Request Pricing, No Idle Cost)
Traditional Redis: $50-100/month for a managed instance, even if idle.
Upstash Redis: $0.20 per 10k read operations, $1.00 per 10k write operations. Unused databases cost nothing.
Sign up at https://upstash.com/. Create a database:
UPSTASH_REDIS_REST_URL=https://xxx.upstash.io
UPSTASH_REDIS_REST_TOKEN=xxxxx
Use it:
import { Redis } from '@upstash/redis';
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL,
token: process.env.UPSTASH_REDIS_REST_TOKEN
});
// Caching
await redis.set('user:123:profile', JSON.stringify(user), { ex: 3600 });
const cached = await redis.get('user:123:profile');
// Rate limiting
const key = `rate:${userId}:${Math.floor(Date.now() / 1000)}`;
const count = await redis.incr(key);
await redis.expire(key, 60);
if (count > 100) {
return new Response('Rate limited', { status: 429 });
}
// Leaderboards
await redis.zadd('leaderboard', {
score: score,
member: userId
});
const topPlayers = await redis.zrevrange('leaderboard', 0, 9, {
withScores: true
});
Upstash Redis feels like normal Redis but scales to zero on idle.
Upstash Kafka for Managed Kafka Without Ops
Set up Kafka without running brokers.
UPSTASH_KAFKA_URL=kafka-xxx.upstash.io:9092
UPSTASH_KAFKA_USERNAME=xxx
UPSTASH_KAFKA_PASSWORD=xxx
Producer:
import { Kafka } from 'kafkajs';
const kafka = new Kafka({
clientId: 'serverless-app',
brokers: [process.env.UPSTASH_KAFKA_URL],
ssl: true,
sasl: {
mechanism: 'scram-sha-256',
username: process.env.UPSTASH_KAFKA_USERNAME,
password: process.env.UPSTASH_KAFKA_PASSWORD
}
});
const producer = kafka.producer();
await producer.connect();
await producer.send({
topic: 'events',
messages: [
{
key: String(userId),
value: JSON.stringify({
event: 'purchase',
amount: 99.99,
timestamp: Date.now()
})
}
]
});
await producer.disconnect();
Consumer:
import { Kafka } from 'kafkajs';
const kafka = new Kafka({
clientId: 'serverless-consumer',
brokers: [process.env.UPSTASH_KAFKA_URL],
ssl: true,
sasl: {
mechanism: 'scram-sha-256',
username: process.env.UPSTASH_KAFKA_USERNAME,
password: process.env.UPSTASH_KAFKA_PASSWORD
}
});
const consumer = kafka.consumer({ groupId: 'analytics-group' });
await consumer.connect();
await consumer.subscribe({ topic: 'events' });
await consumer.run({
eachMessage: async ({ topic, partition, message }) => {
const event = JSON.parse(message.value?.toString() || '{}');
await processEvent(event);
}
});
Upstash Kafka works like normal Kafka but with serverless pricing. Perfect for event-driven apps.
QStash for HTTP-Based Message Queuing (Schedule, Retry, Delay)
QStash is HTTP-based job queueing. No Kafka, no Redis. Just POST to QStash and it retries for you.
QSTASH_TOKEN=xxxxx
Enqueue a job:
import { Client } from '@upstash/qstash';
const qstash = new Client({
token: process.env.QSTASH_TOKEN
});
// Immediate execution
await qstash.publishJSON({
url: 'https://api.example.com/webhooks/process',
body: { orderId: 123, userId: 456 }
});
// Delayed execution (5 minutes)
await qstash.publishJSON({
url: 'https://api.example.com/webhooks/process',
body: { orderId: 123 },
delay: 5 * 60
});
// Scheduled (cron)
await qstash.scheduleJSON({
cron: '0 9 * * *', // Every day at 9 AM
url: 'https://api.example.com/webhooks/daily-digest',
body: { userId: 456 }
});
Endpoint handler:
export default async function handler(
req: VercelRequest,
res: VercelResponse
) {
const { orderId, userId } = req.body;
try {
await processOrder(orderId);
await sendNotification(userId, 'Order processed!');
res.status(200).json({ success: true });
} catch (error) {
// QStash retries automatically
res.status(500).json({ error: error.message });
}
}
QStash handles retries, scheduling, and guaranteed delivery. No job queue infrastructure needed.
QStash for Scheduled Jobs (Replaces Cron on Vercel)
Vercel doesn't offer cron jobs (Vercel Crons is still in beta). Use QStash:
// api/cron/daily-email.ts
import { Client } from '@upstash/qstash';
const qstash = new Client({
token: process.env.QSTASH_TOKEN
});
export default async function POST(req) {
// This is called by QStash on schedule
const users = await db.collection('users').find({}).toArray();
for (const user of users) {
await sendEmail(user.email, 'Daily digest', getDigest(user));
}
return { success: true };
}
// In a separate endpoint, set up the cron
export async function setupCron() {
await qstash.scheduleJSON({
cron: '0 8 * * *', // Every day at 8 AM UTC
url: 'https://api.example.com/api/cron/daily-email',
body: {}
});
}
Access the QStash console to manage schedules visually.
Combining Upstash Redis + QStash for Background Job Processing
Cache results with Redis, queue jobs with QStash:
export async function generateReport(userId: string) {
// Check cache
const cached = await redis.get(`report:${userId}`);
if (cached) {
return JSON.parse(cached);
}
// Queue background job
await qstash.publishJSON({
url: 'https://api.example.com/api/jobs/generate-report',
body: { userId },
delay: 0, // Execute immediately
callback: {
url: 'https://api.example.com/api/callbacks/report-done',
headers: { Authorization: 'Bearer token' }
}
});
return { status: 'queued' };
}
// Job endpoint
export default async function handler(req: VercelRequest, res: VercelResponse) {
const { userId } = req.body;
try {
const report = await generateReportForUser(userId);
// Cache for future requests
await redis.set(
`report:${userId}`,
JSON.stringify(report),
{ ex: 86400 } // 24 hours
);
res.status(200).json({ success: true, report });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
Users get instant responses from cache. Reports generate in the background.
Upstash Vector for Serverless Vector Search
Semantic search without managing embeddings infrastructure:
UPSTASH_VECTOR_URL=https://xxx.upstash.io
UPSTASH_VECTOR_TOKEN=xxxxx
Upsert vectors:
import { Index } from '@upstash/vector';
const index = new Index({
url: process.env.UPSTASH_VECTOR_URL,
token: process.env.UPSTASH_VECTOR_TOKEN
});
// Embed and upsert
const embedding = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: 'A delicious chocolate cake recipe'
});
await index.upsert({
id: 'recipe-123',
values: embedding.data[0].embedding,
metadata: {
title: 'Chocolate Cake',
url: '/recipes/chocolate-cake'
}
});
Query:
const queryEmbedding = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: 'chocolate dessert'
});
const results = await index.query({
vector: queryEmbedding.data[0].embedding,
topK: 5,
includeMetadata: true
});
console.log(results);
// [
// { id: 'recipe-123', score: 0.92, metadata: { title: 'Chocolate Cake' } },
// { id: 'recipe-456', score: 0.88, metadata: { title: 'Brownie' } }
// ]
Upstash Vector scales from zero to millions of vectors.
Integrating Upstash With Vercel, Netlify, Cloudflare
Vercel:
// pages/api/hello.ts
import { Redis } from '@upstash/redis';
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL,
token: process.env.UPSTASH_REDIS_REST_TOKEN
});
export default async function handler(req, res) {
const count = await redis.incr('pageviews');
res.json({ pageviews: count });
}
Add env vars to .env.local and Vercel dashboard.
Netlify Functions:
// netlify/functions/api.ts
import { Redis } from '@upstash/redis';
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL,
token: process.env.UPSTASH_REDIS_REST_TOKEN
});
export const handler = async (event) => {
const count = await redis.incr('netlify-pageviews');
return {
statusCode: 200,
body: JSON.stringify({ count })
};
};
Cloudflare Workers:
// src/index.ts
import { Redis } from '@upstash/redis';
export default {
async fetch(request: Request) {
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL,
token: process.env.UPSTASH_REDIS_REST_TOKEN
});
const count = await redis.incr('cloudflare-pageviews');
return new Response(JSON.stringify({ count }), {
headers: { 'Content-Type': 'application/json' }
});
}
};
All three integrate seamlessly.
Cost Model at Different Scales
Small app (1000 requests/day):
- Redis reads: 1000 req * 5 reads = 5,000 ops =
$0.10/month - Redis writes: 1000 req * 2 writes = 2,000 ops =
$0.20/month - QStash: 1000 ops =
$0.10/month - Total: ~
$0.40/month
Medium app (100,000 requests/day):
- Redis: 500,000 reads =
$10+ 200,000 writes =$20=$30/month - QStash: 100,000 ops =
$10/month - Total: ~
$40/month
Large app (10M requests/day):
- Redis: 50M reads =
$1000+ 20M writes =$2000=$3000/month - QStash: 10M ops =
$1000/month - Total: ~
$4000/month
Compare to traditional:
- Redis instance:
$50-200/month - Kafka cluster:
$500-2000/month - Cron/job queue service:
$200-500/month
Upstash is cheaper until you hit massive scale. At that point, switch to dedicated infrastructure.
When to Use Upstash
✅ Use Upstash when:
- Running on Vercel, Netlify, or Cloudflare
- Serverless functions (AWS Lambda, Google Cloud Functions)
- Inconsistent traffic (nights/weekends are quiet)
- Small team managing infrastructure
- First MVP (iterate fast, optimize later)
❌ Don't use Upstash when:
- Massive scale (billions of requests/month)
- Strict latency requirements (<10ms p99)
- Need Redis modules or specific features
- Cost optimization matters more than engineering time
Checklist
- Create Upstash Redis database
- Add environment variables to deployment platform
- Implement caching with Upstash Redis
- Set up QStash for background jobs
- Configure scheduled jobs with cron
- Implement retries and error handling
- Monitor costs on Upstash console
- Test failover scenarios
- Document cost expectations
- Plan migration to dedicated infrastructure if needed
Conclusion
Upstash removes the friction of managing Redis, Kafka, and job queues in serverless environments. Per-request pricing means you never pay for idle infrastructure. Integrate with Vercel, Netlify, and Cloudflare with a few environment variables.
For early-stage projects and serverless-first teams, Upstash is the obvious choice. Start there. If you outgrow it, you'll have the revenue to justify managed services or dedicated infrastructure.
Serverless doesn't mean you can't have sophisticated event-driven architecture. Upstash makes it simple.