@subflag/openfeature-node-provider
v0.3.0
Published
OpenFeature Node.js provider for Subflag feature flags
Maintainers
Readme
@subflag/openfeature-node-provider
OpenFeature Node.js provider for Subflag feature flags.
Installation
npm install @subflag/openfeature-node-provider @openfeature/server-sdkor with pnpm:
pnpm add @subflag/openfeature-node-provider @openfeature/server-sdkQuick Start
import { OpenFeature } from '@openfeature/server-sdk';
import { SubflagNodeProvider } from '@subflag/openfeature-node-provider';
// Initialize the provider
const provider = new SubflagNodeProvider({
apiUrl: 'http://localhost:8080',
apiKey: 'sdk-production-my-app-your-key-here',
});
// Set provider and wait for it to be ready
await OpenFeature.setProviderAndWait(provider);
// Get a client
const client = OpenFeature.getClient();
// Evaluate flags
const isEnabled = await client.getBooleanValue('new-feature', false);
const bannerText = await client.getStringValue('banner-text', 'Welcome!');
const maxItems = await client.getNumberValue('max-items', 10);
const config = await client.getObjectValue('ui-config', { theme: 'light' });Configuration
SubflagProviderConfig
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| apiUrl | string | Yes | The Subflag API URL (e.g., "http://localhost:8080") |
| apiKey | string | Yes | Your SDK API key (format: sdk-{env}-{random}) |
| timeout | number | No | Request timeout in milliseconds (default: 5000) |
| cache | CacheConfig | No | Cache configuration (see Caching below) |
Caching
By default, the provider makes an API call for every flag evaluation. For better performance, you can enable caching with a pluggable cache interface.
Using the Built-in InMemoryCache
import { OpenFeature } from '@openfeature/server-sdk';
import { SubflagNodeProvider, InMemoryCache } from '@subflag/openfeature-node-provider';
const provider = new SubflagNodeProvider({
apiUrl: 'http://localhost:8080',
apiKey: 'sdk-production-...',
cache: {
cache: new InMemoryCache(),
ttlSeconds: 30, // Cache values for 30 seconds
},
});
await OpenFeature.setProviderAndWait(provider);
const client = OpenFeature.getClient();
// First call hits the API
await client.getBooleanValue('my-flag', false);
// Subsequent calls within TTL use cached value
await client.getBooleanValue('my-flag', false); // No API callUsing Redis (or any custom cache)
Implement the SubflagCache interface to use Redis, Memcached, or any other cache:
import { SubflagNodeProvider, SubflagCache } from '@subflag/openfeature-node-provider';
import Redis from 'ioredis';
const redis = new Redis();
const redisCache: SubflagCache = {
async get(key) {
const data = await redis.get(key);
return data ? JSON.parse(data) : undefined;
},
async set(key, value, ttlSeconds) {
await redis.setex(key, ttlSeconds, JSON.stringify(value));
},
async delete(key) {
await redis.del(key);
},
async clear() {
// Optional: implement if needed
},
};
const provider = new SubflagNodeProvider({
apiUrl: 'http://localhost:8080',
apiKey: 'sdk-production-...',
cache: {
cache: redisCache,
ttlSeconds: 60,
},
});Cache Configuration Options
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| cache | SubflagCache | Yes | Cache implementation |
| ttlSeconds | number | No | Time-to-live in seconds (default: 60) |
| keyGenerator | function | No | Custom cache key generator |
Custom Cache Keys
By default, cache keys are generated as subflag:{flagKey}:{contextHash}. You can customize this:
const provider = new SubflagNodeProvider({
apiUrl: 'http://localhost:8080',
apiKey: 'sdk-production-...',
cache: {
cache: new InMemoryCache(),
ttlSeconds: 30,
keyGenerator: (flagKey, context) => {
return `myapp:flags:${flagKey}:${context?.targetingKey || 'anonymous'}`;
},
},
});Context-Aware Caching
The cache automatically accounts for different evaluation contexts. Different users/contexts get separate cache entries:
// These use different cache entries
await client.getBooleanValue('premium-feature', false, { targetingKey: 'user-1' });
await client.getBooleanValue('premium-feature', false, { targetingKey: 'user-2' });Prefetching Flags
For optimal performance, prefetch all flags in a single API call. This is especially useful in request handlers where you evaluate multiple flags:
import { OpenFeature } from '@openfeature/server-sdk';
import { SubflagNodeProvider, InMemoryCache } from '@subflag/openfeature-node-provider';
const provider = new SubflagNodeProvider({
apiUrl: 'http://localhost:8080',
apiKey: 'sdk-production-...',
cache: {
cache: new InMemoryCache(),
ttlSeconds: 30,
},
});
await OpenFeature.setProviderAndWait(provider);
// In your request handler:
app.get('/api/data', async (req, res) => {
const context = { targetingKey: req.user.id };
// Prefetch all flags for this user (1 API call)
await provider.prefetchFlags(context);
// All subsequent evaluations use cache (0 API calls)
const client = OpenFeature.getClient();
const showNewUI = await client.getBooleanValue('new-ui', false, context);
const maxItems = await client.getNumberValue('max-items', 10, context);
const theme = await client.getStringValue('theme', 'light', context);
res.json({ showNewUI, maxItems, theme });
});Note: prefetchFlags() requires caching to be enabled. It will throw an error if called without a cache configured.
Getting an API Key
See Team Management → API Keys in the docs for instructions on creating API keys.
Usage with Express.js
import express from 'express';
import { OpenFeature } from '@openfeature/server-sdk';
import { SubflagNodeProvider } from '@subflag/openfeature-node-provider';
const app = express();
// Initialize provider on startup
const provider = new SubflagNodeProvider({
apiUrl: process.env.SUBFLAG_API_URL || 'http://localhost:8080',
apiKey: process.env.SUBFLAG_API_KEY || '',
});
await OpenFeature.setProviderAndWait(provider);
// Get a client (can be reused across requests)
const featureClient = OpenFeature.getClient();
app.get('/api/data', async (req, res) => {
// Check feature flag
const usePagination = await featureClient.getBooleanValue('use-pagination', false);
if (usePagination) {
const pageSize = await featureClient.getNumberValue('page-size', 20);
// Return paginated data
res.json({ items: [], pageSize });
} else {
// Return all data
res.json({ items: [] });
}
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});Environment Variables
Store your configuration in environment variables:
# .env
SUBFLAG_API_URL=http://localhost:8080
SUBFLAG_API_KEY=sdk-production-web-app-xQ7mK9nP2wR5tY8uI1oA3sD4Then use a package like dotenv:
import 'dotenv/config';
import { OpenFeature } from '@openfeature/server-sdk';
import { SubflagNodeProvider } from '@subflag/openfeature-node-provider';
const provider = new SubflagNodeProvider({
apiUrl: process.env.SUBFLAG_API_URL!,
apiKey: process.env.SUBFLAG_API_KEY!,
});Supported Flag Types
The provider supports all OpenFeature value types:
- Boolean:
getBooleanValue(flagKey, defaultValue) - String:
getStringValue(flagKey, defaultValue) - Number:
getNumberValue(flagKey, defaultValue) - Object:
getObjectValue(flagKey, defaultValue)
Error Handling
The provider handles errors gracefully and returns default values when:
- The flag doesn't exist (404)
- The API key is invalid (401/403)
- Network errors occur
- The flag value type doesn't match the requested type
const client = OpenFeature.getClient();
// If 'missing-flag' doesn't exist, returns false
const value = await client.getBooleanValue('missing-flag', false);
// You can also get detailed evaluation information
const details = await client.getBooleanDetails('my-flag', false);
console.log(details.reason); // 'STATIC', 'DEFAULT', or 'ERROR'
console.log(details.variant); // Variant name (e.g., 'control', 'treatment')
console.log(details.errorCode); // Error code if reason is 'ERROR'CommonJS Support
This package supports both ESM and CommonJS:
// ESM
import { SubflagNodeProvider } from '@subflag/openfeature-node-provider';
// CommonJS
const { SubflagNodeProvider } = require('@subflag/openfeature-node-provider');Development
# Install dependencies
pnpm install
# Build the provider
pnpm build
# Run tests
pnpm test
# Run tests in watch mode
pnpm test:watch
# Type check
pnpm typecheckLicense
MIT
