DEV Community

Adam Gardner
Adam Gardner

Posted on

Context Aware Feature Flags with flagd and OpenFeature in NodeJS

This is part two of the series. For this to make sense, I recommend you first read / complete part one.

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
}
Enter fullscreen mode Exit fullscreen mode

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 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"
            ]
          }
        ]
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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 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!");
Enter fullscreen mode Exit fullscreen mode

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

chrome screenshot

Safari screenshot

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'];
Enter fullscreen mode Exit fullscreen mode

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! 🎉

Top comments (0)