npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

eehitus-feature-flag-client

v1.6.3

Published

A feature flag client for Node.js and React

Readme

Feature Flag Client

A universal feature flag client that works for both Node.js and React applications. This package allows applications to fetch feature flags from a remote API and determine if a feature is enabled or disabled.

Features

  • ✅ Works in both Node.js (CommonJS & ESM) and React (browser environment)
  • NEW: Built-in React Provider, Context, and Hooks for easy integration
  • NEW: Conditional exports - import core client or React-specific components
  • NEW: Compatible with updated API format with cache-busting support
  • ✅ Fetches feature flags from a configurable API with sub-10ms response time
  • Enhanced: Handles new wrapped response format {flags: [], _meta: {}}
  • NEW: Built-in cache-busting with unique request IDs and metadata
  • ✅ Supports automatic polling every minute (for Node.js)
  • NEW: Supports configurable polling for browser environments
  • ✅ Supports manual fetching (for React)
  • ✅ Uses Axios (peer dependency) for HTTP requests
  • NEW: CloudFlare CDN compatible with proper headers

Installation

Step 1: Install the package

# Using Yarn
yarn add eehitus-feature-flag-client axios

# Using npm
npm install eehitus-feature-flag-client axios

Note: axios is a peer dependency, so it must be installed separately.


API Response Format

The Feature Flag API has been updated to include cache-busting mechanisms and enhanced metadata:

New Response Format:

{
  "flags": [
    {
      "id": 1,
      "name": "new-dashboard",
      "explanation": "New dashboard interface"
    },
    {
      "id": 2,
      "name": "dark-mode",
      "explanation": "Dark mode theme support"
    }
  ],
  "_meta": {
    "requestId": "1755800710303-wr4i346c4cc",
    "timestamp": 1755800710315,
    "method": "GET",
    "cacheBust": {
      "timestamp": 1755800710303,
      "random": "wr4i346c4cc",
      "version": "1.0",
      "nonce": "mel123",
      "method": "GET"
    },
    "responseTime": "5.23ms",
    "uniqueUrl": "/flags/active?app=9&environment=test&timestamp=1755800710303"
  }
}

Key Features:

  • flags: Array of active feature flags for your application
  • _meta: Metadata object containing cache-busting information and request details
  • requestId: Unique identifier for each request for debugging
  • cacheBust: Cache-busting parameters to ensure fresh responses
  • responseTime: Server-side processing time for performance monitoring

Import Options

Core Client (Any Environment)

import { FeatureFlagClient } from 'eehitus-feature-flag-client';

React Components (React Only)

import { 
  FeatureFlagProvider, 
  useFeatureFlags, 
  FeatureFlagged 
} from 'eehitus-feature-flag-client/react';

Usage

Node.js (ES Modules)

import { FeatureFlagClient } from "eehitus-feature-flag-client";

const client = new FeatureFlagClient(
  "https://your-feature-flag-api.com", // API URL (without /flags/active)
  9,                                   // Application ID (use correct app ID)
  "test"                              // Environment (dev, test, prelive, live)
);

// Fetch flags manually (optional, useful for immediate use)
await client.fetchOnce();

// Check if a feature is enabled
console.log("New Dashboard Enabled:", client.flagEnabled("new-dashboard"));

// Check feature flags dynamically every minute
setInterval(() => {
  console.log("Dark Mode Enabled:", client.flagEnabled("dark-mode"));
}, 60000);

Node.js (CommonJS)

const { FeatureFlagClient } = require("eehitus-feature-flag-client");

const client = new FeatureFlagClient(
  "https://your-feature-flag-api.com",
  9,
  "production"
);

// Flags are automatically fetched and kept up to date
setTimeout(() => {
  console.log("Feature Enabled:", client.flagEnabled("beta-feature"));
}, 1000); // Small delay to allow initial fetch

React (Simple Usage)

import { useEffect, useState } from "react";
import { FeatureFlagClient } from "eehitus-feature-flag-client";

// Creating a client with browser polling enabled
const client = new FeatureFlagClient(
  "https://your-feature-flag-api.com",
  9,
  "dev",
  {
    enableBrowserPolling: true,
    pollingInterval: 2 * 60 * 1000, // Poll every 2 minutes
    timeout: 10000                  // 10 second timeout
  }
);

export default function FeatureFlagsExample() {
  const [flagEnabled, setFlagEnabled] = useState(false);

  useEffect(() => {
    async function fetchFlags() {
      // IMPORTANT: Always await fetchOnce before checking flags
      await client.fetchOnce();
      setFlagEnabled(client.flagEnabled("new-dashboard"));
    }

    fetchFlags();
    
    // Clean up when component unmounts
    return () => {
      client.stopFetching();
    };
  }, []);

  return <div>{flagEnabled ? "Feature is ON" : "Feature is OFF"}</div>;
}

React (Recommended - Using Built-in Provider)

import { FeatureFlagProvider, useFeatureFlags, FeatureFlagged } from 'eehitus-feature-flag-client/react';

// In your app root
function App() {
  return (
    <FeatureFlagProvider 
      url="https://your-feature-flag-api.com" 
      appId={9} 
      environment="dev"
      commonFlags={['new-dashboard', 'dark-mode']}
      options={{
        enableBrowserPolling: true,
        pollingInterval: 2 * 60 * 1000
      }}
    >
      <MyComponent />
    </FeatureFlagProvider>
  );
}

// In your components
function MyComponent() {
  const { isFlagEnabled, isLoading } = useFeatureFlags();
  
  if (isLoading) return <div>Loading...</div>;
  
  return (
    <div>
      {isFlagEnabled('new-dashboard') && <div>New Dashboard is enabled!</div>}
      
      <FeatureFlagged name="dark-mode" fallback={<div>Dark mode is disabled</div>}>
        <div>Dark mode is enabled!</div>
      </FeatureFlagged>
    </div>
  );
}

Advanced React Implementation

For more robust feature flag management in React applications, we recommend using a context-based provider pattern. This approach makes feature flags available throughout your application and handles loading/caching efficiently.

Step 1: Create a Feature Flag Provider

Create a file named FeatureFlagProvider.tsx:

import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react';
import { FeatureFlagClient } from 'eehitus-feature-flag-client';

// Create a Feature Flag Client instance with browser polling enabled
const featureFlagClient = new FeatureFlagClient(
  process.env.REACT_APP_FLAG_API_URL || 'https://your-api.com',
  Number(process.env.REACT_APP_FLAG_APP_ID) || 9,
  process.env.REACT_APP_FLAG_ENVIRONMENT || 'dev',
  {
    enableBrowserPolling: true,
    pollingInterval: 5 * 60 * 1000, // 5 minutes
    timeout: 10000
  }
);

// Define the context type
interface FeatureFlagContextType {
  isLoading: boolean;
  flags: Record<string, boolean>;
  refreshFlags: () => Promise<void>;
  isFlagEnabled: (flagName: string) => boolean;
}

// Create context with default values
const FeatureFlagContext = createContext<FeatureFlagContextType>({
  isLoading: true,
  flags: {},
  refreshFlags: async () => {},
  isFlagEnabled: () => false,
});

// Create a hook for using feature flags
export const useFeatureFlags = () => useContext(FeatureFlagContext);

interface FeatureFlagProviderProps {
  children: ReactNode;
}

export function FeatureFlagProvider({ children }: FeatureFlagProviderProps): JSX.Element {
  const [isLoading, setIsLoading] = useState(true);
  const [flags, setFlags] = useState<Record<string, boolean>>({});
  const [error, setError] = useState<Error | null>(null);

  // Common flags to check
  const commonFlags = [
    'new-dashboard',
    'dark-mode',
    'beta-features',
    // Add any other flags you want to check
  ];

  // Function to load feature flags
  const loadFeatureFlags = async () => {
    try {
      setIsLoading(true);
      
      // Use fetchFresh to ensure a new request is made
      await featureFlagClient.fetchFresh();
      
      // Create a map of all flags for easy access
      const flagsMap: Record<string, boolean> = {};
      
      commonFlags.forEach(flagName => {
        flagsMap[flagName] = featureFlagClient.flagEnabled(flagName);
      });
      
      setFlags(flagsMap);
      setError(null);
    } catch (err) {
      console.error('Failed to load feature flags:', err);
      setError(err instanceof Error ? err : new Error(String(err)));
    } finally {
      setIsLoading(false);
    }
  };

  // Load flags on component mount
  useEffect(() => {
    // Initial load
    loadFeatureFlags();
    
    // Clean up when component unmounts
    return () => {
      featureFlagClient.stopFetching();
    };
  }, []);

  // Function to check if a flag is enabled
  const isFlagEnabled = (flagName: string): boolean => {
    // First check our cached flags
    if (flagName in flags) {
      return flags[flagName];
    }
    
    // If not in our cache, check directly from the client
    return featureFlagClient.flagEnabled(flagName);
  };

  // Function to manually refresh flags
  const refreshFlags = async (): Promise<void> => {
    await loadFeatureFlags();
  };

  return (
    <FeatureFlagContext.Provider
      value={{
        isLoading,
        flags,
        refreshFlags,
        isFlagEnabled,
      }}
    >
      {children}
    </FeatureFlagContext.Provider>
  );
}

// Higher-order component for feature flag conditional rendering
interface FeatureFlaggedProps {
  flagName: string;
  fallback?: React.ReactNode;
  children: React.ReactNode;
}

export function FeatureFlagged({ flagName, fallback = null, children }: FeatureFlaggedProps): JSX.Element {
  const { isFlagEnabled } = useFeatureFlags();
  
  return isFlagEnabled(flagName) ? <>{children}</> : <>{fallback}</>;
}

Step 2: Wrap Your Application with the Provider

In your main index.tsx or App.tsx:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { FeatureFlagProvider } from './feature-flags/FeatureFlagProvider';

ReactDOM.render(
  <React.StrictMode>
    <FeatureFlagProvider>
      <App />
    </FeatureFlagProvider>
  </React.StrictMode>,
  document.getElementById('root')
);

Step 3: Use Feature Flags in Components

Now you can use feature flags throughout your application in three ways:

Method 1: Using the useFeatureFlags hook

import { useFeatureFlags } from './feature-flags/FeatureFlagProvider';

function MyComponent() {
  const { isFlagEnabled, isLoading } = useFeatureFlags();
  
  if (isLoading) {
    return <div>Loading...</div>;
  }
  
  return (
    <div>
      {isFlagEnabled('dark-mode') && (
        <button>Switch to Light Mode</button>
      )}
    </div>
  );
}

Method 2: Using the FeatureFlagged component

import { FeatureFlagged } from './feature-flags/FeatureFlagProvider';
import OldDashboard from './components/OldDashboard';
import NewDashboard from './components/NewDashboard';

function DashboardPage() {
  return (
    <FeatureFlagged 
      flagName="new-dashboard" 
      fallback={<OldDashboard />}
    >
      <NewDashboard />
    </FeatureFlagged>
  );
}

Method 3: Refreshing flags manually

import { useFeatureFlags } from './feature-flags/FeatureFlagProvider';

function SettingsPage() {
  const { refreshFlags, flags } = useFeatureFlags();
  
  return (
    <div>
      <h1>Application Settings</h1>
      <button onClick={refreshFlags}>Refresh Feature Flags</button>
      
      <pre>{JSON.stringify(flags, null, 2)}</pre>
    </div>
  );
}

API

new FeatureFlagClient(url: string, appId: number, environment: string, options?: FeatureFlagClientOptions)

Creates a new feature flag client instance.

| Parameter | Type | Description | |--------------|---------|------------------------------------| | url | string | Base API endpoint (without /flags/active) | | appId | number | Application identifier (ID) - Use correct app ID (e.g., 9) | | environment | string | Environment (dev, test, prelive, live) | | options | object | Optional configuration options (see below) |

FeatureFlagClientOptions

| Option | Type | Default | Description | |----------------------|-----------|---------|-------------------------------------------| | enableBrowserPolling | boolean | false | Enable polling in browser environments | | pollingInterval | number | 60000 | Polling interval in milliseconds (default: 1 minute) | | timeout | number | 10000 | Request timeout in milliseconds (default: 10 seconds) | | headers | object | {} | Additional headers to send with requests |

.fetchOnce(): Promise<Record<string, boolean>>

Fetches flags if they haven't been loaded before. Returns the current flag states.

.fetchFresh(): Promise<Record<string, boolean>>

Forces a fresh fetch of feature flags from the server, regardless of whether they've been loaded before. Returns the updated flag states.

.flagEnabled(flagName: string): boolean

Returns true if the feature flag is enabled, otherwise false.

.getFlagEnabledAsync(flagName: string): Promise<boolean>

Asynchronously ensures flags are loaded before checking if a flag is enabled. Useful for initial page load.

.ensureFlagsLoaded(): Promise<void>

Ensures that flags have been loaded at least once before proceeding.

.startPolling(interval?: number): void

Starts periodic polling for flag updates. If an interval is provided, it updates the polling interval.

.stopFetching(): void

Stops automatic flag updates for both Node.js and browser environments.

.isPollingActive(): boolean

Checks if polling is currently active.

.setPollingInterval(interval: number): void

Updates the polling interval. If polling is already active, it restarts with the new interval.


Error Handling

The client handles the new API error format that includes metadata:

Error Response Format:

{
  "error": "Application ID is required",
  "timestamp": 1755800321779,
  "cacheBust": {
    "timestamp": 1755800321779,
    "random": "cuhtuekkpm",
    "version": "1.0",
    "nonce": "melq6ewj",
    "method": "GET"
  },
  "_meta": {
    "timestamp": 1755800321779,
    "requestId": "1755800321779-3qztnw2os62",
    "cacheBypassed": true,
    "generated": "2025-08-21T18:18:41.779Z"
  }
}

Error Handling in Code:

try {
  const flags = await client.fetchFresh();
  console.log("Flags loaded:", flags);
} catch (error) {
  if (error.response?.data?.error) {
    console.error("API Error:", error.response.data.error);
    console.error("Request ID:", error.response.data._meta?.requestId);
  } else {
    console.error("Network Error:", error.message);
  }
}

Cache-Busting Features

The client automatically includes cache-busting parameters to ensure fresh responses:

Automatic Cache-Busting:

  • Timestamp: Current timestamp added to requests
  • Random ID: Unique random string for each request
  • Request ID: Server generates unique ID for tracking
  • CloudFlare Compatible: Works with CloudFlare CDN caching

Cache-Busting Parameters:

// Automatically added to requests:
{
  app: 9,
  environment: "test",
  timestamp: 1755800710303,
  r: "wr4i346c4cc"
}

Important React Notes

  • Browser Polling: The new enableBrowserPolling option allows automatic flag refreshing in browser environments.
  • Always await fetchOnce() or fetchFresh(): When using the client directly in React, always await the fetch methods before checking flags to prevent race conditions.
  • Context API: Using the context-based provider approach is recommended for most React applications as it manages loading states and provides a centralized way to access flags.
  • Environment Variables: Configure your application with the appropriate environment variables for your feature flag API.
  • App ID: Make sure to use the correct App ID (e.g., 9) that has feature flags configured in your target environment.

Troubleshooting

Common Issues:

No flags returned (empty object)

// Problem: Using wrong App ID
const client = new FeatureFlagClient(url, 2, 'test'); // App ID 2 might have no flags

// Solution: Use correct App ID with configured flags
const client = new FeatureFlagClient(url, 9, 'test'); // App ID 9 has flags

API Parameter Errors

// ✅ Correct: Use 'app' parameter
const client = new FeatureFlagClient(url, appId, environment);

// ❌ Wrong: 'applicationId' parameter is not supported
// The API expects 'app' parameter, not 'applicationId'

Response Format Issues

If you see errors like TypeError: response.data.forEach is not a function, ensure you're using the latest version of the client that supports the new API format.

Cache Issues

The new API includes cache-busting mechanisms. If you're seeing stale data:

  • Use fetchFresh() to force a new request
  • Check that cache-busting parameters are included in requests
  • Verify CloudFlare cache settings if applicable

Cleanup

To stop fetching when shutting down your app:

Node.js

process.on("SIGINT", () => {
  console.log("Stopping feature flag updates...");
  client.stopFetching();
  process.exit();
});

React

useEffect(() => {
  // Initialize flags...
  
  return () => {
    client.stopFetching();
  };
}, []);

Version History

v1.6.1 (Latest)

  • MAJOR: Updated to support new API response format {flags: [], _meta: {}}
  • NEW: Added cache-busting support with unique request IDs
  • NEW: Enhanced error handling for new error format
  • NEW: CloudFlare CDN compatibility
  • NEW: Added timeout configuration option
  • FIXED: Resolved TypeError: forEach is not a function with new API format
  • IMPROVED: Better TypeScript support with updated interfaces

v1.5.0

  • ✅ Added browser polling support
  • ✅ Enhanced React integration
  • ✅ Improved error handling

License

MIT License


Now, your Node.js and React applications can seamlessly use feature flags with the latest API format including cache-busting and enhanced metadata! 🚀