Node.js
Node.js integration for server-side applications and APIs using the JavaScript SDK.
Requirements
- Node.js 16+
- NPM, Yarn, or PNPM
Installation
npm install @darkfeature/sdk-javascript
# or
yarn add @darkfeature/sdk-javascript
# or
pnpm add @darkfeature/sdk-javascript
Basic Usage
import { DarkFeatureClient } from '@darkfeature/sdk-javascript'
const client = new DarkFeatureClient({
apiKey: 'your-api-key', // Store in environment variable
context: {
userId: '123',
email: '[email protected]',
version: '1.0.0',
},
})
// Get a single feature flag
const isEnabled = await client.getFeature('new-navigation', { fallback: false })
console.log(`Feature is ${isEnabled ? 'enabled' : 'disabled'}`)
Express.js Middleware
Integrate feature flags into your Express.js application:
import express from 'express'
import { DarkFeatureClient } from '@darkfeature/sdk-javascript'
const app = express()
const client = new DarkFeatureClient({
apiKey: process.env.DARKFEATURE_API_KEY,
})
// Middleware to add feature flags to requests
app.use(async (req, res, next) => {
const context = {
userId: req.user?.id,
email: req.user?.email,
userAgent: req.get('User-Agent'),
}
req.features = await client.getFeatures({
features: {
'new-navigation': false,
'advanced-analytics': false,
'beta-ui': false,
},
context,
})
next()
})
app.get('/api/data', async (req, res) => {
if (req.features['new-navigation']) {
// New implementation
res.json({ data: 'enhanced-data', version: 'v2' })
} else {
// Legacy implementation
res.json({ data: 'basic-data', version: 'v1' })
}
})
app.listen(3000, () => {
console.log('Server running on port 3000')
})
TypeScript with Fastify
Use with Fastify and TypeScript for type-safe feature flag integration:
import Fastify, { FastifyRequest, FastifyReply } from 'fastify'
import { DarkFeatureClient } from '@darkfeature/sdk-javascript'
interface UserContext {
userId?: string
email?: string
plan: 'free' | 'pro' | 'enterprise'
}
const fastify = Fastify({ logger: true })
const client = new DarkFeatureClient({
apiKey: process.env.DARKFEATURE_API_KEY,
})
// Declare feature flags interface
declare module 'fastify' {
interface FastifyRequest {
features: {
'new-navigation': boolean
'premium-support': boolean
'advanced-metrics': boolean
}
}
}
// Plugin to add feature flags
fastify.addHook(
'preHandler',
async (request: FastifyRequest, reply: FastifyReply) => {
const context: UserContext = {
userId: request.user?.id,
email: request.user?.email,
plan: request.user?.plan || 'free',
}
request.features = await client.getFeatures({
features: {
'new-navigation': false,
'premium-support': false,
'advanced-metrics': false,
},
context,
})
},
)
fastify.get('/api/dashboard', async (request, reply) => {
const data: any = { dashboard: 'basic' }
if (request.features['new-navigation']) {
data.enhanced = true
data.charts = ['revenue', 'users', 'engagement']
}
if (request.features['advanced-metrics']) {
data.metrics = { realtime: true, historical: true }
}
return data
})
fastify.listen({ port: 3000 }, err => {
if (err) throw err
console.log('Server running on port 3000')
})
Advanced Configuration
Environment Variables
// .env
DARKFEATURE_API_KEY=your-server-side-api-key
DARKFEATURE_BASE_URL=https://api.darkfeature.com
DARKFEATURE_TIMEOUT=5000
// app.js
import { DarkFeatureClient } from '@darkfeature/sdk-javascript'
const client = new DarkFeatureClient({
apiKey: process.env.DARKFEATURE_API_KEY,
baseUrl: process.env.DARKFEATURE_BASE_URL,
timeout: parseInt(process.env.DARKFEATURE_TIMEOUT) || 5000,
cache: {
enabled: true,
ttl: 300000, // 5 minutes
maxSize: 1000,
},
debug: process.env.NODE_ENV === 'development',
})
Error Handling
import { DarkFeatureClient } from '@darkfeature/sdk-javascript'
const client = new DarkFeatureClient({
apiKey: process.env.DARKFEATURE_API_KEY,
})
async function getFeatureWithFallback(featureName, fallback = false) {
try {
return await client.getFeature(featureName, { fallback })
} catch (error) {
console.error(`Error getting feature ${featureName}:`, error)
return fallback
}
}
// Usage
const isEnabled = await getFeatureWithFallback('new-feature', false)
Server-Side Rendering (Next.js)
// pages/api/features.js
import { DarkFeatureClient } from '@darkfeature/sdk-javascript'
const client = new DarkFeatureClient({
apiKey: process.env.DARKFEATURE_API_KEY,
})
export default async function handler(req, res) {
const { userId, email } = req.query
const features = await client.getFeatures({
features: {
'new-navigation': false,
'premium-features': false,
},
context: {
userId,
email,
},
})
res.json(features)
}
// pages/index.js
export async function getServerSideProps(context) {
const user = getUserFromContext(context)
// Pre-fetch feature flags on server
const features = await fetch(
'/api/features?' +
new URLSearchParams({
userId: user.id,
email: user.email,
}),
).then(res => res.json())
return {
props: {
user,
features,
},
}
}
Testing
import { DarkFeatureClient } from '@darkfeature/sdk-javascript'
// Mock client for testing
class MockDarkFeatureClient extends DarkFeatureClient {
constructor(mockFlags = {}) {
super({ apiKey: 'test-key' })
this.mockFlags = mockFlags
}
async getFeature(featureName, options = {}) {
return this.mockFlags[featureName] ?? options.fallback
}
async getFeatures(features, options = {}) {
const result = {}
for (const [key, fallback] of Object.entries(features)) {
result[key] = this.mockFlags[key] ?? fallback
}
return result
}
}
// Test usage
const mockClient = new MockDarkFeatureClient({
'new-navigation': true,
'premium-features': false,
})
const isEnabled = await mockClient.getFeature('new-navigation', {
fallback: false,
})
console.log(isEnabled) // true
Performance Optimization
Caching
import { DarkFeatureClient } from '@darkfeature/sdk-javascript'
const client = new DarkFeatureClient({
apiKey: process.env.DARKFEATURE_API_KEY,
cache: {
enabled: true,
ttl: 300000, // 5 minutes
maxSize: 1000,
},
})
// Features will be cached automatically
const isEnabled = await client.getFeature('new-navigation', { fallback: false })
Batch Requests
// Get multiple features in one request
const features = await client.getFeatures({
features: {
'feature-a': false,
'feature-b': false,
'feature-c': false,
},
context: { userId: '123' },
})
// Use features
if (features['feature-a']) {
// Feature A logic
}
if (features['feature-b']) {
// Feature B logic
}
Best Practices
1. Environment Configuration
// config/features.js
import { DarkFeatureClient } from '@darkfeature/sdk-javascript'
const createFeatureClient = () => {
return new DarkFeatureClient({
apiKey: process.env.DARKFEATURE_API_KEY,
baseUrl: process.env.DARKFEATURE_BASE_URL,
timeout: parseInt(process.env.DARKFEATURE_TIMEOUT) || 5000,
cache: {
enabled: process.env.NODE_ENV === 'production',
ttl: 300000,
maxSize: 1000,
},
debug: process.env.NODE_ENV === 'development',
})
}
export default createFeatureClient
2. Middleware Pattern
// middleware/features.js
import { DarkFeatureClient } from '@darkfeature/sdk-javascript'
const client = new DarkFeatureClient({
apiKey: process.env.DARKFEATURE_API_KEY,
})
export const featureFlagsMiddleware = featureKeys => {
return async (req, res, next) => {
try {
const context = {
userId: req.user?.id,
email: req.user?.email,
userAgent: req.get('User-Agent'),
ip: req.ip,
}
req.features = await client.getFeatures({
features: featureKeys,
context,
})
next()
} catch (error) {
console.error('Feature flags error:', error)
// Use fallback values
req.features = Object.fromEntries(
Object.keys(featureKeys).map(key => [key, featureKeys[key]]),
)
next()
}
}
}
// Usage
app.use(
featureFlagsMiddleware({
'new-navigation': false,
'premium-features': false,
}),
)
3. Error Handling
// utils/features.js
import { DarkFeatureClient } from '@darkfeature/sdk-javascript'
const client = new DarkFeatureClient({
apiKey: process.env.DARKFEATURE_API_KEY,
})
export async function getFeatureSafely(featureName, fallback = false) {
try {
return await client.getFeature(featureName, { fallback })
} catch (error) {
console.error(`Feature flag error for ${featureName}:`, error)
return fallback
}
}
export async function getFeaturesSafely(features, context = {}) {
try {
return await client.getFeatures({ features, context })
} catch (error) {
console.error('Feature flags error:', error)
return features // Return fallback values
}
}
Security Considerations
API Key Management
// Never expose server-side API keys to clients
const client = new DarkFeatureClient({
apiKey: process.env.DARKFEATURE_API_KEY, // ✅ Secure
// apiKey: 'hardcoded-key', // ❌ Never do this
})
User Context Validation
// Validate and sanitize user context
const sanitizeContext = (user) => {
return {
userId: user?.id?.toString(),
email: user?.email?.toLowerCase(),
plan: ['free', 'pro', 'enterprise'].includes(user?.plan) ? user.plan : 'free',
}
}
const context = sanitizeContext(req.user)
const features = await client.getFeatures({ features: {...}, context })
Next Steps
- Client-Side Code Examples - Frontend integration
- Platform Overview - Learn about advanced features
- Best Practices - Follow proven patterns
Last updated on