@founder-os/redis
v0.1.0
Published
Shared Redis client, distributed rate limiter, and SSO token store for FounderOS
Readme
@founder-os/redis
Shared Redis client, distributed rate limiter, and SSO token store for the FounderOS platform.
Setup — Railway Redis
1. Create a Redis service on Railway
- Go to your Railway project dashboard
- Click "+ New" → "Database" → "Redis"
- Railway auto-provisions the instance and sets
REDIS_URL/REDIS_PRIVATE_URL
2. Link the REDIS_URL variable
Railway automatically creates REDIS_URL for the Redis service. You need to reference it in each app service:
# In each app's Railway service settings → Variables
REDIS_URL=${{Redis.REDIS_URL}}
# Or for internal networking (faster, no egress cost):
REDIS_URL=${{Redis.REDIS_PRIVATE_URL}}3. Local development
For local dev, either:
Option A: Use Railway's local proxy
railway run pnpm devOption B: Run Redis locally
docker run -d --name founderos-redis -p 6379:6379 redis:7-alpine
# Then in .env:
REDIS_URL=redis://localhost:6379Option C: No Redis (graceful fallback)
The package works without Redis — it logs a warning and fails open. SSO and rate limiting will use in-memory/JWT-only fallbacks.
Usage
Basic Redis client
import { redis } from '@founder-os/redis';
await redis.set('key', 'value', 'EX', 60);
const val = await redis.get('key');Rate limiting
import { checkRateLimit, rateLimitHeaders, RATE_LIMITS } from '@founder-os/redis/rate-limiter';
// In an API route or middleware:
const ip = request.headers.get('x-forwarded-for') || 'unknown';
const result = await checkRateLimit(`api:${ip}`, RATE_LIMITS.API);
if (result.limited) {
return new Response('Too many requests', {
status: 429,
headers: rateLimitHeaders(result),
});
}
// Add rate limit headers to successful responses too
const response = NextResponse.json(data);
Object.entries(rateLimitHeaders(result)).forEach(([k, v]) => response.headers.set(k, v));
return response;Preset rate limits
| Preset | Requests | Window |
|--------|----------|--------|
| RATE_LIMITS.API | 100 | 1 min |
| RATE_LIMITS.AI | 20 | 1 min |
| RATE_LIMITS.AUTH | 10 | 1 min |
| RATE_LIMITS.UPLOAD | 5 | 1 min |
| RATE_LIMITS.WEBHOOK | 200 | 1 min |
SSO token replay prevention
Handled automatically by @founder-os/auth/sso. When REDIS_URL is set:
generateSSOToken()registers a unique JTI in RedisverifySSOToken()atomically consumes the JTI (one-time use)- If a token is replayed, it's rejected with a warning log
Health check
import { redisHealthCheck } from '@founder-os/redis';
const health = await redisHealthCheck();
// { ok: true, latencyMs: 2 } or { ok: false, error: 'Connection refused' }Architecture
┌─────────────────────────┐
│ @founder-os/redis │
│ packages/redis/ │
├─────────────────────────┤
│ src/index.ts │ Singleton client, health check
│ src/rate-limiter.ts │ Distributed sliding window
│ src/sso-store.ts │ JTI one-time use enforcement
└────────┬────────────────┘
│ ioredis
▼
┌─────────────────────────┐
│ Railway Redis │
│ (or local Docker) │
└─────────────────────────┘Key design decisions
- Fail-open: If Redis is unavailable, all operations succeed (no request blocking). Security degrades gracefully — SSO works without replay protection, rate limiting is disabled.
- Lua scripts: Rate limiter and SSO store use Lua scripts for atomic operations (no race conditions between INCR and EXPIRE).
- Lazy connection: Redis connects on first use, not at import time. Apps start fast even if Redis is slow.
- No Edge Runtime dependency:
ioredisrequires Node.js. Middleware rate limiting should call Redis from API routes, not Edge middleware.
