Basestack Docs
Backend

Open Feature Node SDK

The Basestack Open Feature Node SDK provider allows you to use Basestack Feature Flags with the Open Feature standard in Node.js server applications. This provider bridges Basestack's feature flag infrastructure with Open Feature's standardized API, providing a vendor-neutral, portable feature flag solution for backend services.

Open Feature is an open standard for feature flag management that provides a consistent API across different providers. With this Node SDK, you can leverage Open Feature's excellent developer experience including type safety, and a standardized API while using Basestack as your feature flag backend. This is perfect for API routes, middleware, background jobs, and serverless functions.

Preview Status: This provider is currently in preview and still under active development. Basic feature flag functionality is supported, but advanced features like evaluation context and user identity targeting are still in the works. We recommend using this provider if you only need basic feature flag functionality.

Getting started

Install

Install the Open Feature Server SDK, core package, and the Basestack provider. The @openfeature/server-sdk provides the core Open Feature functionality for Node.js, while @basestack/openfeature-provider connects it to Basestack's API.

Install the SDK
npm install @openfeature/server-sdk @openfeature/core @basestack/openfeature-provider
pnpm install @openfeature/server-sdk @openfeature/core @basestack/openfeature-provider
yarn add @openfeature/server-sdk @openfeature/core @basestack/openfeature-provider
bun add @openfeature/server-sdk @openfeature/core @basestack/openfeature-provider

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_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.

Initialize the Provider

Create and configure the Basestack server provider, then set it as the default provider for Open Feature. The provider handles API communication, caching, and automatic flag refreshing.

src/main.ts
import { OpenFeature } from '@openfeature/server-sdk';
import { createBasestackServerProvider } from "@basestack/openfeature-provider/server";

const provider = createBasestackServerProvider({
  apiUrl: "https://flags-api.basestack.co/v1",
  projectKey: process.env.FEATURE_FLAGS_PROJECT_KEY,
  environmentKey: process.env.FEATURE_FLAGS_ENVIRONMENT_KEY,
  prefetch: true, // Prefetch all flags on initialization
  refreshIntervalMs: 30_000, // Refresh flags every 30 seconds
});

try {
  await OpenFeature.setProviderAndWait(provider);
  console.log('OpenFeature provider initialized successfully');
} catch (error) {
  console.error('Failed to initialize provider:', error);
}

The provider configuration includes:

  • apiUrl: Your Basestack API endpoint
  • projectKey & environmentKey: Your Basestack credentials
  • prefetch: Whether to fetch all flags on initialization
  • refreshIntervalMs: How often to refresh flags automatically (in milliseconds)

Use Flags in Your Application

Once initialized, get a client instance and use it to evaluate flags throughout your application. The client provides type-safe methods for different flag value types.

src/main.ts
import { OpenFeature } from '@openfeature/server-sdk';
import { createBasestackServerProvider } from "@basestack/openfeature-provider/server";

// ... provider initialization ...

const client = OpenFeature.getClient();

// Get boolean flag value (async)
const isHeaderEnabled = await client.getBooleanValue('header', false);

if (isHeaderEnabled) {
  console.log("Header is enabled");
  // Use new header logic
} else {
  // Use default header logic
}

// Get object flag value this is the payload of the flag
const config = await client.getObjectValue('header', { color: 'blue' });

The client provides async methods for different value types:

  • getBooleanValue(key, defaultValue, context?): Get a boolean flag
  • getObjectValue(key, defaultValue, context?): Get an object/JSON flag

Advanced Usage

Flag Details

Get detailed information about flag evaluations, including metadata and reasons:

src/main.ts
const client = OpenFeature.getClient();

// Get boolean flag details
const details = await client.getBooleanDetails('header', false);

console.log(details.value); // The flag value
console.log(details.variant); // The variant name
console.log(details.reason); // Why this value was returned
console.log(details.flagMetadata); // Additional metadata

// Similar methods exist for other types
const objectDetails = await client.getObjectDetails('header', { color: 'blue' });

Framework Examples

Express.js

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

src/flags.js
import { OpenFeature } from '@openfeature/server-sdk';
import { createBasestackServerProvider } from "@basestack/openfeature-provider/server";

let client = null;

export async function getFlagsClient() {
  if (!client) {
    const provider = createBasestackServerProvider({
      apiUrl: "https://flags-api.basestack.co/v1",
      projectKey: process.env.FEATURE_FLAGS_PROJECT_KEY,
      environmentKey: process.env.FEATURE_FLAGS_ENVIRONMENT_KEY,
      prefetch: true,
      refreshIntervalMs: 30_000,
    });

    await OpenFeature.setProviderAndWait(provider);
    client = OpenFeature.getClient();
  }
  return client;
}

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 = await getFlagsClient();
    const isEnabled = await client.getBooleanValue(req.params.slug, false);
    
    res.json({
      isEnabled,
    });
  } catch (error) {
    console.error('Failed to fetch flag', 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.ts
import { Hono } from "hono";
import { OpenFeature } from "@openfeature/server-sdk";
import { createBasestackServerProvider } from "@basestack/openfeature-provider/server";

const app = new Hono();

const provider = createBasestackServerProvider({
  apiUrl: "https://flags-api.basestack.co/v1",
  projectKey: process.env.FEATURE_FLAGS_PROJECT_KEY,
  environmentKey: process.env.FEATURE_FLAGS_ENVIRONMENT_KEY,
  prefetch: true,
  refreshIntervalMs: 30_000,
});

OpenFeature.setProvider(provider);

const client = OpenFeature.getClient();

app.get("/experiments/header", async (c) => {
  const details = await client.getObjectDetails<{ color?: string }>("header", { color: "blue" });

  return c.json({
    payload: details.value,
    isEnabled: details.variant === "enabled",
    metadata: details.flagMetadata,
  });
});

export default app;

NestJS

Create a service provider for NestJS:

src/flags/flags.service.ts
import { Injectable, OnModuleInit } from "@nestjs/common";
import { OpenFeature } from "@openfeature/server-sdk";
import { createBasestackServerProvider } from "@basestack/openfeature-provider/server";
import type { Client } from "@openfeature/core";

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

  async onModuleInit() {
    const provider = createBasestackServerProvider({
      apiUrl: "https://flags-api.basestack.co/v1",
      projectKey: process.env.FEATURE_FLAGS_PROJECT_KEY,
      environmentKey: process.env.FEATURE_FLAGS_ENVIRONMENT_KEY,
      prefetch: true,
      refreshIntervalMs: 30_000,
    });

    await OpenFeature.setProviderAndWait(provider);
    this.client = OpenFeature.getClient();
  }

  async getBooleanFlag(slug: string, defaultValue = false) {
    return this.client.getBooleanValue(slug, defaultValue);
  }

  async getObjectFlag<T>(slug: string, defaultValue: T) {
    return this.client.getObjectValue<T>(slug, defaultValue);
  }
}

Use in controllers:

src/features/features.controller.ts
import { Controller, Get, Param, Headers } 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,
    @Headers("x-user-id") userId?: string
  ) {
    const enabled = await this.flagsService.getBooleanFlag(slug, false);
    
    return {
      enabled,
      slug,
    };
  }
}

Fastify

Fastify is a fast and low overhead web framework:

src/index.js
import Fastify from "fastify";
import { OpenFeature } from "@openfeature/server-sdk";
import { createBasestackServerProvider } from "@basestack/openfeature-provider/server";

const fastify = Fastify({ logger: true });

const provider = createBasestackServerProvider({
  apiUrl: "https://flags-api.basestack.co/v1",
  projectKey: process.env.FEATURE_FLAGS_PROJECT_KEY,
  environmentKey: process.env.FEATURE_FLAGS_ENVIRONMENT_KEY,
  prefetch: true,
  refreshIntervalMs: 30_000,
});

await OpenFeature.setProviderAndWait(provider);
const client = OpenFeature.getClient();

fastify.get("/api/feature/:slug", async (request, reply) => {
  const isEnabled = await client.getBooleanValue(request.params.slug, false);
  
  return { enabled: isEnabled };
});

const start = async () => {
  try {
    await fastify.listen({ port: 3000 });
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
};

start();

Bun

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

src/server.ts
import { OpenFeature } from "@openfeature/server-sdk";
import { createBasestackServerProvider } from "@basestack/openfeature-provider/server";

const provider = createBasestackServerProvider({
  apiUrl: "https://flags-api.basestack.co/v1",
  projectKey: process.env.FEATURE_FLAGS_PROJECT_KEY,
  environmentKey: process.env.FEATURE_FLAGS_ENVIRONMENT_KEY,
  prefetch: true,
  refreshIntervalMs: 30_000,
});

await OpenFeature.setProviderAndWait(provider);
const client = OpenFeature.getClient();

Bun.serve({
  port: 3000,
  async fetch(req) {
    const url = new URL(req.url);
    
    if (url.pathname.startsWith("/api/flag/")) {
      const slug = url.pathname.split("/").pop();
      const isEnabled = await client.getBooleanValue(slug!, false);
      
      return Response.json({
        enabled: isEnabled,
      });
    }
    
    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 { OpenFeature } from "@openfeature/server-sdk";
import { createBasestackServerProvider } from "@basestack/openfeature-provider/server";

// Initialize outside handler to reuse across invocations
let client = null;

async function getClient() {
  if (!client) {
    const provider = createBasestackServerProvider({
      apiUrl: "https://flags-api.basestack.co/v1",
      projectKey: process.env.FEATURE_FLAGS_PROJECT_KEY,
      environmentKey: process.env.FEATURE_FLAGS_ENVIRONMENT_KEY,
      prefetch: true,
      refreshIntervalMs: 30_000,
    });

    await OpenFeature.setProviderAndWait(provider);
    client = OpenFeature.getClient();
  }
  return client;
}

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

Configuration Options

The createBasestackServerProvider function accepts the following configuration:

OptionTypeRequiredDefaultDescription
apiUrlstringYes-Basestack API endpoint URL
projectKeystringYes-Your Basestack project key
environmentKeystringYes-Your Basestack environment key
prefetchbooleanNofalseWhether to fetch all flags on initialization
refreshIntervalMsnumberNo30000How often to refresh flags automatically (milliseconds)

Best Practices

1. Initialize Provider Early

Initialize the provider as early as possible in your application lifecycle:

// Initialize before starting server
await OpenFeature.setProviderAndWait(provider);
// Then start your server

2. Use Singleton Pattern

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

let client = null;

export async function getClient() {
  if (!client) {
    await OpenFeature.setProviderAndWait(provider);
    client = OpenFeature.getClient();
  }
  return client;
}

3. Use Appropriate Default Values

Always provide sensible default values that represent the "off" or "safe" state:

// Good: Safe default
const isEnabled = await client.getBooleanValue('risky-feature', false);

// Avoid: Unsafe default
const isEnabled = await client.getBooleanValue('risky-feature', true);

4. Handle Errors Gracefully

Always handle provider initialization errors gracefully:

try {
  await OpenFeature.setProviderAndWait(provider);
} catch (error) {
  console.error('Failed to initialize provider:', error);
  // Fallback to default behavior or exit gracefully
}

5. Use Flag Details for Debugging

Use getDetails methods when you need to understand why a flag returned a specific value:

const details = await client.getBooleanDetails('feature', false);
console.log('Flag reason:', details.reason);
console.log('Flag metadata:', details.flagMetadata);

6. Optimize Refresh Intervals

Balance freshness with performance by setting appropriate refresh intervals:

// Frequent updates for critical flags
refreshIntervalMs: 10_000, // 10 seconds

// Less frequent for stable flags
refreshIntervalMs: 60_000, // 1 minute

Troubleshooting

Provider Not Initializing

If the provider fails to initialize:

  1. Check Environment Variables: Ensure your project and environment keys are set correctly
  2. Verify API URL: Confirm the apiUrl is correct and accessible
  3. Check Network: Verify your server can reach the Basestack API endpoint
  4. Review Logs: Check server logs for detailed error messages

Flags Always Returning Default Values

If flags always return default values:

  1. Check Flag Keys: Verify the flag key matches what's configured in Basestack
  2. Verify Credentials: Ensure project and environment keys are correct
  3. Check Provider Status: Ensure the provider initialized successfully
  4. Inspect Network: Check network requests to see if API calls are being made

Flags Not Updating

If flags aren't updating:

  1. Check Refresh Interval: Flags refresh based on refreshIntervalMs. Wait for the interval or reduce it
  2. Verify Prefetch: If prefetch is enabled, flags are fetched on initialization
  3. Check Provider Events: Listen to ConfigurationChanged events to detect updates
  4. Manual Refresh: Consider implementing manual refresh if needed

Type Errors

If you're getting TypeScript errors:

  1. Install Types: Ensure @openfeature/core types are installed
  2. Check Imports: Verify you're importing from the correct packages
  3. Type Assertions: Use type assertions for object values when needed

Performance Issues

If you're experiencing performance problems:

  1. Increase Refresh Interval: Reduce API calls by increasing refreshIntervalMs
  2. Disable Prefetch: If you don't need all flags, set prefetch: false
  3. Use Caching: The provider includes built-in caching, but you can optimize further
  4. Singleton Pattern: Ensure you're reusing client instances rather than creating new ones