switcho-sdk
v1.1.0
Published
Switcho SDK for Node.js and Edge runtimes. Optimized for 1M+ user scale with circuit breaker, request coalescing, and metrics.
Downloads
276
Maintainers
Readme
switcho
Server-side Switcho SDK for Node.js ≥18, Vercel Edge Functions, and Cloudflare Workers.
Zero dependencies. Uses the native fetch API and an in-process Map cache — no localStorage, no React.
Why a backend SDK?
The browser SDK (switcho-react-sdk) fetches flags directly from the client, which triggers CORS pre-flight requests and exposes your API key in the browser bundle. For backend use cases — SSR, API routes, middleware — you want to:
- Fetch flags once on the server and share across requests (in-process cache)
- Keep your API key server-side only (not in a
VITE_env var) - Avoid CORS entirely — server-to-server calls don't need CORS headers
Install
npm install switchoQuick start
import { FlagFlowClient } from 'switcho';
const flags = new FlagFlowClient({
apiKey: process.env.FLAGFLOW_KEY, // server-side only — never expose in browser
environment: process.env.NODE_ENV === 'production' ? 'production' : 'development',
apiUrl: 'https://flags.switcho.dev', // your CF Worker URL
});
await flags.connect();
if (flags.isEnabled('new-checkout')) {
// ...
}Express / Fastify middleware
import express from 'express';
import { FlagFlowClient } from 'switcho';
const app = express();
const flagClient = new FlagFlowClient({
apiKey: process.env.FLAGFLOW_KEY,
environment: 'production',
apiUrl: 'https://flags.switcho.dev',
ttlMs: 30_000, // re-fetch after 30s (default)
});
// Attach req.flags to every request (cache-aware — one fetch per TTL period)
app.use(flagClient.middleware());
app.get('/checkout', (req, res) => {
if (req.flags.isEnabled('new-checkout')) {
return res.json({ ui: 'new' });
}
res.json({ ui: 'old' });
});Next.js (App Router — Server Component)
// lib/flags.ts
import { FlagFlowClient } from 'switcho';
// Module-level singleton — reused across requests in the same Node.js process
export const flagClient = new FlagFlowClient({
apiKey: process.env.FLAGFLOW_KEY!,
environment: 'production',
apiUrl: 'https://flags.switcho.dev',
});// app/page.tsx
import { flagClient } from '@/lib/flags';
export default async function Page() {
await flagClient.connect(); // cache-aware — no extra fetch if TTL is fresh
return flagClient.isEnabled('new-homepage')
? <NewHomepage />
: <OldHomepage />;
}Hono (Cloudflare Workers)
import { Hono } from 'hono';
import { FlagFlowClient } from 'switcho';
const app = new Hono<{ Bindings: { FLAGFLOW_KEY: string } }>();
app.use('*', async (c, next) => {
const client = new FlagFlowClient({
apiKey: c.env.FLAGFLOW_KEY,
environment: 'production',
apiUrl: 'https://flags.switcho.dev',
ttlMs: 30_000,
});
await client.connect();
c.set('flags', client);
await next();
});
app.get('/api/checkout', (c) => {
const flags = c.get('flags') as FlagFlowClient;
return c.json({ ui: flags.isEnabled('new-checkout') ? 'new' : 'old' });
});
export default app;API Reference
new FlagFlowClient(options)
| Option | Type | Default | Description |
|---|---|---|---|
| apiKey | string | required | API key from Switcho dashboard |
| environment | string | required | e.g. "production" |
| apiUrl | string | required | CF Worker base URL |
| ttlMs | number | 30000 | In-process cache TTL (ms) |
| timeoutMs | number | 5000 | Per-fetch timeout (ms) |
| maxRetries | number | 2 | Retry attempts with exponential back-off |
| debug | boolean | false | Log fetch lifecycle to console.debug |
| offlineMode | boolean | false | Enable offline mode with disk persistence (Node.js only) |
| offlineStoragePath | string | auto | Custom path for cache file |
| offlineMaxAge | number | 86400000 | Max age for offline cache in ms (default 24h) |
client.connect() → Promise<FlagMap>
Fetches flags. Returns cached flags when TTL is fresh. Concurrent calls are coalesced.
client.refresh() → Promise<FlagMap>
Forces a fresh fetch, bypassing the cache.
client.isEnabled(flagKey) → boolean
Returns true if the flag is enabled, false for unknown keys.
client.getVariant(flagKey, defaultValue?) → T
Returns the raw flag value, falling back to defaultValue.
client.getFlags() → FlagMap
Returns a shallow copy of all flags.
client.middleware() → Express middleware
Attaches req.flags with isEnabled, getVariant, getFlags to every request.
client.getOfflineStatus() → object
Returns offline cache status (if offline mode is enabled).
client.clearOfflineCache() → boolean
Clears the offline disk cache. Returns true on success.
connect(options) → Promise<FlagMap>
Low-level stateless fetch — useful for serverless functions that cache at infra level.
Offline Mode (Node.js only)
Offline mode allows your application to continue functioning when the Switcho API is unavailable. Flags are persisted to disk and loaded on startup.
const flagClient = new FlagFlowClient({
apiKey: process.env.FLAGFLOW_KEY,
environment: 'production',
apiUrl: 'https://flags.switcho.dev',
offlineMode: true, // Enable offline mode
offlineMaxAge: 86400000, // Cache flags for 24h
});
await flagClient.connect();
// Check offline status
const status = flagClient.getOfflineStatus();
console.log(status);
// {
// enabled: true,
// hasCache: true,
// cacheAge: 123456,
// cacheExpired: false,
// flagsInCache: 42
// }
// Clear cache if needed
flagClient.clearOfflineCache();Limitations:
- Only works in Node.js environments with file system access
- Automatically disabled in edge runtimes (Cloudflare Workers, Vercel Edge)
- Cache file is stored in the working directory (
.switcho-cache-{environment}.json) - Cached flags expire after
offlineMaxAge(default: 24 hours)
Caching behaviour
| Scenario | Behaviour |
|---|---|
| Cache fresh (age < ttlMs) | Returns cached flags immediately, no fetch |
| Cache stale | Fetches fresh flags from CF Worker |
| Fetch fails, stale cache available | Returns stale flags (graceful degradation) |
| Fetch fails, no cache | Throws — wrap in try/catch and use defaults |
| Concurrent requests during fetch | Coalesced — single in-flight promise |
| Offline mode enabled | Flags persisted to disk, loaded on startup |
| API down, offline cache available | Uses disk cache (if within max age) |
License
MIT
