OneApp Docs
PackagesDatabase

@repo/db-upstash-redis

Redis caching, rate limiting, and session storage. Edge-compatible REST API. Cache expensive queries, limit API requests, store user sessions. Works in serverless and edge runtimes.

Quick Start

Add Redis caching in 5 minutes:

pnpm add @repo/db-upstash-redis

Cache database queries, rate limit API routes, store sessions. Skip to Quick Start →

Why @repo/db-upstash-redis?

Database queries are slow. Re-fetching the same data on every request wastes time and money. Users abuse APIs without rate limits. Sessions need fast storage. Traditional Redis requires persistent connections that don't work in serverless.

@repo/db-upstash-redis solves this with Upstash's REST-based Redis that works perfectly in serverless and edge environments.

Production-ready with automatic caching, rate limiting algorithms, session management, and edge runtime compatibility.

Use cases

  • Query caching — Cache expensive database queries, reduce response times from 500ms to 10ms
  • API rate limiting — Protect endpoints from abuse with sliding window or token bucket algorithms
  • Session storage — Store user sessions with automatic expiration
  • Real-time counters — Track page views, likes, votes with atomic operations
  • Feature flags — Fast lookups for feature toggles without hitting the database

How it works

@repo/db-upstash-redis provides a REST-based Redis client with caching and rate limiting utilities:

import { redis } from "@repo/db-upstash-redis";
import { cache } from "@repo/db-upstash-redis/cache";

// Cache expensive query
const getUser = cache(
  async (userId: string) => {
    return await prisma.user.findUnique({ where: { id: userId } });
  },
  { key: (userId) => `user:${userId}`, ttl: 3600 }
);

const user = await getUser("user_123"); // Cached for 1 hour

Uses Upstash REST API (works in edge/serverless), automatic cache invalidation, and multiple rate limiting algorithms.

Key features

Edge-compatible — REST API works in Vercel Edge, Cloudflare Workers, serverless functions

Automatic caching — Cache wrapper handles lookups, misses, and invalidation

Rate limiting — Sliding window, token bucket, and fixed window algorithms

Session storage — Built-in session management with TTL

Atomic operations — Counters, locks, and concurrent-safe updates

Pipeline support — Batch multiple operations in single network call

Quick Start

1. Install dependencies

pnpm add @repo/db-upstash-redis @upstash/redis @upstash/ratelimit

2. Configure Upstash connection

.env.local
UPSTASH_REDIS_REST_URL=https://xxx.upstash.io
UPSTASH_REDIS_REST_TOKEN=AXxx...

3. Cache a database query

app/lib/users.ts
import { cache } from "@repo/db-upstash-redis/cache";
import { prisma } from "@repo/db-prisma";

export const getUser = cache(
  async (userId: string) => {
    return await prisma.user.findUnique({
      where: { id: userId }
    });
  },
  {
    key: (userId) => `user:${userId}`,
    ttl: 3600 // Cache for 1 hour
  }
);

// Usage
const user = await getUser("user_123"); // Cached!

4. Add rate limiting to an API route

app/api/search/route.ts
import { ratelimit } from "@repo/db-upstash-redis/ratelimit";

const limiter = ratelimit({
  requests: 10, // 10 requests
  window: "60s" // per 60 seconds
});

export async function GET(req: Request) {
  const ip = req.headers.get("x-forwarded-for") ?? "anonymous";
  const { success } = await limiter.limit(ip);

  if (!success) {
    return new Response("Too Many Requests", { status: 429 });
  }

  // Handle request
  return Response.json({ results: [] });
}

That's it! You now have Redis caching and rate limiting with edge compatibility.

Stale-while-revalidate

Serve stale data instantly while fetching fresh data in background:

import { cacheSWR } from "@repo/db-upstash-redis/cache";

const data = await cacheSWR("key", fetchData, {
  ttl: 60, // Fresh for 60s
  staleFor: 300 // Serve stale for 5min while revalidating
});

Technical Details

For Developers: Technical implementation details

Upstash Redis integration for caching, session storage, rate limiting, and real-time features. Edge-compatible with REST API.

Installation

pnpm add @repo/db-upstash-redis

Overview

PropertyValue
Locationpackages/db-upstash-redis
Dependencies@upstash/redis, @upstash/ratelimit
Edge CompatibleYes

Export Paths

PathDescription
@repo/db-upstash-redisRedis client instance
@repo/db-upstash-redis/cacheCaching utilities
@repo/db-upstash-redis/ratelimitRate limiting

Basic Usage

Redis Client

import { redis } from "@repo/db-upstash-redis";

// String operations
await redis.set("key", "value");
// highlight-next-line
await redis.set("key", "value", { ex: 3600 }); // Expires in 1 hour
const value = await redis.get("key");

// Delete
await redis.del("key");

// Check existence
const exists = await redis.exists("key");

Hash Operations

Use Case

Hashes are ideal for storing objects like user profiles where you need to access individual fields.

import { redis } from "@repo/db-upstash-redis";

// Set hash field
// highlight-next-line
await redis.hset("user:123", { name: "John", email: "john@example.com" });

// Get hash field
const name = await redis.hget("user:123", "name");

// Get all fields
const user = await redis.hgetall("user:123");

// Delete field
await redis.hdel("user:123", "email");

List Operations

import { redis } from "@repo/db-upstash-redis";

// Push to list
await redis.lpush("queue", "item1", "item2");
await redis.rpush("queue", "item3");

// Pop from list
const item = await redis.lpop("queue");

// Get range
// highlight-next-line
const items = await redis.lrange("queue", 0, -1);

Set Operations

import { redis } from "@repo/db-upstash-redis";

// Add to set
await redis.sadd("tags", "javascript", "typescript", "react");

// Check membership
const isMember = await redis.sismember("tags", "javascript");

// Get all members
const tags = await redis.smembers("tags");

// Remove from set
await redis.srem("tags", "react");

Caching

Cache Wrapper

Automatic Caching

The cache wrapper automatically handles cache lookup, miss, and storage. Use it to wrap expensive database queries.

import { cache } from "@repo/db-upstash-redis/cache";

// Cache function result
// highlight-start
const getUser = cache(
  async (userId: string) => {
    return await prisma.user.findUnique({ where: { id: userId } });
  },
  {
    key: (userId) => `user:${userId}`,
    ttl: 3600 // 1 hour
  }
);
// highlight-end

const user = await getUser("user_123");

Manual Caching

import { cacheGet, cacheSet, cacheDelete } from "@repo/db-upstash-redis/cache";

// Get from cache
const cached = await cacheGet<User>("user:123");

if (!cached) {
  const user = await fetchUser("123");
  // highlight-next-line
  await cacheSet("user:123", user, { ttl: 3600 });
}

// Invalidate cache
await cacheDelete("user:123");

// Pattern delete
await cacheDelete("user:*");

Stale-While-Revalidate

SWR Pattern

SWR serves stale data immediately while fetching fresh data in the background. This provides fast responses with eventual consistency.

import { cacheSWR } from "@repo/db-upstash-redis/cache";

const data = await cacheSWR("api:data", async () => await fetchExpensiveData(), {
  // highlight-start
  ttl: 60, // Fresh for 60 seconds
  staleFor: 300 // Serve stale for 5 minutes while revalidating
  // highlight-end
});

Rate Limiting

Basic Rate Limiter

import { ratelimit } from "@repo/db-upstash-redis/ratelimit";

const limiter = ratelimit({
  // highlight-start
  requests: 10, // 10 requests
  window: "60s" // per 60 seconds
  // highlight-end
});

export async function POST(req: Request) {
  const ip = req.headers.get("x-forwarded-for") ?? "anonymous";

  const { success, remaining, reset } = await limiter.limit(ip);

  // highlight-start
  if (!success) {
    return new Response("Too Many Requests", {
      status: 429,
      headers: {
        "X-RateLimit-Remaining": remaining.toString(),
        "X-RateLimit-Reset": reset.toString()
      }
    });
  }
  // highlight-end

  // Handle request
}

Sliding Window

Accuracy

Sliding window is more accurate than fixed window for rate limiting as it prevents burst requests at window boundaries.

import { ratelimit } from "@repo/db-upstash-redis/ratelimit";

const limiter = ratelimit({
  requests: 100,
  window: "1h",
  // highlight-next-line
  algorithm: "sliding" // More accurate than fixed window
});

Token Bucket

import { ratelimit } from "@repo/db-upstash-redis/ratelimit";

const limiter = ratelimit({
  requests: 10,
  window: "10s",
  // highlight-start
  algorithm: "token-bucket",
  refillRate: 1 // 1 token per second
  // highlight-end
});

Session Storage

import { sessionStore } from "@repo/db-upstash-redis";

// Create session
// highlight-start
const sessionId = await sessionStore.create({
  userId: "user_123",
  data: { role: "admin" },
  ttl: 86400 // 24 hours
});
// highlight-end

// Get session
const session = await sessionStore.get(sessionId);

// Update session
await sessionStore.update(sessionId, {
  lastActivity: new Date().toISOString()
});

// Delete session
await sessionStore.delete(sessionId);

Pub/Sub

REST API Limitation

Upstash REST API doesn't support persistent subscriptions. For real-time features, consider using a WebSocket service or polling.

import { redis } from "@repo/db-upstash-redis";

// Publish
await redis.publish(
  "notifications",
  JSON.stringify({
    type: "new_message",
    userId: "user_123"
  })
);

// Note: For subscriptions, use a long-running process
// Upstash REST API doesn't support persistent subscriptions

Atomic Operations

import { redis } from "@repo/db-upstash-redis";

// Increment
// highlight-start
const count = await redis.incr("page:views");
const byAmount = await redis.incrby("page:views", 5);
// highlight-end

// Decrement
await redis.decr("stock:item_123");

// Set if not exists
const wasSet = await redis.setnx("lock:resource", "locked");

// Expire
await redis.expire("temp:data", 3600);

Pipeline

Performance

Use pipelines to batch multiple commands in a single network round-trip. This significantly improves performance for bulk operations.

import { redis } from "@repo/db-upstash-redis";

// highlight-start
const pipeline = redis.pipeline();

pipeline.set("key1", "value1");
pipeline.set("key2", "value2");
pipeline.get("key1");
pipeline.get("key2");

const results = await pipeline.exec();
// highlight-end
// [null, null, "value1", "value2"]

Environment Variables

# Upstash Redis
UPSTASH_REDIS_REST_URL="https://xxx.upstash.io"
UPSTASH_REDIS_REST_TOKEN="AXxx..."

On this page