Framework guides
Express.js
An Express.js process keeps one Featurevisor instance for every request, and each request evaluates with its own context.
Install#
In an existing Express application:
npm install @featurevisor/sdkSave the refresh helper from the Node.js guide as datafile-refresh.mjs, next to server.mjs. You need only the helper, not the Node.js startup code.
The server#
This server loads the datafile before it listens, refreshes it every five minutes, and closes cleanly on SIGTERM. It expects authentication middleware that sets req.user from a verified session:
import express from 'express'import { createServer } from 'node:http'import { once } from 'node:events'import { createFeaturevisor } from '@featurevisor/sdk'import { refreshDatafile, waitForRefresh } from './datafile-refresh.mjs'const PORT = 3000const DATAFILE_URL = 'https://cdn.example.com/featurevisor-all.json'const app = express()const server = createServer(app)const f = createFeaturevisor({})// Register your authentication middleware here, before the routesapp.get('/checkout', (req, res) => { if (!req.user) return res.sendStatus(401) const context = { userId: String(req.user.id), country: req.user.country, } if (f.isEnabled('checkout', context)) { res.send('New checkout') } else { res.send('Checkout') }})let refreshTimerserver.on('close', () => { clearInterval(refreshTimer) waitForRefresh(f).then(() => f.close()).catch(reportError)})async function start() { await refreshDatafile(f, DATAFILE_URL) server.listen(PORT) await once(server, 'listening') refreshTimer = setInterval(() => { refreshDatafile(f, DATAFILE_URL).catch((error) => console.error('Refresh failed:', error), ) }, 5 * 60 * 1000) refreshTimer.unref() const shutdown = () => { clearInterval(refreshTimer) // close runs once active requests have finished server.close() } process.once('SIGTERM', shutdown) process.once('SIGINT', shutdown)}function reportError(error) { console.error('Application failed:', error) process.exitCode = 1}start() .catch(async (error) => { reportError(error) await f.close() }) .catch(reportError)Run it with node server.mjs. It needs a Node.js version with fetch and AbortSignal.timeout.
- If the first datafile cannot be loaded, the server does not start.
- A failed refresh keeps the previous datafile.
- On shutdown, the server stops accepting requests, lets the current ones finish, waits for any refresh in progress, then closes the instance.
- If your application already handles signals, add these steps to its handler instead. Never add a second refresh loop, and never call
setContext()on the shared instance for one user.
A child instance per request#
When routes in several modules evaluate for the same user, give each request a child instance with that user's context. Register this middleware after authentication and before the routes that use it, since Express runs middleware in the order it is added:
app.use((req, res, next) => { if (req.user) { req.features = f.spawn({ userId: String(req.user.id), country: req.user.country, }) res.on('close', () => req.features.close()) } next()})A route then reads values without building the context again:
app.get('/basket', (req, res) => { if (!req.features) return res.sendStatus(401) res.json({ newCheckout: req.features.isEnabled('checkout'), buyLabel: req.features.getVariable('buyButton', 'label'), })})For user-4 in the Netherlands, this returns newCheckout: true and buyLabel: 'Nu kopen'. The child follows the parent's datafile updates, and res.on('close') releases it however the request ends.
TypeScript#
Declare the property on Express's Request, in a custom.d.ts listed in tsconfig.json:
import type { FeaturevisorChildInstance } from '@featurevisor/sdk'declare global { namespace Express { interface Request { features?: FeaturevisorChildInstance } }}Your authentication library declares user the same way.


