| title | Roll out features to targeted audiences in a Node.js app |
|---|---|
| titleSuffix | Azure App Configuration |
| description | Learn how to enable staged rollout of features for targeted audiences in a Node.js application. |
| ms.service | azure-app-configuration |
| ms.devlang | javascript |
| author | zhiyuanliang-ms |
| ms.author | zhiyuanliang |
| ms.topic | how-to |
| ms.date | 06/17/2025 |
In this guide, you'll use the targeting filter to roll out a feature to targeted audiences for your Node.js application. For more information about the targeting filter, see Roll out features to targeted audiences.
- An Azure account with an active subscription. Create one for free.
- An App Configuration store, as shown in the tutorial for creating a store.
- A Beta feature flag with targeting filter. Create the feature flag.
- LTS versions of Node.js.
In this section, you create a web application that uses the Beta feature flag to control the access to the beta version of a web page.
-
Create a folder called
targeting-filter-tutorialand initialize the project.mkdir targeting-filter-tutorial cd targeting-filter-tutorial npm init -y -
Install the following packages.
npm install @azure/app-configuration-provider npm install @microsoft/feature-management npm install express
-
Create a new file named app.js and add the following code.
const express = require("express"); const server = express(); const port = "8080"; server.listen(port, () => { console.log(`Server is running at http://localhost:${port}`); });
-
Update the app.js and add the following code.
// Existing code ... const appConfigEndpoint = process.env.AZURE_APPCONFIG_ENDPOINT; const { DefaultAzureCredential } = require("@azure/identity"); const { load } = require("@azure/app-configuration-provider"); const { FeatureManager, ConfigurationMapFeatureFlagProvider } = require("@microsoft/feature-management"); let appConfig; let featureManager; async function initializeConfig() { // Load feature flags from App Configuration. appConfig = await load(appConfigEndpoint, new DefaultAzureCredential(), { featureFlagOptions: { enabled: true, refresh: { enabled: true } } }); // Create feature manager with feature flag provider that accesses feature flags from App Configuration. featureManager = new FeatureManager( new ConfigurationMapFeatureFlagProvider(appConfig)); } // Use a middleware to refresh the configuration before each request. server.use((req, res, next) => { appConfig.refresh(); next(); }); // Existing code ...
You connect to Azure App Configuration to load feature flags, enable automatic refresh, and create a
FeatureManagerobject for accessing feature flags later. A middleware is added to refresh configuration before each request. -
Update the code to ensure the Express server starts only after the configuration has been successfully initialized.
// Existing code ... initializeConfig() .then(() => { // Start the express server. server.listen(port, () => { console.log(`Server is running at http://localhost:${port}`); }); })
Add the following code to the app.js file to configure the route handler for the Express server. The server will serve different contents based on whether the Beta feature flag is enabled.
// Existing code ...
server.get("/", async (req, res) => {
const isBetaEnabled = await featureManager.isEnabled("Beta");
const [title, message] = isBetaEnabled
? ["Beta Page", "This is a beta page."]
: ["Home Page", "Welcome."];
res.send(
`<!DOCTYPE html>
<html>
<head><title>${title}</title></head>
<body style="display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0;">
<h1 style="text-align: center; font-size: 5rem;">${message}</h1>
</body>
</html>`
);
});
initializeConfig()
// Existing code ...A targeting context is required when evaluating features with targeting enabled. To explicitly provide this context for feature evaluation, you can pass it as a parameter to the featureManager.isEnabled method.
const isBetaEnabled = await featureManager.isEnabled("Beta", { userId: "UserA", groups: ["Group1"] });In a web application, the targeting context can also be provided as an ambient context by implementing the ITargetingContextAccessor interface. An ambient targeting context means that targeting information is automatically retrieved from the environment, such as the current HTTP request, without needing to explicitly pass it to each featureManager.isEnabled() call.
You use ambient targeting context in this tutorial.
-
Add the following code after the Express server declaration. It uses
AsyncLocalStorageto store the current request, allowing the feature manager to automatically retrieve the targeting context via a targeting context accessor callback. For more details, see Using AsyncLocalStorage for request context.const express = require("express"); const server = express(); const port = 8080; const { AsyncLocalStorage } = require("async_hooks"); const requestAccessor = new AsyncLocalStorage(); // Use a middleware to store request context. server.use((req, res, next) => { // Store the request in AsyncLocalStorage for this request chain. requestAccessor.run(req, () => { next(); }); }); // Create a targeting context accessor that retrieves user data from the current request. const targetingContextAccessor = { getTargetingContext: () => { // Get the current request from AsyncLocalStorage. const request = requestAccessor.getStore(); if (!request) { return undefined; } const { userId, groups } = request.query; return { userId: userId, groups: groups ? groups.split(",") : [] }; } }; // Existing code ...
-
When constructing the
FeatureManager, pass the targeting context accessor to theFeatureManagerOptions.featureManager = new FeatureManager( new ConfigurationMapFeatureFlagProvider(appConfig), { targetingContextAccessor: targetingContextAccessor });
After completing the previous steps, your app.js file should now contain the following complete implementation.
const express = require("express");
const server = express();
const port = 8080;
const { AsyncLocalStorage } = require("async_hooks");
const requestAccessor = new AsyncLocalStorage();
// Use a middleware to store request context
server.use((req, res, next) => {
// Store the request in AsyncLocalStorage for this request chain
requestAccessor.run(req, () => {
next();
});
});
// Create a targeting context accessor that retrieves user data from the current request
const targetingContextAccessor = {
getTargetingContext: () => {
// Get the current request from AsyncLocalStorage
const request = requestAccessor.getStore();
if (!request) {
return undefined;
}
const { userId, groups } = request.query;
return {
userId: userId,
groups: groups ? groups.split(",") : []
};
}
};
const appConfigEndpoint = process.env.AZURE_APPCONFIG_ENDPOINT;
const { DefaultAzureCredential } = require("@azure/identity");
const { load } = require("@azure/app-configuration-provider");
const { FeatureManager, ConfigurationMapFeatureFlagProvider } = require("@microsoft/feature-management");
let appConfig;
let featureManager;
async function initializeConfig() {
// Load feature flags from App Configuration.
appConfig = await load(appConfigEndpoint, new DefaultAzureCredential(), {
featureFlagOptions: {
enabled: true,
refresh: {
enabled: true
}
}
});
// Create feature manager with feature flag provider that accesses feature flags from App Configuration and targeting context accessor.
featureManager = new FeatureManager(
new ConfigurationMapFeatureFlagProvider(appConfig),
{
targetingContextAccessor: targetingContextAccessor
});
}
// Use a middleware to refresh the configuration before each request
server.use((req, res, next) => {
appConfig.refresh();
next();
});
server.get("/", async (req, res) => {
const isBetaEnabled = await featureManager.isEnabled("Beta");
const [title, message] = isBetaEnabled
? ["Beta Page", "This is a beta page."]
: ["Home Page", "Welcome."];
res.send(
`<!DOCTYPE html>
<html>
<head><title>${title}</title></head>
<body style="display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0;">
<h1 style="text-align: center; font-size: 5rem;">${message}</h1>
</body>
</html>`
);
});
// Initialize the configuration and start the server
initializeConfig()
.then(() => {
// Start the express server.
server.listen(port, () => {
console.log(`Server is running at http://localhost:${port}`);
});
})-
Set the environment variable named AZURE_APPCONFIG_ENDPOINT to the endpoint of your App Configuration store found under the Overview of your store in the Azure portal.
If you use the Windows command prompt, run the following command and restart the command prompt to allow the change to take effect:
setx AZURE_APPCONFIG_ENDPOINT "<endpoint-of-your-app-configuration-store>"
If you use PowerShell, run the following command:
$Env:AZURE_APPCONFIG_ENDPOINT = "<endpoint-of-your-app-configuration-store>"
If you use macOS or Linux, run the following command:
export AZURE_APPCONFIG_ENDPOINT='<endpoint-of-your-app-configuration-store>'
-
Run the application.
node app.js
-
Open your browser and navigate to
localhost:8080. You should see the default view of the app.:::image type="content" source="media/howto-targetingfilter-javascript/beta-disabled.png" alt-text="Screenshot of the app, showing the default greeting message.":::
-
- Add
userIdas a query parameter in the URL to specify the user ID. Visitlocalhost:8080/[email protected]. You see the beta page, because[email protected]is specified as a targeted user.
:::image type="content" source="media/howto-targetingfilter-javascript/beta-enabled.png" alt-text="Screenshot of the app, showing the beta page.":::
- Add
-
Visit
localhost:8080/[email protected]. You cannot see the beta page, because[email protected]is specified as an excluded user.:::image type="content" source="media/howto-targetingfilter-javascript/beta-not-targeted.png" alt-text="Screenshot of the app, showing the default content.":::
To learn more about the feature filters, continue to the following documents.
[!div class="nextstepaction"] Enable conditional features with feature filters
[!div class="nextstepaction"] Enable features on a schedule
For the full feature rundown of the JavaScript feature management library, continue to the following document.
[!div class="nextstepaction"] .NET Feature Management