Skip to main content

Node

TypeScript Support

⭐ Recommended: Use the Reforge CLI to generate TypeScript definitions for type-safe access to your flags and configs:

npx @reforge-com/cli generate --targets node-ts

Install the Latest Version

npm install @reforge-com/node

TypeScript types are included with the package.

Initialize a Client

First, generate your types:

npx @reforge-com/cli generate --targets node-ts

Then use the generated typed class:

import { Reforge } from "@reforge-com/node";
import { ReforgeTypesafeNode } from "./generated/reforge-server";

const baseReforge = new Reforge({
sdkKey: process.env.REFORGE_BACKEND_SDK_KEY,
enableSSE: true,
enablePolling: true,
});

await baseReforge.init();

// Create the typed instance
const reforge = new ReforgeTypesafeNode(baseReforge);

// Now you get full type safety and autocomplete!
const myStringConfig = reforge.myStringConfig(); // string type inferred
const myFeatureFlag = reforge.coolFeature(); // boolean type inferred
const configWithContext = reforge.userSpecificSetting({
user: { key: "user-123" },
}); // Type-safe context

Next.js API Routes (Singleton Pattern)

For Next.js API routes and other serverless environments, use the singleton pattern to avoid re-initialization on every request:

// app/api/flags/route.ts (or pages/api/flags.ts)
import { Reforge, type Contexts } from "@reforge-com/node";
import { ReforgeTypesafeNode } from "../../../generated/reforge-server";
import { NextRequest, NextResponse } from "next/server";

let baseReforge: Reforge | null = null;
let reforge: ReforgeTypesafeNode | null = null;

async function getReforgeClient(): Promise<ReforgeTypesafeNode> {
if (!baseReforge || !reforge) {
baseReforge = new Reforge({
sdkKey: process.env.REFORGE_BACKEND_SDK_KEY!,
enableSSE: true,
enablePolling: true,
});

await baseReforge.init();
reforge = new ReforgeTypesafeNode(baseReforge);
}

return reforge;
}

export async function GET(request: NextRequest) {
const rf = await getReforgeClient();

// Get user context from request headers, cookies, etc.
const userId = request.headers.get("x-user-id");
const context: Contexts = {
user: { key: userId || "anonymous" },
};

// Use type-safe methods with context
const welcomeMessage = rf.welcomeMessage(context);
const isFeatureEnabled = rf.newFeature(context);

return NextResponse.json({
welcomeMessage,
isFeatureEnabled,
});
}
Singleton Pattern Benefits

This pattern prevents re-initialization on every API request, which is crucial for serverless environments where functions are frequently recycled. The client is initialized once per container lifecycle and reused across requests.

Environment Variables

Make sure to use REFORGE_BACKEND_SDK_KEY (not NEXT_PUBLIC_) in API routes since they run server-side. Only client-side code needs the NEXT_PUBLIC_ prefix.

Feature Flags and Dynamic Config

With generated types, you get camelCase method names and full type safety:

// Instead of string keys, use type-safe methods:
if (reforge.newUserOnboarding()) {
// boolean type inferred
// Show new user flow
}

const maxRetries = reforge.apiRetryCount(); // number type inferred
const welcomeMessage = reforge.welcomeMessage(); // string type inferred

// Context is type-safe too
const userSpecificLimit = reforge.userDailyLimit({
user: { key: "user-123", plan: "pro" },
}); // Types enforced for context structure

Context

Reforge Launch supports context for intelligent rule-based evaluation of get and isFeatureEnabled based on the current request/device/user/etc.

Given

import type { Contexts } from "@reforge-com/node";

const context: Contexts = {
user: { key: "some-unique-identifier", country: "US" },
subscription: { key: "pro-sub", plan: "pro" },
};

With the generated typed class, context is passed directly to each method:

// Each generated method accepts context as an optional parameter
const configValue = reforge.someConfigName(context);
const isEnabled = reforge.someFeatureName(context);

// Or use inline context
const userSpecificValue = reforge.welcomeMessage({
user: { key: "user-123", language: "en" },
device: { mobile: true },
}); // All type-safe!

You can also use the base reforge instance for inContext blocks:

baseReforge.inContext(context, (rf) => {
// Use the typed instance inside the context
const typedRf = new ReforgeTypesafeNode(rf);

console.log(typedRf.someConfigName());
console.log(typedRf.someFeatureName());
});

Mustache Templating

Reforge supports Mustache templating for dynamic string configurations, allowing you to create personalized messages, URLs, and other dynamic content.

Prerequisites

Install Mustache as a peer dependency:

npm install mustache

Example Configuration

In your Reforge dashboard, create a string configuration with Mustache variables:

  • Configuration Key: welcome.message
  • Configuration Type: json
  • Value:
    {
    "message": "Hello {{userName}}! Welcome to {{appName}}. You have {{creditsCount}} credit(s) remaining.",
    "cta": "Buy More Credits"
    }
  • Zod Schema (optional, for validation):
    z.object({
    message: z.string(),
    cta: z.string(),
    });

Usage Examples

The CLI generates type-safe template functions:

import {ReforgeTypesafeNode} from './generated/reforge-server';

// Get the configured object
const welcomeMessageObject = reforge.welcomeMessage();

// Template functions are generated automatically
// Returns: "Hello Alice! Welcome to MyApp. You have 150 credits remaining."
const welcomeText = welcomeMessageObject.message({
// Type-safe parameters
userName: 'Alice', // string type
appName: 'MyApp', // number type
creditsCount: 150, // string type
});
});

// Returns: Buy More Credits
const welcomeCta = welcomeMessageObject.cta;

Context-Aware Templating

Combine templating with Reforge context for personalized experiences:

const userContext = {
user: { key: "user-123", plan: "premium" },
device: { mobile: false },
};

// Template adapts based on context
const personalizedContentObject = reforge.dynamicContent(userContext);

const personalizedMessage = personalizedContentObject.message({
name: "Alice",
isPremium: true,
deviceType: "desktop",
});

const someOtherField = personalizedContentObject.someOtherProperty;

Config Change Listeners

Monitor configuration changes in real-time using config change listeners:

// Add a listener for config changes
const unsubscribe = reforge.addConfigChangeListener((changes) => {
console.log("Configuration changed:", changes);

// React to specific config changes
changes.forEach((change) => {
if (change.key === "database.connection.pool.size") {
// Reconfigure database connection pool
updateDatabasePool(change.newValue);
}
});
});

// Remove the listener when done
unsubscribe();

Config change listeners are useful for:

  • Updating application state when configs change
  • Triggering cache invalidation
  • Logging configuration changes for debugging
  • Implementing reactive configuration management

Advanced Methods

Raw Config Access

Access raw configuration metadata and structure:

// Get raw config with full metadata
const rawConfig = reforge.raw("api.retry.count");
if (rawConfig) {
console.log("Config type:", rawConfig.configType);
console.log("Value type:", rawConfig.valueType);
console.log("Rules:", rawConfig.rows);
}

Available Keys

Get all available configuration keys:

const allKeys = reforge.keys();
console.log(allKeys); // ["config.one", "config.two", "feature.flag", ...]

Default Context

Access the default context for the current environment:

const defaultCtx = reforge.defaultContext();
console.log("Default context:", defaultCtx);

Runtime Configuration Override

Set configuration values at runtime (useful for testing or dynamic overrides):

// Override a config value at runtime
reforge.set("feature.beta", { bool: true });

// The override persists until the next config update or restart
const isEnabled = reforge.isFeatureEnabled("feature.beta"); // true

Connection Management

Manual Updates

Force immediate configuration updates:

// Force an update now
await reforge.updateNow();

// Update only if configs are stale (older than 5 minutes)
reforge.updateIfStalerThan(5 * 60 * 1000); // 5 minutes in ms

Connection Control

// Stop polling for updates (SSE continues if enabled)
reforge.stopPolling();

// Properly close all connections and clean up resources
reforge.close(); // Stops SSE, clears timeouts, disables telemetry

Important: Always call reforge.close() when shutting down your application to ensure proper cleanup of connections and prevent memory leaks.

Telemetry

Reforge automatically collects telemetry data to help you understand how your configurations are being used. The telemetry system includes several components:

Telemetry Components

// Access telemetry components
console.log("Evaluation summaries:", reforge.telemetry?.evaluationSummaries);
console.log("Context shapes:", reforge.telemetry?.contextShapes);
console.log("Example contexts:", reforge.telemetry?.exampleContexts);
console.log("Known loggers:", reforge.telemetry?.knownLoggers);

Configuration

Control telemetry collection via constructor options:

const reforge = new Reforge({
sdkKey: process.env.REFORGE_BACKEND_SDK_KEY!,

// Control evaluation summaries (default: true)
collectEvaluationSummaries: true,

// Control logger usage tracking (default: true)
collectLoggerCounts: true,

// Control context data collection (default: "periodicExample")
contextUploadMode: "SHAPE_ONLY", // or "NONE" or "periodicExample"
});

Context Upload Modes:

  • "periodicExample" - Sends both context structure and example values
  • "SHAPE_ONLY" - Sends only context keys and types, no values
  • "NONE" - Disables context collection entirely

Reference

Option Definitions

NameDescriptionDefault
collectEvaluationSummariesSend counts of config/flag evaluation results back to Reforge Launch to view in web apptrue
collectLoggerCountsSend counts of logger usage back to Reforge Launch to power log-levels configuration screentrue
contextUploadModeUpload either context "shapes" (the names and data types your app uses in reforge contexts) or periodically send full example contexts"periodicExample"
defaultLevelLevel to be used as the min-verbosity for a loggerPath if no value is configured in Reforge Launch"warn"
enableSSEWhether or not we should listen for live changes from Reforge Launchtrue
enablePollingWhether or not we should poll for changes from Reforge Launchtrue