Node.js SDK
Server-side feature flags with built-in circuit breaker, request deduplication, caching, and Prometheus metrics.
Circuit Breaker
Auto-recovery
Prometheus
Built-in metrics
Caching
Persistent cache
SSE Support
Real-time updates
Installation
npm install @rollgate/sdk-node
# or
yarn add @rollgate/sdk-node
# or
pnpm add @rollgate/sdk-nodeQuick Start
import { RollgateClient } from '@rollgate/sdk-node';
// Initialize once at startup
const rollgate = new RollgateClient({
apiKey: process.env.ROLLGATE_API_KEY!,
baseUrl: 'https://api.rollgate.io',
refreshInterval: 30000, // Poll every 30s
});
// Initialize and start polling
await rollgate.init();
// Check flags
const isEnabled = rollgate.isEnabled('my-feature', false);
// Cleanup on shutdown
process.on('SIGTERM', () => rollgate.close());Configuration
| Option | Type | Default | Description |
|---|---|---|---|
| apiKey | string | required | Server API key (rg_server_...) |
| baseUrl | string | 'https://api.rollgate.io' | API base URL |
| refreshInterval | number | 30000 | Polling interval (ms), 0 to disable |
| enableStreaming | boolean | false | Use SSE for real-time updates (opt-in) |
| streaming | boolean | false | Alias for enableStreaming |
| timeout | number | 5000 | Request timeout (ms) |
Methods
isEnabled(key, defaultValue)
Check if a boolean flag is enabled. Returns the flag value or the default.
const showFeature = rollgate.isEnabled('new-feature', false);
if (showFeature) {
// New feature code
}getValue<T>(key, defaultValue)
Get a flag value of any type. Use for string, number, or JSON flags.
// String flag
const theme = rollgate.getValue<string>('theme', 'light');
// Number flag
const maxRetries = rollgate.getValue<number>('max-retries', 3);
// JSON flag
const config = rollgate.getValue<{ enabled: boolean; limit: number }>(
'rate-limit-config',
{ enabled: false, limit: 100 }
);getString(key, defaultValue)
Type-safe helper for string flags.
const buttonColor = rollgate.getString('cta-color', 'blue');
const variant = rollgate.getString('checkout-variant', 'control');getNumber(key, defaultValue)
Type-safe helper for number flags.
const pageSize = rollgate.getNumber('results-per-page', 20);
const timeout = rollgate.getNumber('api-timeout-ms', 5000);getJSON<T>(key, defaultValue)
Type-safe helper for JSON flags with generic type support.
interface FeatureConfig {
enabled: boolean;
maxItems: number;
allowedRoles: string[];
}
const config = rollgate.getJSON<FeatureConfig>('feature-config', {
enabled: false,
maxItems: 10,
allowedRoles: ['admin']
});
// Access typed properties
if (config.enabled && config.allowedRoles.includes(user.role)) {
// Feature logic
}getAllFlags()
Get all flags as an object.
const flags = rollgate.getAllFlags();
// { 'feature-a': true, 'feature-b': false }identify(user)
Set user context for targeting rules. Re-fetches flags with the new context.
await rollgate.identify({
id: 'user-123',
email: '[email protected]',
attributes: {
plan: 'enterprise', // For "plan in pro,enterprise" rules
country: 'US', // For "country equals US" rules
company: 'Acme Inc', // For "company contains Acme" rules
app_version: '2.1.0', // For "app_version semver_gt 2.0.0" rules
}
});Targeting Rules
User context enables server-side targeting rule evaluation. Rules are defined in the dashboard and evaluated securely on the server - the client never sees the targeting logic.
// Express middleware: identify user on each request
app.use(async (req, res, next) => {
if (req.user) {
await rollgate.identify({
id: req.user.id,
email: req.user.email,
attributes: {
plan: req.user.subscription?.plan || 'free',
country: req.headers['cf-ipcountry'] || 'unknown',
company: req.user.organization?.name,
app_version: req.headers['x-app-version'],
}
});
}
next();
});
// Now flags are evaluated with user context
app.get('/api/feature', (req, res) => {
// This check includes targeting rules evaluation
const enabled = rollgate.isEnabled('premium-feature', false);
res.json({ enabled });
});Available operators: equals, not_equals, contains, starts_with, ends_with, in, not_in, gt, gte, lt, lte, regex, is_set, is_not_set, semver_gt, semver_lt, semver_eq
refresh()
Force refresh flags from the server.
await rollgate.refresh();Circuit Breaker
The SDK includes a circuit breaker that protects your app when Rollgate is unavailable. When open, it uses cached flag values.
// Get current state
const state = rollgate.getCircuitState();
// 'closed' | 'open' | 'half-open'
// Listen to events
rollgate.on('circuit-open', () => {
console.log('Circuit breaker opened - using cached flags');
});
rollgate.on('circuit-closed', () => {
console.log('Circuit breaker closed - normal operation');
});
// Force reset (use with caution)
rollgate.resetCircuit();Prometheus Metrics
Export metrics in Prometheus format for monitoring:
app.get('/metrics', (req, res) => {
res.set('Content-Type', 'text/plain');
res.send(rollgate.getPrometheusMetrics('rollgate'));
});Available metrics:
rollgate_requests_total- Total API requestsrollgate_request_latency_ms- Request latency histogramrollgate_cache_hit_rate- Cache hit percentagerollgate_circuit_state- Circuit breaker staterollgate_evaluations_total- Flag evaluations
Error Handling & Graceful Degradation
The SDK is designed to never throw exceptions during flag evaluation. If Rollgate is unavailable, flags return their default values. Use these patterns for production-grade reliability:
// Default values ensure your app works even without Rollgate
const showNewFeature = rollgate.isEnabled('new-feature', false);
// Returns false if: flag doesn't exist, API down, circuit open
// Monitor degraded state
app.get('/health', (req, res) => {
const circuit = rollgate.getCircuitState();
const isHealthy = circuit === 'closed';
res.status(isHealthy ? 200 : 503).json({
status: isHealthy ? 'healthy' : 'degraded',
circuit,
flagsSource: circuit === 'open' ? 'cache' : 'live',
cacheAge: rollgate.getCacheStats().ageMs,
});
});
// Alert on circuit state changes
rollgate.on('circuit-open', () => {
alerting.send('warning', 'Rollgate circuit breaker opened - using cached flags');
});
rollgate.on('circuit-closed', () => {
alerting.send('info', 'Rollgate circuit breaker closed - normal operation resumed');
});Initialization Error Handling
async function startServer() {
try {
await rollgate.init();
console.log('Rollgate initialized with', Object.keys(rollgate.getAllFlags()).length, 'flags');
} catch (error) {
// Log but don't crash - SDK will use defaults
console.error('Rollgate init failed:', error.message);
console.warn('Continuing with default flag values');
}
// Server starts regardless of Rollgate status
app.listen(3000);
}
startServer();Feature-Critical Flags
For flags that control critical features, add explicit fallback logic:
function shouldEnablePayments() {
// Critical feature: payments
const flagValue = rollgate.isEnabled('payments-enabled', true); // Default: enabled
const circuitOpen = rollgate.getCircuitState() === 'open';
if (circuitOpen) {
// Circuit is open, we're using cached values
// For payments, always enable if cache is stale (fail-safe)
const cacheAge = rollgate.getCacheStats().ageMs;
if (cacheAge > 300000) { // Cache older than 5 minutes
console.warn('Stale flag cache, enabling payments as fail-safe');
return true;
}
}
return flagValue;
}Express Example
import express from 'express';
import { RollgateClient } from '@rollgate/sdk-node';
const app = express();
const rollgate = new RollgateClient({
apiKey: process.env.ROLLGATE_API_KEY!,
});
// Middleware to add flags to request
app.use(async (req, res, next) => {
// Identify user if authenticated
if (req.user) {
await rollgate.identify({
id: req.user.id,
email: req.user.email,
});
}
req.flags = rollgate.getAllFlags();
next();
});
// Use flags in routes
app.get('/api/checkout', (req, res) => {
if (req.flags['new-checkout']) {
return newCheckoutHandler(req, res);
}
return legacyCheckoutHandler(req, res);
});
// Health check
app.get('/health', (req, res) => {
res.json({
status: rollgate.getCircuitState() === 'open' ? 'degraded' : 'healthy',
circuit: rollgate.getCircuitState(),
});
});
// Start server
async function start() {
await rollgate.init();
app.listen(3000);
}
start();