Node.jsv1.2.1Backend

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-node

Quick Start

src/flags.ts
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

OptionTypeDefaultDescription
apiKeystringrequiredServer API key (rg_server_...)
baseUrlstring'https://api.rollgate.io'API base URL
refreshIntervalnumber30000Polling interval (ms), 0 to disable
enableStreamingbooleanfalseUse SSE for real-time updates (opt-in)
streamingbooleanfalseAlias for enableStreaming
timeoutnumber5000Request 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.

Per-request targeting
// 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:

Express endpoint
app.get('/metrics', (req, res) => {
  res.set('Content-Type', 'text/plain');
  res.send(rollgate.getPrometheusMetrics('rollgate'));
});

Available metrics:

  • rollgate_requests_total - Total API requests
  • rollgate_request_latency_ms - Request latency histogram
  • rollgate_cache_hit_rate - Cache hit percentage
  • rollgate_circuit_state - Circuit breaker state
  • rollgate_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:

Graceful degradation
// 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

src/server.ts
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();