Context Aware Feature Flags in with flagd and OpenFeature in NodeJS
Published: 2024-03-29 04:11:45

This is part two of the series. For this to make sense, I recommend you first read / complete [part one](https://dev.to/agardnerit/add-feature-flags-with-only-a-json-flat-file-in-nodejs-41ef).

## Actually Useful Feature Flags

Feature flags as demonstrated in part one of this tutorial are not actually very useful. Yes, they abstract the values to an external storage system, and that is nice.

But the real value of feature flags is when you can combine runtime information and / or environmental and / or client information to adjust the value of the flag in realtime.

## An Example

In the previous example, the possible flag values were:

- `#FFFFFF` (white) if `flags.json` did not exist
- `#73A53E` (green) because that was the `defaultVariant`

However, what if we wanted a ruleset like this:

- `#FFFFFF` (white) if `flags.json` does not exist
- `#4AB9D9` (blue) if `flags.json` does exist and the user is using the Google Chrome browser.
- `#73A53E` (green) if `flags.json` does exist and the user is using the Safari browser
- `#FF0000` (red) if `flags.json` does exist and the user is using any other browser (ie. not Chrome or Safari)

We could, of course, code something like this:

```
// Assume req is the request object
const userAgent = req.headers['user-agent'];

if (req.headers['user-agent'].includes("Chrome")) {
  const backgroundColour = "#4AB9D9"; // blue
}
else if (req.headers['user-agent'].includes("Safari")) {
  const backgroundColour = "#73A53E"; // green
}
else {
  const backgroundColour = "#FF0000"; // red
}
```

But that is:

1. Missing the advantages of using a feature flag system
2. Hardcoded
3. Messy

## Let the Flag System Perform That Logic

Instead, let [flagd](https://flagd.dev) perform that logic for you.


## New Flag Definition

First, overwrite your existing `flags.json` file with this content:

```
{
  "$schema": "https://flagd.dev/schema/v0/flags.json",
  "flags": {
    "background-colour": {
      "state": "ENABLED",
      "variants": {
        "white": "#D6D4D2",
        "green": "#73A53E",
        "orange": "#FF7C00",
        "lime": "#D3D309",
        "blue": "#4AB9D9",
        "red": "#FF0000"
      },
      "defaultVariant": "red",
      "targeting": {
        "if": [
          {
            "in": [
              "Chrome",
              {
                "var": "uA"
              }
            ]
          },
          "blue",
          {
            "if": [
              {
                "in": [
                  "Safari",
                  {
                    "var": "uA"
                  }
                ]
              },
              "green"
            ]
          }
        ]
      }
    }
  }
}
```

You can read that as:

1. One flag called `background-colour` is available and enabled.
2. It has `6` possible values: white, green, orange, lime, blue and red.
3. By default, the flag variant will be `red` and thus return a value of `#FF0000`.
4. When the flag is evaluated, if some additional contextual data with the key of `uA` is present and the value of `uA` contains the word `Chrome` then return `blue`, not `red`.
5. When the flag is evaluated, if `uA` is present and `Chrome` is not present but `Safari` is, return `green`, not `red`.
6. In all other cases (`uA` is not present or `uA` is present but contains neither `Chrome` or `Safari`), return the default: `red`.

Here you can see that, with relatively simple JSON Logic, flagd can build complex rules. flagd can perform lots of other logic and the [flagd playground](https://flagd.dev/playground/) is a great place to experiment in realtime.

## New app.js
Now overwrite your `app.js` file to this:

```
var fs = require('fs');
var http = require('http');
var port = 9123;
var html = fs.readFileSync('index.html').toString();
var {OpenFeature} = require("@openfeature/server-sdk")
var {FlagdProvider} = require("@openfeature/flagd-provider")

var getBackgroundColour = async function(req) {

  const userAgent = req.headers['user-agent'];

  const backgroundColour = await client.getStringValue("background-colour", "#FFFFFF", { 'uA': userAgent });
  
  return backgroundColour;
}

// OpenFeature Initialisation Code
// Register your feature flag provider
OpenFeature.setProvider(new FlagdProvider());
// create a new client
const client = OpenFeature.getClient();

// =======================================================
// This is our main HttpServer Handler
// =======================================================
var server = http.createServer(async function (req, res) {

  res.writeHead(200, 'OK', {'Content-Type': 'text/html'});

  // replace background colour
  var finalHtml = html.replace(/BACKGROUND-COLOUR/g, await getBackgroundColour(req));
  res.write(finalHtml);
  res.end();
});

// Listen on port 9123, IP defaults to 127.0.0.1
server.listen(port);

// Put a friendly message on the terminal
console.log('Server running at http://127.0.0.1:' + port + '/');
console.log("Info", "Service is up and running - feed me with data!");
```

Open `http://127.0.0.1:9123` in Google Chrome and then again in Safari.

![chrome screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yhrgz421phnekzxu3gq2.png)


![Safari screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/su7reeea0d792epg4puo.png)


## Explanation

Lots of the content is the same as part one, but let's run through things.

First look at `app.js`. Again we initialised OpenFeature and the flagd provider.

This time though, when we request the background colour, we pass the `req` (request) object into `getBackgroundColour`.

The `User-Agent` string is extracted from the `req` object:

```
const userAgent = req.headers['user-agent'];
```

The "user agent" is sent to flagd with a key of `uA` (you can pass any values you wish to flagd - we just chose to use `uA` in an attempt to be clear that we choose the key).

If flagd is not available (because we're using `in-process` this would mean the `flags.json` file is not available), then the value `#FFFFFF` would be returned.

flagd performs the logic (which you saw is specified in the `flags.json` file) and returns the correct value back to the OpenFeature client.

## Summary

So, now you know how to use contextual runtime information with OpenFeature and flagd.

Whether you're using flagd or any other OpenFeature provider, the client code is identical.

You are limited only by your imagination: User agents, browser versions, timestamps, geolocations, semver release versions and anything else you wish to use as a contextual attribute.

As long as your chosen backend can deal with the data you send, you can achieve the same as the demo above.

Happy feature flagging! 🎉