Basestack Docs
Backend

Node.js (Official SDK)

Seamless integration with Node.js streamlines the testing and development of features across production and various environments. The Basestack JavaScript SDK works perfectly in server-side Node.js applications, allowing you to leverage feature flags in API routes, middleware, background jobs, and serverless functions.

This SDK provides a simple, promise-based API for fetching and managing feature flags with built-in caching, making it ideal for high-performance server applications. Whether you're building with Express, Hono, NestJS, Fastify, or Bun, this SDK integrates effortlessly into your workflow.

Getting started

We only support ESM (ECMAScript Modules) in the browser or server environment. If you are using CommonJS, you will need to use a bundler like Webpack or Rollup to bundle your code.

Examples

Install

Install the SDK using your preferred package manager. The SDK is lightweight and has no external dependencies beyond the Fetch API (available in Node.js 18+).

Install the SDK
npm install @basestack/flags-js
pnpm install @basestack/flags-js
yarn add @basestack/flags-js
bun add @basestack/flags-js

Environment Variables

Configure your environment variables to connect to your Basestack Feature Flags project. These values identify your project and environment.

.env
# BASESTACK FEATURE FLAGS
FEATURE_FLAGS_BASE_URL="https://flags-api.basestack.co/v1"
FEATURE_FLAGS_PROJECT_KEY=""
FEATURE_FLAGS_ENVIRONMENT_KEY=""

You can find your project and environment keys in your Basestack Feature Flags Dashboard.

Keep your project and environment keys secure. Never commit them to version control. Use environment variables or secure secret management systems.

Create SDK Instance

Create a new instance of the FlagsSDK class. In Node.js 18+, the native Fetch API is available, so no additional setup is needed.

Basic Initialization (Node.js 18+):

src/flags.js
import { FlagsSDK } from "@basestack/flags-js";

const client = new FlagsSDK({
  baseURL: process.env.FEATURE_FLAGS_BASE_URL,
  projectKey: process.env.FEATURE_FLAGS_PROJECT_KEY,
  environmentKey: process.env.FEATURE_FLAGS_ENVIRONMENT_KEY,
});

Advanced Configuration:

For production applications, customize caching and preload specific flags:

src/flags.js
import { FlagsSDK } from "@basestack/flags-js";

const client = new FlagsSDK({
  baseURL: process.env.FEATURE_FLAGS_BASE_URL,
  projectKey: process.env.FEATURE_FLAGS_PROJECT_KEY,
  environmentKey: process.env.FEATURE_FLAGS_ENVIRONMENT_KEY,
  
  // Preload specific flags on initialization
  preloadFlags: ["header", "footer", "checkout"],
  
  // Customize cache settings
  cache: {
    enabled: true,
    ttl: 10 * 60 * 1000, // 10 minutes (default: 5 minutes)
    maxSize: 50, // Maximum cached flags (default: 100)
  },
});

The Fetch API is available in Node.js starting from version 18.0.0. If you're using an older version, see the "Node.js < 18" section below.

Node.js < 18 Support

If your project uses Node.js version earlier than 18, you'll need to provide a custom fetch implementation. The SDK supports this via the fetchImpl option.

Using cross-fetch:

src/flags.js
import fetch from "cross-fetch";
import { FlagsSDK } from "@basestack/flags-js";

const client = new FlagsSDK({
  baseURL: process.env.FEATURE_FLAGS_BASE_URL,
  projectKey: process.env.FEATURE_FLAGS_PROJECT_KEY,
  environmentKey: process.env.FEATURE_FLAGS_ENVIRONMENT_KEY,
  fetchImpl: fetch, // Use cross-fetch for Node.js < 18
});

Using Axios:

src/flags.js
import axios from 'axios';
import { FlagsSDK } from '@basestack/flags-js';

// Axios wrapper to match fetch API signature
const axiosFetch = async (input, init) => {
  const { method = 'GET', headers, body } = init || {};
  const response = await axios({
    url: input.toString(),
    method,
    headers,
    data: body,
  });

  return new Response(JSON.stringify(response.data), {
    status: response.status,
    statusText: response.statusText,
    headers: new Headers(response.headers),
  });
};

const client = new FlagsSDK({
  baseURL: process.env.FEATURE_FLAGS_BASE_URL,
  projectKey: process.env.FEATURE_FLAGS_PROJECT_KEY,
  environmentKey: process.env.FEATURE_FLAGS_ENVIRONMENT_KEY,
  fetchImpl: axiosFetch,
});

Framework Examples

Express.js

Create a singleton instance and use it in your Express routes:

src/flags.js
import { FlagsSDK } from "@basestack/flags-js";

let flagsClient = null;

export function getFlagsClient() {
  if (!flagsClient) {
    flagsClient = new FlagsSDK({
      baseURL: process.env.FEATURE_FLAGS_BASE_URL,
      projectKey: process.env.FEATURE_FLAGS_PROJECT_KEY,
      environmentKey: process.env.FEATURE_FLAGS_ENVIRONMENT_KEY,
    });
  }
  return flagsClient;
}

Use in Express routes:

src/routes/api.js
import express from "express";
import { getFlagsClient } from "../flags.js";

const router = express.Router();

router.get("/feature/:slug", async (req, res) => {
  try {
    const client = getFlagsClient();
    const flag = await client.getFlag(req.params.slug);
    
    res.json({
      enabled: flag.enabled,
      payload: flag.payload,
    });
  } catch (error) {
    res.status(500).json({ error: "Failed to fetch flag" });
  }
});

export default router;

Hono

Hono is a fast web framework. Here's how to use the SDK with Hono:

src/index.js
import { Hono } from "hono";
import { FlagsSDK } from "@basestack/flags-js";

const app = new Hono();

// Initialize SDK once
const flagsClient = new FlagsSDK({
  baseURL: process.env.FEATURE_FLAGS_BASE_URL,
  projectKey: process.env.FEATURE_FLAGS_PROJECT_KEY,
  environmentKey: process.env.FEATURE_FLAGS_ENVIRONMENT_KEY,
});

// Middleware to check flags
app.use("*", async (c, next) => {
  const flag = await flagsClient.getFlag("maintenance-mode");
  if (flag.enabled) {
    return c.json({ message: "Service under maintenance" }, 503);
  }
  await next();
});

// Route using flags
app.get("/api/feature", async (c) => {
  const flag = await flagsClient.getFlag("new-api");
  
  if (flag.enabled) {
    return c.json({ message: "New API enabled", payload: flag.payload });
  }
  
  return c.json({ message: "Legacy API" });
});

export default app;

NestJS

Create a service provider for NestJS:

src/flags/flags.service.ts
import { Injectable, OnModuleInit } from "@nestjs/common";
import { FlagsSDK } from "@basestack/flags-js";

@Injectable()
export class FlagsService implements OnModuleInit {
  private client: FlagsSDK;

  constructor() {
    this.client = new FlagsSDK({
      baseURL: process.env.FEATURE_FLAGS_BASE_URL,
      projectKey: process.env.FEATURE_FLAGS_PROJECT_KEY,
      environmentKey: process.env.FEATURE_FLAGS_ENVIRONMENT_KEY,
    });
  }

  async onModuleInit() {
    // Preload flags on module initialization
    await this.client.init();
  }

  async getFlag(slug: string) {
    return this.client.getFlag(slug);
  }

  async getAllFlags() {
    return this.client.getAllFlags();
  }
}

Use in controllers:

src/features/features.controller.ts
import { Controller, Get, Param } from "@nestjs/common";
import { FlagsService } from "../flags/flags.service";

@Controller("features")
export class FeaturesController {
  constructor(private readonly flagsService: FlagsService) {}

  @Get(":slug")
  async getFeature(@Param("slug") slug: string) {
    const flag = await this.flagsService.getFlag(slug);
    return {
      enabled: flag.enabled,
      payload: flag.payload,
    };
  }
}

Bun

Bun has native fetch support, so the SDK works out of the box:

src/server.ts
import { FlagsSDK } from "@basestack/flags-js";

const flagsClient = new FlagsSDK({
  baseURL: process.env.FEATURE_FLAGS_BASE_URL,
  projectKey: process.env.FEATURE_FLAGS_PROJECT_KEY,
  environmentKey: process.env.FEATURE_FLAGS_ENVIRONMENT_KEY,
});

Bun.serve({
  port: 3000,
  async fetch(req) {
    const url = new URL(req.url);
    
    if (url.pathname === "/api/flag/:slug") {
      const slug = url.pathname.split("/").pop();
      const flag = await flagsClient.getFlag(slug!);
      
      return Response.json({
        enabled: flag.enabled,
        payload: flag.payload,
      });
    }
    
    return new Response("Not Found", { status: 404 });
  },
});

Serverless Functions (AWS Lambda, Vercel, etc.)

For serverless functions, initialize the SDK outside the handler for better performance:

src/handler.js
import { FlagsSDK } from "@basestack/flags-js";

// Initialize outside handler to reuse across invocations
const flagsClient = new FlagsSDK({
  baseURL: process.env.FEATURE_FLAGS_BASE_URL,
  projectKey: process.env.FEATURE_FLAGS_PROJECT_KEY,
  environmentKey: process.env.FEATURE_FLAGS_ENVIRONMENT_KEY,
});

export async function handler(event) {
  try {
    const flag = await flagsClient.getFlag("new-feature");
    
    if (flag.enabled) {
      // Use new feature logic
      return {
        statusCode: 200,
        body: JSON.stringify({ message: "New feature enabled" }),
      };
    }
    
    // Use legacy logic
    return {
      statusCode: 200,
      body: JSON.stringify({ message: "Legacy feature" }),
    };
  } catch (error) {
    return {
      statusCode: 500,
      body: JSON.stringify({ error: "Failed to fetch flag" }),
    };
  }
}

Usage Examples

Basic Flag Check

src/example.js
import { FlagsSDK } from "@basestack/flags-js";

const client = new FlagsSDK({
  projectKey: process.env.FEATURE_FLAGS_PROJECT_KEY,
  environmentKey: process.env.FEATURE_FLAGS_ENVIRONMENT_KEY,
});

async function checkFeature() {
  try {
    const flag = await client.getFlag("new-checkout");
    
    if (flag.enabled) {
      console.log("New checkout is enabled");
      console.log("Payload:", flag.payload);
      // Use new checkout flow
    } else {
      console.log("Using legacy checkout");
      // Use legacy checkout flow
    }
  } catch (error) {
    console.error("Failed to fetch flag:", error);
    // Fallback to default behavior
  }
}

await checkFeature();

Preloading Flags

Preload flags on application startup for better performance:

src/app.js
import { FlagsSDK } from "@basestack/flags-js";

const client = new FlagsSDK({
  projectKey: process.env.FEATURE_FLAGS_PROJECT_KEY,
  environmentKey: process.env.FEATURE_FLAGS_ENVIRONMENT_KEY,
  preloadFlags: ["header", "footer", "checkout"], // Preload these flags
});

// Initialize SDK before starting server
await client.init();

// Now flags are cached and ready to use
console.log("Flags preloaded and ready");

Error Handling

Always handle errors gracefully:

src/error-handling.js
async function getFlagSafely(slug, defaultValue = false) {
  try {
    const flag = await client.getFlag(slug);
    return flag.enabled;
  } catch (error) {
    console.error(`Failed to fetch flag "${slug}":`, error);
    // Return safe default value
    return defaultValue;
  }
}

// Usage
const isEnabled = await getFlagSafely("new-feature", false);

Best Practices

1. Use Singleton Pattern

Create a single SDK instance and reuse it across your application:

// flags.js
let client = null;

export function getFlagsClient() {
  if (!client) {
    client = new FlagsSDK(config);
  }
  return client;
}

2. Preload Critical Flags

Preload flags that are needed immediately:

const client = new FlagsSDK({
  // ... config
  preloadFlags: ["header", "footer", "checkout"],
});

await client.init();

3. Handle Errors Gracefully

Always provide fallback behavior:

try {
  const flag = await client.getFlag("feature");
  // Use flag
} catch (error) {
  // Fallback to default behavior
  useDefaultFeature();
}

4. Use Caching

Enable caching to reduce API calls:

const client = new FlagsSDK({
  // ... config
  cache: {
    enabled: true,
    ttl: 5 * 60 * 1000, // 5 minutes
  },
});

5. Environment-Specific Configuration

Use different configurations for different environments:

const isProduction = process.env.NODE_ENV === "production";

const client = new FlagsSDK({
  baseURL: isProduction 
    ? "https://flags-api.basestack.co/v1"
    : "https://flags-dev-api.basestack.co/v1",
  projectKey: process.env.FEATURE_FLAGS_PROJECT_KEY,
  environmentKey: isProduction
    ? process.env.PROD_ENVIRONMENT_KEY
    : process.env.DEV_ENVIRONMENT_KEY,
});

Troubleshooting

Fetch API Not Available

If you see errors about fetch not being available:

  1. Upgrade to Node.js 18+: The easiest solution
  2. Use cross-fetch: Install cross-fetch and pass it as fetchImpl
  3. Use Axios: Install axios and create a fetch-compatible wrapper

Flags Not Updating

If flags aren't updating:

  1. Check Cache: Flags are cached for 5 minutes by default. Clear cache or wait for TTL
  2. Verify Environment: Ensure you're using the correct environment key
  3. Check Network: Verify your server can reach the Basestack API

TypeScript Errors

If you're getting TypeScript errors:

  1. Install Types: Types are included with the package
  2. Check Imports: Use import type for type-only imports
  3. Type Assertions: Use type assertions for payloads when needed

TypeScript Support

The SDK is built with TypeScript and provides full type definitions:

src/types.ts
import type { FlagsSDK, Flag, SDKConfig, CacheConfig } from "@basestack/flags-js";

// Use types in your code
const config: SDKConfig = {
  projectKey: process.env.FEATURE_FLAGS_PROJECT_KEY!,
  environmentKey: process.env.FEATURE_FLAGS_ENVIRONMENT_KEY!,
};

const client = new FlagsSDK(config);
const flag: Flag = await client.getFlag("header");

Type Definitions

type-definitions.ts
/** Configuration for caching mechanism */
export interface CacheConfig {
  /** Enable or disable caching (default: true) */
  enabled?: boolean;
  /** Time-to-Live for cache entries in milliseconds (default: 5 minutes) */
  ttl?: number;
  /** Maximum number of cache entries (default: 100) */
  maxSize?: number;
}

/** SDK Configuration Options */
export interface SDKConfig {
  /** Base URL for the feature flag service (optional) */
  baseURL?: string;
  /** Project identification key (required) */
  projectKey: string;
  /** Environment identification key (required) */
  environmentKey: string;
  /** Caching configuration options */
  cache?: CacheConfig;
  /** Custom fetch implementation (optional) */
  fetchImpl?: typeof fetch;
  /** Flags to preload during initialization */
  preloadFlags?: string[];
}

/** Feature flag data structure */
export interface Flag {
  slug: string;
  enabled: boolean;
  payload?: unknown;
  expiredAt?: Date | null;
  createdAt: Date;
  updatedAt: Date;
  description: string;
  error?: boolean;
}

SDK Reference

Initialization Options

PropertyTypeDescriptionRequiredDefault Value
baseURLstringSpecifies the target URL to point to.falsehttps://flags-api.basestack.co/v1
projectKeystringSpecifies the project to retrieve data from.truenull
environmentKeystringSpecifies the project environment from which to retrieve flags.truenull
preloadFlagsstring[]List of flags to preload during initialization.false[]
cache.enabledbooleanEnables or disables caching of flags.falsetrue
cache.ttlnumberTime-to-live for cached flags in milliseconds.false5 * 60 * 1000
cache.maxSizenumberMaximum number of flags to store in the cache.false100
fetchImplfunctionCustom fetch implementation (for Node.js < 18).falseglobal.fetch

Available Methods

MethodDescriptionReturnsEnvironment
init() => Promise<void>Initiates the flags request, enabling caching capabilities.Promise<void>Browser & Node & Serverless
clearCache(): voidClears all cached flags.voidBrowser & Node & Serverless
clearFlagCache(slug: string): voidClears the cache for a specific flag identified by the slug.voidBrowser & Node & Serverless
getFlag(slug: string): Promise<Flag>Retrieves a specific flag by its slug.Promise<Flag>Browser & Node & Serverless
getAllFlags(): Promise<{ flags: Flag[] }>Retrieves all flags.Promise<{ flags: Flag[] }>Browser & Node & Serverless