← Back to articles
Backend UpstashRedisServerless

Upstash: Serverless Redis for Modern Web Applications

· 3 min read

Traditional Redis requires a running server, a fixed monthly cost, and connection management that doesn’t fit serverless architectures. Upstash solves this with a serverless Redis service that charges per request, works over HTTP, and runs natively on edge runtimes. For developers deploying on Vercel, Cloudflare Workers, or any serverless platform, it’s the simplest way to add caching, rate limiting, and background jobs without managing infrastructure.

HTTP-based Redis client

Standard Redis clients use persistent TCP connections. That doesn’t work in serverless functions where each invocation is isolated and short-lived. Upstash’s @upstash/redis client communicates over HTTP REST, which means no connection pooling, no timeouts, and no cold start penalties.

import { Redis } from '@upstash/redis';

const redis = Redis.fromEnv();

export async function getCached<T>(
  key: string,
  ttl: number,
  fetcher: () => Promise<T>
): Promise<T> {
  const cached = await redis.get<T>(key);
  if (cached) return cached;

  const data = await fetcher();
  await redis.set(key, data, { ex: ttl });
  return data;
}

Redis.fromEnv() reads UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN from environment variables. The client serializes and deserializes JSON automatically, so there’s no manual JSON.parse needed. Each call is a single HTTP request, fully compatible with edge runtimes where TCP sockets aren’t available.

Rate limiting with @upstash/ratelimit

Building rate limiting from scratch means handling sliding windows, token buckets, and atomic counters. Upstash packages all of this in a single library that works out of the box with Next.js middleware.

import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, '10 s'),
  analytics: true,
});

export async function middleware(request: NextRequest) {
  const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1';
  const { success, remaining } = await ratelimit.limit(ip);

  if (!success) {
    return NextResponse.json({ error: 'Too many requests' }, { status: 429 });
  }

  const response = NextResponse.next();
  response.headers.set('X-RateLimit-Remaining', String(remaining));
  return response;
}

export const config = { matcher: ['/api/:path*'] };

The sliding window algorithm distributes requests evenly instead of allowing bursts at window boundaries. This runs at the edge, meaning rate limiting happens before the request reaches the origin server. The analytics flag sends usage data to the Upstash dashboard for monitoring.

QStash for background jobs

Serverless functions have execution time limits. A Vercel function times out after 10 seconds on the hobby plan. Long-running tasks like sending emails, processing images, or syncing data need to run asynchronously. QStash is Upstash’s HTTP-based message queue that triggers webhooks with guaranteed delivery.

import { Client } from '@upstash/qstash';

const qstash = new Client({ token: process.env.QSTASH_TOKEN! });

export async function POST(request: Request) {
  const order = await request.json();
  await db.order.create({ data: order });

  await qstash.publishJSON({
    url: `${process.env.APP_URL}/api/jobs/send-confirmation`,
    body: { orderId: order.id, email: order.email },
    retries: 3,
    delay: '5s',
  });

  return Response.json({ status: 'created' });
}

The API route creates the order and enqueues the email job in a single request. QStash handles retries, delays, and dead letter queues. If the webhook endpoint fails, QStash retries up to three times with exponential backoff. No Redis queues to manage, no worker processes to keep alive.

Conclusion

Upstash fills the gap between managed databases and serverless compute. HTTP-based Redis that works on the edge, rate limiting that deploys in middleware, and background jobs that survive function timeouts. For TypeScript developers building on Vercel or similar platforms, it removes the infrastructure friction that usually comes with adding caching, queues, and async processing.