Skip to content

Commit d3c0bd7

Browse files
authored
Merge pull request #301495 from zhiyuanliang-ms/zhiyuanliang/js-targeting
Azure App Configuration - Add targeting filter doc for js
2 parents 88391f2 + 49a8bc6 commit d3c0bd7

13 files changed

+370
-25
lines changed

articles/azure-app-configuration/TOC.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,8 @@
210210
href: howto-targetingfilter.md
211211
- name: ASP.NET Core
212212
href: howto-targetingfilter-aspnet-core.md
213+
- name: JavaScript
214+
href: howto-targetingfilter-javascript.md
213215
- name: Use variant feature flags
214216
items:
215217
- name: Overview

articles/azure-app-configuration/feature-management-javascript-reference.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ const featureManager = new FeatureManager(featureProvider);
132132

133133
#### Use Azure App Configuration to dynamically control the state of the feature flag
134134

135-
Azure App Configuration is not only a solution to externalize storage and centralized management of your feature flags, but also it allows to dynamically turn on/off the feature flags.
135+
Azure App Configuration is not only a solution to externalize storage and centralized management of your feature flags, but also it allows you to dynamically turn on/off the feature flags.
136136

137137
To enable the dynamic refresh for feature flags, you need to configure the `refresh` property of `featureFlagOptions` when loading feature flags from Azure App Configuration.
138138

@@ -526,7 +526,7 @@ app.use((req, res, next) => {
526526
const targetingContextAccessor = {
527527
getTargetingContext: () => {
528528
// Get the current request from AsyncLocalStorage
529-
const request = requestContext.getStore();
529+
const request = requestAccesor.getStore();
530530
if (!request) {
531531
return undefined; // Return undefined if there's no current request
532532
}

articles/azure-app-configuration/feature-management-overview.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ Feature | .NET | Spring | Python | JavaScript
4141
------- | ---- | ------ | ------ | ----------
4242
Targeting Filter | [GA](./feature-management-dotnet-reference.md#targeting) | GA | [GA](./feature-management-python-reference.md#targeting) | [GA](./feature-management-javascript-reference.md#targeting)
4343
Targeting Exclusion | [GA](./feature-management-dotnet-reference.md#targeting-exclusion) | GA | [GA](./feature-management-python-reference.md#targeting-exclusion) | [GA](./feature-management-javascript-reference.md#targeting-exclusion)
44-
Ambient Targeting | [GA](./feature-management-dotnet-reference.md#targeting-in-a-web-application) | WIP | WIP | [Preview](./feature-management-javascript-reference.md#targeting-in-a-web-application)
44+
Ambient Targeting | [GA](./feature-management-dotnet-reference.md#targeting-in-a-web-application) | WIP | WIP | [GA](./feature-management-javascript-reference.md#targeting-in-a-web-application)
4545
Time Window Filter | [GA](./feature-management-dotnet-reference.md#microsofttimewindow) | GA | [GA](./feature-management-python-reference.md#microsofttimewindow) | [GA](./feature-management-javascript-reference.md#microsofttimewindow)
4646
Recurring Time Window | [GA](./feature-management-dotnet-reference.md#microsofttimewindow) | GA | WIP | WIP
4747
Custom Feature Filter | [GA](./feature-management-dotnet-reference.md#implementing-a-feature-filter) | GA | [GA](./feature-management-python-reference.md#implementing-a-feature-filter) | [GA](./feature-management-javascript-reference.md#implementing-a-feature-filter)

articles/azure-app-configuration/howto-feature-filters-javascript.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@ You've added a custom feature filter named **Random** with a **Percentage** para
4848
1. Register the `RandomFilter` when creating the `FeatureManager`.
4949

5050
``` javascript
51-
const fm = new FeatureManager(ffProvider, {customFilters: [new RandomFilter()]});
51+
const fm = new FeatureManager(
52+
new ConfigurationMapFeatureFlagProvider(appConfig),
53+
{
54+
customFilters: [new RandomFilter()]
55+
});
5256
```
5357

5458
## Feature filter in action
Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
---
2+
title: Roll out features to targeted audiences in a Node.js app
3+
titleSuffix: Azure App Configuration
4+
description: Learn how to enable staged rollout of features for targeted audiences in a Node.js application.
5+
ms.service: azure-app-configuration
6+
ms.devlang: javascript
7+
author: zhiyuanliang-ms
8+
ms.author: zhiyuanliang
9+
ms.topic: how-to
10+
ms.date: 06/17/2025
11+
---
12+
13+
# Roll out features to targeted audiences in a Node.js application
14+
15+
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](./howto-targetingfilter.md).
16+
17+
## Prerequisites
18+
19+
- An Azure account with an active subscription. [Create one for free](https://azure.microsoft.com/free/).
20+
- An App Configuration store, as shown in the [tutorial for creating a store](./quickstart-azure-app-configuration-create.md#create-an-app-configuration-store).
21+
- A _Beta_ feature flag with targeting filter. [Create the feature flag](./howto-targetingfilter.md).
22+
- [LTS versions of Node.js](https://github.com/nodejs/release#release-schedule).
23+
24+
## Create a web application with a feature flag
25+
26+
In this section, you create a web application that uses the [_Beta_ feature flag](./howto-targetingfilter.md) to control the access to the beta version of a web page.
27+
28+
### Set up a Node.js Express project
29+
30+
1. Create a folder called `targeting-filter-tutorial` and initialize the project.
31+
32+
```bash
33+
mkdir targeting-filter-tutorial
34+
cd targeting-filter-tutorial
35+
npm init -y
36+
```
37+
38+
1. Install the following packages.
39+
40+
```bash
41+
npm install @azure/app-configuration-provider
42+
npm install @microsoft/feature-management
43+
npm install express
44+
```
45+
46+
1. Create a new file named *app.js* and add the following code.
47+
48+
```js
49+
const express = require("express");
50+
const server = express();
51+
const port = "8080";
52+
53+
server.listen(port, () => {
54+
console.log(`Server is running at http://localhost:${port}`);
55+
});
56+
```
57+
58+
### Connect to Azure App Configuration
59+
60+
1. Update the *app.js* and add the following code.
61+
62+
```js
63+
// Existing code ...
64+
const appConfigEndpoint = process.env.AZURE_APPCONFIG_ENDPOINT;
65+
const { DefaultAzureCredential } = require("@azure/identity");
66+
const { load } = require("@azure/app-configuration-provider");
67+
const { FeatureManager, ConfigurationMapFeatureFlagProvider } = require("@microsoft/feature-management");
68+
69+
let appConfig;
70+
let featureManager;
71+
72+
async function initializeConfig() {
73+
// Load feature flags from App Configuration.
74+
appConfig = await load(appConfigEndpoint, new DefaultAzureCredential(), {
75+
featureFlagOptions: {
76+
enabled: true,
77+
refresh: {
78+
enabled: true
79+
}
80+
}
81+
});
82+
83+
// Create feature manager with feature flag provider that accesses feature flags from App Configuration.
84+
featureManager = new FeatureManager(
85+
new ConfigurationMapFeatureFlagProvider(appConfig));
86+
}
87+
88+
// Use a middleware to refresh the configuration before each request.
89+
server.use((req, res, next) => {
90+
appConfig.refresh();
91+
next();
92+
});
93+
// Existing code ...
94+
```
95+
96+
You connect to Azure App Configuration to load feature flags, enable automatic refresh, and create a `FeatureManager` object for accessing feature flags later. A middleware is added to refresh configuration before each request.
97+
98+
1. Update the code to ensure the Express server starts only after the configuration has been successfully initialized.
99+
100+
```js
101+
// Existing code ...
102+
initializeConfig()
103+
.then(() => {
104+
// Start the express server.
105+
server.listen(port, () => {
106+
console.log(`Server is running at http://localhost:${port}`);
107+
});
108+
})
109+
```
110+
111+
### Use the feature flag
112+
113+
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.
114+
115+
```js
116+
// Existing code ...
117+
server.get("/", async (req, res) => {
118+
const isBetaEnabled = await featureManager.isEnabled("Beta");
119+
const [title, message] = isBetaEnabled
120+
? ["Beta Page", "This is a beta page."]
121+
: ["Home Page", "Welcome."];
122+
123+
res.send(
124+
`<!DOCTYPE html>
125+
<html>
126+
<head><title>${title}</title></head>
127+
<body style="display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0;">
128+
<h1 style="text-align: center; font-size: 5rem;">${message}</h1>
129+
</body>
130+
</html>`
131+
);
132+
});
133+
134+
initializeConfig()
135+
// Existing code ...
136+
```
137+
138+
## Enable targeting for the web application
139+
140+
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.
141+
142+
```js
143+
const isBetaEnabled = await featureManager.isEnabled("Beta", { userId: "UserA", groups: ["Group1"] });
144+
```
145+
146+
In a web application, the targeting context can also be provided as an ambient context by implementing the [ITargetingContextAccessor](./feature-management-javascript-reference.md#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.
147+
148+
You use ambient targeting context in this tutorial.
149+
150+
1. Add the following code after the Express server declaration. It uses `AsyncLocalStorage` to 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](./feature-management-javascript-reference.md#using-asynclocalstorage-for-request-context).
151+
152+
```js
153+
const express = require("express");
154+
const server = express();
155+
const port = 8080;
156+
157+
const { AsyncLocalStorage } = require("async_hooks");
158+
const requestAccessor = new AsyncLocalStorage();
159+
// Use a middleware to store request context.
160+
server.use((req, res, next) => {
161+
// Store the request in AsyncLocalStorage for this request chain.
162+
requestAccessor.run(req, () => {
163+
next();
164+
});
165+
});
166+
167+
// Create a targeting context accessor that retrieves user data from the current request.
168+
const targetingContextAccessor = {
169+
getTargetingContext: () => {
170+
// Get the current request from AsyncLocalStorage.
171+
const request = requestAccessor.getStore();
172+
if (!request) {
173+
return undefined;
174+
}
175+
const { userId, groups } = request.query;
176+
return {
177+
userId: userId,
178+
groups: groups ? groups.split(",") : []
179+
};
180+
}
181+
};
182+
// Existing code ...
183+
```
184+
185+
1. When constructing the `FeatureManager`, pass the targeting context accessor to the `FeatureManagerOptions`.
186+
187+
```js
188+
featureManager = new FeatureManager(
189+
new ConfigurationMapFeatureFlagProvider(appConfig),
190+
{
191+
targetingContextAccessor: targetingContextAccessor
192+
});
193+
```
194+
195+
After completing the previous steps, your _app.js_ file should now contain the following complete implementation.
196+
197+
```js
198+
const express = require("express");
199+
const server = express();
200+
const port = 8080;
201+
202+
const { AsyncLocalStorage } = require("async_hooks");
203+
const requestAccessor = new AsyncLocalStorage();
204+
// Use a middleware to store request context
205+
server.use((req, res, next) => {
206+
// Store the request in AsyncLocalStorage for this request chain
207+
requestAccessor.run(req, () => {
208+
next();
209+
});
210+
});
211+
212+
// Create a targeting context accessor that retrieves user data from the current request
213+
const targetingContextAccessor = {
214+
getTargetingContext: () => {
215+
// Get the current request from AsyncLocalStorage
216+
const request = requestAccessor.getStore();
217+
if (!request) {
218+
return undefined;
219+
}
220+
const { userId, groups } = request.query;
221+
return {
222+
userId: userId,
223+
groups: groups ? groups.split(",") : []
224+
};
225+
}
226+
};
227+
228+
const appConfigEndpoint = process.env.AZURE_APPCONFIG_ENDPOINT;
229+
const { DefaultAzureCredential } = require("@azure/identity");
230+
const { load } = require("@azure/app-configuration-provider");
231+
const { FeatureManager, ConfigurationMapFeatureFlagProvider } = require("@microsoft/feature-management");
232+
233+
let appConfig;
234+
let featureManager;
235+
236+
async function initializeConfig() {
237+
// Load feature flags from App Configuration.
238+
appConfig = await load(appConfigEndpoint, new DefaultAzureCredential(), {
239+
featureFlagOptions: {
240+
enabled: true,
241+
refresh: {
242+
enabled: true
243+
}
244+
}
245+
});
246+
247+
// Create feature manager with feature flag provider that accesses feature flags from App Configuration and targeting context accessor.
248+
featureManager = new FeatureManager(
249+
new ConfigurationMapFeatureFlagProvider(appConfig),
250+
{
251+
targetingContextAccessor: targetingContextAccessor
252+
});
253+
}
254+
255+
// Use a middleware to refresh the configuration before each request
256+
server.use((req, res, next) => {
257+
appConfig.refresh();
258+
next();
259+
});
260+
261+
server.get("/", async (req, res) => {
262+
const isBetaEnabled = await featureManager.isEnabled("Beta");
263+
const [title, message] = isBetaEnabled
264+
? ["Beta Page", "This is a beta page."]
265+
: ["Home Page", "Welcome."];
266+
267+
res.send(
268+
`<!DOCTYPE html>
269+
<html>
270+
<head><title>${title}</title></head>
271+
<body style="display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0;">
272+
<h1 style="text-align: center; font-size: 5rem;">${message}</h1>
273+
</body>
274+
</html>`
275+
);
276+
});
277+
278+
// Initialize the configuration and start the server
279+
initializeConfig()
280+
.then(() => {
281+
// Start the express server.
282+
server.listen(port, () => {
283+
console.log(`Server is running at http://localhost:${port}`);
284+
});
285+
})
286+
```
287+
288+
289+
## Targeting filter in action
290+
291+
1. 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.
292+
293+
If you use the Windows command prompt, run the following command and restart the command prompt to allow the change to take effect:
294+
295+
```cmd
296+
setx AZURE_APPCONFIG_ENDPOINT "<endpoint-of-your-app-configuration-store>"
297+
```
298+
299+
If you use PowerShell, run the following command:
300+
301+
```powershell
302+
$Env:AZURE_APPCONFIG_ENDPOINT = "<endpoint-of-your-app-configuration-store>"
303+
```
304+
305+
If you use macOS or Linux, run the following command:
306+
307+
```bash
308+
export AZURE_APPCONFIG_ENDPOINT='<endpoint-of-your-app-configuration-store>'
309+
```
310+
311+
1. Run the application.
312+
313+
```bash
314+
node app.js
315+
```
316+
317+
1. Open your browser and navigate to `localhost:8080`. You should see the default view of the app.
318+
319+
:::image type="content" source="media/howto-targetingfilter-javascript/beta-disabled.png" alt-text="Screenshot of the app, showing the default greeting message.":::
320+
321+
1. 1. Add `userId` as a query parameter in the URL to specify the user ID. Visit `localhost:8080/[email protected]`. You see the beta page, because `[email protected]` is specified as a targeted user.
322+
323+
:::image type="content" source="media/howto-targetingfilter-javascript/beta-enabled.png" alt-text="Screenshot of the app, showing the beta page.":::
324+
325+
1. Visit `localhost:8080/[email protected]`. You cannot see the beta page, because `[email protected]` is specified as an excluded user.
326+
327+
:::image type="content" source="media/howto-targetingfilter-javascript/beta-not-targeted.png" alt-text="Screenshot of the app, showing the default content.":::
328+
329+
## Next steps
330+
331+
To learn more about the feature filters, continue to the following documents.
332+
333+
> [!div class="nextstepaction"]
334+
> [Enable conditional features with feature filters](./howto-feature-filters.md)
335+
336+
> [!div class="nextstepaction"]
337+
> [Enable features on a schedule](./howto-timewindow-filter-aspnet-core.md)
338+
339+
For the full feature rundown of the JavaScript feature management library, continue to the following document.
340+
341+
> [!div class="nextstepaction"]
342+
> [.NET Feature Management](./feature-management-javascript-reference.md)

0 commit comments

Comments
 (0)