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
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+).
npm install @basestack/flags-jspnpm install @basestack/flags-jsyarn add @basestack/flags-jsbun add @basestack/flags-jsEnvironment Variables
Configure your environment variables to connect to your Basestack Feature Flags project. These values identify your project and environment.
# 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+):
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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
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:
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:
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:
- Upgrade to Node.js 18+: The easiest solution
- Use cross-fetch: Install
cross-fetchand pass it asfetchImpl - Use Axios: Install
axiosand create a fetch-compatible wrapper
Flags Not Updating
If flags aren't updating:
- Check Cache: Flags are cached for 5 minutes by default. Clear cache or wait for TTL
- Verify Environment: Ensure you're using the correct environment key
- Check Network: Verify your server can reach the Basestack API
TypeScript Errors
If you're getting TypeScript errors:
- Install Types: Types are included with the package
- Check Imports: Use
import typefor type-only imports - Type Assertions: Use type assertions for payloads when needed
TypeScript Support
The SDK is built with TypeScript and provides full type definitions:
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
/** 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
| Property | Type | Description | Required | Default Value |
|---|---|---|---|---|
baseURL | string | Specifies the target URL to point to. | false | https://flags-api.basestack.co/v1 |
projectKey | string | Specifies the project to retrieve data from. | true | null |
environmentKey | string | Specifies the project environment from which to retrieve flags. | true | null |
preloadFlags | string[] | List of flags to preload during initialization. | false | [] |
cache.enabled | boolean | Enables or disables caching of flags. | false | true |
cache.ttl | number | Time-to-live for cached flags in milliseconds. | false | 5 * 60 * 1000 |
cache.maxSize | number | Maximum number of flags to store in the cache. | false | 100 |
fetchImpl | function | Custom fetch implementation (for Node.js < 18). | false | global.fetch |
Available Methods
| Method | Description | Returns | Environment |
|---|---|---|---|
init() => Promise<void> | Initiates the flags request, enabling caching capabilities. | Promise<void> | Browser & Node & Serverless |
clearCache(): void | Clears all cached flags. | void | Browser & Node & Serverless |
clearFlagCache(slug: string): void | Clears the cache for a specific flag identified by the slug. | void | Browser & 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 |