Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/better-auth/better-hub/llms.txt

Use this file to discover all available pages before exploring further.

Better Hub uses Redis for caching GitHub API responses, reducing API rate limit consumption and improving performance. The application uses Upstash Redis for serverless-friendly Redis access via REST API.

Why Redis?

Better Hub caches:
  • GitHub API responses - Issues, pull requests, repositories, user data
  • GitHub user profiles - Authenticated user information
  • GitHub ETags - For conditional API requests
  • Temporary session data - Fast session lookups
Caching reduces GitHub API calls by up to 90%, staying well within rate limits.

Upstash Setup (Production)

1

Create Upstash account

Sign up at Upstash Console.
2

Create Redis database

  1. Click Create Database
  2. Select a region close to your application
  3. Choose Regional (or Global for multi-region)
  4. Enable TLS (recommended)
3

Get REST credentials

In your database dashboard, find:
  • REST URL: https://[endpoint].upstash.io
  • REST Token: Your authentication token
4

Configure environment variables

Add to your .env file:
UPSTASH_REDIS_REST_URL=https://your-endpoint.upstash.io
UPSTASH_REDIS_REST_TOKEN=your_rest_token_here
Keep your REST token secret. Never commit it to version control or expose it in client-side code.

Local Development Setup

Run Redis locally with serverless-redis-http proxy:
1

Start Redis with Docker Compose

docker-compose up -d redis
This starts:
  • Redis on port 6379
  • serverless-redis-http proxy on port 8079
2

Configure local environment

Add to .env:
UPSTASH_REDIS_REST_URL=http://localhost:8079
UPSTASH_REDIS_REST_TOKEN=local_token
The proxy provides an Upstash-compatible REST API over local Redis.

Option 2: Upstash Free Tier

Use Upstash free tier for development:
  • 10,000 commands/day
  • 256 MB storage
  • Perfect for development and testing
Just create a database and use the production-like REST API locally.

Redis Client Configuration

Better Hub uses @upstash/redis for serverless-optimized Redis access.

Client Initialization

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

const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL!,
  token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
See implementation in apps/web/src/lib/redis.ts.

Caching GitHub API Responses

// Cache with 1 hour TTL
await redis.set(
  `github_user:${token}`,
  JSON.stringify(userData),
  { ex: 3600 } // 3600 seconds = 1 hour
);

// Retrieve cached data
const cached = await redis.get(`github_user:${token}`);
Example from apps/web/src/lib/auth.ts:19-26.

Caching Strategies

GitHub User Data

Cache Key: github_user:{token_hash} TTL: 1 hour Purpose: Avoid repeated GET /user API calls
const cached = await redis.get<GitHubUser>(`github_user:${hash}`);
if (cached) return cached;

// Fetch from GitHub API if not cached
const githubUser = await octokit.users.getAuthenticated();
await redis.set(`github_user:${hash}`, JSON.stringify(githubUser.data), {
  ex: 3600
});

GitHub API Responses

Cache Key: github:{userId}:{endpoint}:{params_hash} TTL: Varies by endpoint (5 minutes to 24 hours) Purpose: Cache repository data, issues, PRs, etc.
const cacheKey = `github:${userId}:repos:${owner}/${repo}`;
const cached = await redis.get(cacheKey);

if (cached) {
  return JSON.parse(cached);
}

const data = await octokit.repos.get({ owner, repo });
await redis.set(cacheKey, JSON.stringify(data), { ex: 1800 }); // 30 min

ETag-Based Conditional Caching

GitHub API supports ETags for conditional requests:
const etag = await redis.get(`github_etag:${cacheKey}`);

const response = await octokit.repos.get({
  owner,
  repo,
  headers: etag ? { 'If-None-Match': etag } : {},
});

if (response.status === 304) {
  // Not modified, use cached data
  return await redis.get(cacheKey);
}

// Update cache and ETag
await redis.set(cacheKey, JSON.stringify(response.data), { ex: 3600 });
await redis.set(`github_etag:${cacheKey}`, response.headers.etag, { ex: 3600 });
This reduces GitHub API consumption even further.

Cache Invalidation

Manual Invalidation

Invalidate specific cache entries:
await redis.del(`github:${userId}:repos:${owner}/${repo}`);

Pattern-Based Invalidation

Invalidate all caches for a user:
const keys = await redis.keys(`github:${userId}:*`);
if (keys.length > 0) {
  await redis.del(...keys);
}
keys() is expensive on large datasets. Use sparingly and consider prefix-based deletion strategies.

TTL-Based Expiration

Most caches use automatic TTL expiration:
  • Short-lived data (issues, PRs): 5-15 minutes
  • Medium-lived data (repositories, users): 30-60 minutes
  • Long-lived data (organization info): 24 hours

Performance Optimization

Batch Operations

Use pipeline for multiple operations:
const pipeline = redis.pipeline();

pipeline.set('key1', 'value1');
pipeline.set('key2', 'value2');
pipeline.set('key3', 'value3');

await pipeline.exec();
Reduces round trips from 3 to 1.

JSON Compression

Compress large JSON before caching:
import { compress, decompress } from 'lz-string';

const compressed = compress(JSON.stringify(largeData));
await redis.set(key, compressed, { ex: 3600 });

const cached = await redis.get(key);
if (cached) {
  const data = JSON.parse(decompress(cached));
}
Reduces storage and transfer costs.

Cache Warming

Pre-populate cache for common queries:
// On user login, warm cache with frequently accessed data
await Promise.all([
  warmUserRepos(userId),
  warmUserIssues(userId),
  warmUserPRs(userId),
]);

Monitoring and Debugging

Upstash Console

Monitor Redis usage in Upstash Console:
  • Commands/sec - Request rate
  • Storage - Memory usage
  • Latency - Response times
  • Slow commands - Performance bottlenecks

Cache Hit Rate

Track cache effectiveness:
let hits = 0;
let misses = 0;

const getCached = async (key: string) => {
  const cached = await redis.get(key);
  if (cached) {
    hits++;
    return cached;
  }
  misses++;
  return null;
};

// Calculate hit rate
const hitRate = (hits / (hits + misses)) * 100;
console.log(`Cache hit rate: ${hitRate.toFixed(2)}%`);
Aim for 80%+ hit rate for frequently accessed data.

Debug Logging

Log cache operations in development:
const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL!,
  token: process.env.UPSTASH_REDIS_REST_TOKEN!,
  enableTelemetry: process.env.NODE_ENV === 'production',
});

Cost Optimization

Upstash Pricing

Upstash bills based on:
  • Commands - Number of Redis commands executed
  • Storage - Data stored in memory
  • Bandwidth - Data transfer
Free tier:
  • 10,000 commands/day
  • 256 MB storage
Pro tier:
  • $0.20 per 100K commands
  • $0.25 per GB storage

Reduce Costs

1

Optimize TTLs

Don’t cache longer than necessary. Use appropriate TTLs for each data type.
2

Use compression

Compress large JSON to reduce storage costs.
3

Batch operations

Use pipelines to reduce command count.
4

Clean up stale data

Regularly delete unused keys:
redis-cli --scan --pattern 'old_pattern:*' | xargs redis-cli del

Security

REST API Security

Security checklist:
  1. Never expose UPSTASH_REDIS_REST_TOKEN in client-side code
  2. Use HTTPS URLs in production (https://...)
  3. Enable TLS on Upstash database
  4. Rotate tokens periodically
  5. Use IP whitelisting if available

Data Encryption

Upstash Redis supports:
  • TLS encryption in transit - Enabled by default on REST API
  • Encryption at rest - Available on paid plans
For sensitive data, encrypt before caching:
import { encrypt, decrypt } from '@/lib/crypto';

const encrypted = encrypt(sensitiveData, process.env.ENCRYPTION_KEY!);
await redis.set(key, encrypted, { ex: 3600 });

const cached = await redis.get(key);
if (cached) {
  const decrypted = decrypt(cached, process.env.ENCRYPTION_KEY!);
}

Troubleshooting

Connection Failed

Error: Failed to fetch or connection timeout Solutions:
  • Verify UPSTASH_REDIS_REST_URL is correct
  • Check UPSTASH_REDIS_REST_TOKEN is valid
  • Ensure no firewall blocking outbound HTTPS
  • For local development, verify Docker container is running

Authentication Error

Error: Unauthorized or Invalid token Solutions:
  • Regenerate REST token in Upstash Console
  • Verify no extra spaces in .env file
  • Check token hasn’t expired (tokens don’t expire by default)

Rate Limit Exceeded

Error: Too many requests Solutions:
  • Upgrade Upstash plan
  • Optimize number of Redis commands (use pipelines)
  • Increase cache TTLs to reduce churn

Memory Limit Exceeded

Error: OOM or storage limit Solutions:
  • Enable eviction policy: allkeys-lru (evict least recently used)
  • Reduce TTLs for large objects
  • Compress data before caching
  • Upgrade to larger plan

Advanced Configuration

Custom Eviction Policy

Configure in Upstash Console:
  • noeviction - Return errors when memory limit reached
  • allkeys-lru - Evict least recently used keys (recommended)
  • allkeys-lfu - Evict least frequently used keys
  • volatile-ttl - Evict keys with shortest TTL

Global Replication

Upstash supports multi-region replication:
  1. Create Global Database in Upstash Console
  2. Select primary and replica regions
  3. Use same REST URL - Upstash routes to nearest region
Reduces latency for global users.

Read Replicas

For read-heavy workloads, use separate read endpoints:
const writeRedis = new Redis({
  url: process.env.UPSTASH_REDIS_WRITE_URL!,
  token: process.env.UPSTASH_REDIS_WRITE_TOKEN!,
});

const readRedis = new Redis({
  url: process.env.UPSTASH_REDIS_READ_URL!,
  token: process.env.UPSTASH_REDIS_READ_TOKEN!,
});
Scales read capacity independently.

Build docs developers (and LLMs) love