Published on

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

Authors

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)

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.

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.