OpenFeature Node.js Provider
Introduction
The OpenFeature Node.js Provider is intended to be used in combination with the OpenFeature Node.js SDK. The provider wraps the ConfigDirector Node.js SDK.
The minimum Node.js version supported is 18.0.
Installation
The provider can be installed from NPM: https://www.npmjs.com/package/@configdirector/openfeature-server-provider
npm install --save @openfeature/server-sdk @configdirector/openfeature-server-provider
yarn add @openfeature/server-sdk @openfeature/core @configdirector/openfeature-server-provider
pnpm add @openfeature/server-sdk @openfeature/core @configdirector/openfeature-server-provider
bun add @openfeature/server-sdk @openfeature/core @configdirector/openfeature-server-provider
Configure and initialize the client
- Create an instance of the provider using your server SDK key. You can retrieve a server SDK key for each environment under
SDK keysin the dashboard's navigation panel. - Set the OpenFeature provider.
- Get a client instance from OpenFeature.
import { OpenFeature } from "@openfeature/server-sdk";
import { ConfigDirectorProvider } from "@configdirector/openfeature-server-provider";
const provider = new ConfigDirectorProvider("YOUR-SERVER-SDK-KEY");
try {
await OpenFeature.setProviderAndWait(provider);
} catch (error) {
console.warn("Starting on in-code default values until ConfigDirector sends config:", error);
}
const client = OpenFeature.getClient();
When no config arrives in time
setProviderAndWait rejects with ProviderNotReadyError when no config arrives from ConfigDirector within the connection timeout, which is 3 seconds unless you set connection.timeout in milliseconds. The provider then reports the ERROR status, with an Error event, and keeps connecting in the background, unless ConfigDirector refused the connection, as it does for an invalid SDK key. Until config arrives, flags resolve to their in-code default values with the PROVIDER_NOT_READY error code. Once it arrives, the provider reports READY, with one Ready event.
The example above catches the rejection, logs it and starts on in-code default values. To start without waiting for ConfigDirector at all, set the provider with setProvider instead:
OpenFeature.setProvider(new ConfigDirectorProvider("YOUR-SERVER-SDK-KEY"));
Before version 1.12.0 of the provider, setProviderAndWait resolved after the timeout, and the provider reported READY while it served in-code default values.
Additional configuration options
Additional configuration options can be passed into the provider in the optional second argument of the constructor.
For example, the metadata can be provided like this:
import { ConfigDirectorProvider } from "@configdirector/openfeature-server-provider";
const provider = new ConfigDirectorProvider("YOUR-SERVER-SDK-KEY", {
metadata: {
appName: "YOUR-APP-NAME",
appVersion: "1.0.2",
},
});
The provider accepts the same configuration options as the Node.js SDK client, refer to the additional configuration options section of the Node.js SDK for a full list.
Retrieve config values
To retrieve config values, use the OpenFeature client:
const booleanValue = client.getBooleanValue("my-config-key", false);
const stringValue = client.getStringValue("my-string-config-key", "Default");
For additional information regarding the OpenFeature client refer to the OpenFeature Node.js SDK documentation.
Evaluation details and hooks
The detailed getters, such as getBooleanDetails, say why a value was returned:
| Outcome | Reason | Error code | Variant |
|---|---|---|---|
| A value was found | TARGETING_MATCH | The value ID | |
| The config carries no value | DEFAULT | ||
| The config key is unknown | ERROR | FLAG_NOT_FOUND | |
| No config has arrived yet | ERROR | PROVIDER_NOT_READY | |
| The value cannot be read as the requested type | ERROR | TYPE_MISMATCH |
The variant is ConfigDirector's ID for the value served, a stable hash you can send to analytics in place of the value itself; values served in offline mode have none. In the three error cases the getters return the in-code default value you pass, and OpenFeature runs the error stage of your hooks, so an error-monitoring hook sees a missing config or a value of another type. getObjectValue with an object as its in-code default value reads only a JSON object, and with an array only a JSON array; JSON of the other shape gives TYPE_MISMATCH.
Before version 1.12.0 of the provider, the details carried no reason, variant or error code, and error hooks did not run.
Context
The context can be provided as the third argument to value getter functions of the OpenFeature client. The OpenFeature Node.js provider evaluates targeting rules locally without additional network calls for different contexts.
const booleanValue = await client.getBooleanValue("my-config-key", false, {
targetingKey: "12345", // In OpenFeature, the `targetingKey` represents the context's identifier
name: "Example User",
traits: {
region: "North America", // Any arbitrary traits which can be referenced in targeting rules
},
});
To evaluate flags for an entity other than a user, such as a tenant, set the context kind with a top-level kind attribute:
const booleanValue = await client.getBooleanValue("my-config-key", false, {
targetingKey: "acme",
kind: "tenant",
name: "Acme Inc.",
traits: {
plan: "enterprise",
},
});
Leaving kind out means user. A kind inside traits is an ordinary trait, not the context kind. A context kind is 1 to 64 lowercase letters, digits, dashes, or underscores. A context with an invalid context kind is ignored: the SDK logs an error, and flags still resolve successfully, as if no context had been given. Context kinds are available from version 1.10.0 of the provider.
For additional information regarding the OpenFeature client refer to the OpenFeature Node.js SDK documentation.
Test your code
Code that reads flags through OpenFeature is tested by swapping the provider, so it never has to change for a test and nothing from ConfigDirector is involved. The OpenFeature Node.js SDK ships an in-memory provider for that: TypedInMemoryProvider, in @openfeature/server-sdk 1.21 and newer, which serves the values your test sets. Earlier versions ship the same provider as InMemoryProvider, now deprecated.
Each flag is a set of named variants and the defaultVariant it resolves to:
import { OpenFeature, TypedInMemoryProvider } from "@openfeature/server-sdk";
const provider = new TypedInMemoryProvider({
"new-checkout": { variants: { on: true, off: false }, defaultVariant: "on" },
"max-items": { variants: { default: 20 }, defaultVariant: "default" },
});
afterEach(() => OpenFeature.close());
test("shows the new checkout", async () => {
await OpenFeature.setProviderAndWait(provider);
const client = OpenFeature.getClient();
expect(await client.getBooleanValue("new-checkout", false)).toBe(true);
expect(await client.getNumberValue("max-items", 0)).toBe(20);
provider.putConfiguration({
"new-checkout": { variants: { on: true, off: false }, defaultVariant: "off" },
});
expect(await client.getBooleanValue("new-checkout", true)).toBe(false);
});
putConfiguration replaces every flag and emits ConfigurationChanged with the keys it was given. A flag can also carry a contextEvaluator, a function from the evaluation context to the name of the variant to serve, for a test of code that targets specific users. OpenFeature is one global, so shut it down after each test with OpenFeature.close(), which lets the next test register its own provider.