Add Feature Flags with only a JSON flat file in NodeJS
Tags: webdev, node, featureflag, openfeature
Published: 2024-03-24 19:24:01

Did you know that it is possible to add feature flags to your application simply by adding a JSON file containing the flags? Read on and I'll show you how to do it in NodeJS.

Zero restarts or downtime, just a fully dynamic application, all by using a JSON file.

## Start without Feature Flags

Start the tutorial at the beginning, with a simple node app that has no feature flagging capabilities.

The app is a simple HTTP server which renders a static HTML page but, while rendering, replaces the placeholder text `BACKGROUND-COLOUR` with the hex value returned from `getBackgroundColour()`.

Create a `package.json` file like this:

```
{
    "name": "MyApp",
    "version": "0.1.0",
    "private": true,
    "dependencies": {
    },
    "scripts": {
      "start": "node app.js"
    }
}
```

Next, create `app.js` like this:

```
var fs = require('fs');
var http = require('http');
var port = 9123;
var html = fs.readFileSync('index.html').toString();

var getBackgroundColour = async function() {
    return "#AAEEDD";
}

// ===========================================================
// 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 placeholder with real hex value
  var finalHtml = html.replace(/BACKGROUND-COLOUR/g, await getBackgroundColour());
        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!");
```

Finally, create a file called `index.html` with this content:

```
<html>
  <head>
    <title>Demo</title>
    <style>
      body {background-color: BACKGROUND-COLOUR };
    </style>
  </head>
  <body>
    <div style="text-align:center">
    <h1>A Demo Page...</h1>
    <p>This is a simple demo page. The background colour is: BACKGROUND-COLOUR</p>
    </div>
    </div>
  </body>
</html>
```

### Run Simple App

Run the app with `npm run start` and you should see this:

```
> MyApp@0.1.0 start
> node app.js

Server running at http://127.0.0.1:9123/
Info Service is up and running - feed me with data!
```

![Basic Application](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdmyfszotl5u1wwz5nc3.png)

Try changing the hex value from `"#AAEEDD"` to any other valid hex value. It will work, but you will need to restart the application to refresh the colour.

In today's world, restarts and / or redeployments often equal downtime. Downtime is the enemy!

Let's fix the app so we can change colour without any restarts or downtime.

## Add Feature Flags

It is time to add feature flags to your application. Create a file called `flags.json` and add 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"
        },
        "defaultVariant": "green"
     }
    }
}
```

Replace `app.js` with this content (notice the new OpenFeature code and modified `getBackgroundColour` implementation):

```
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 getBackgroundColor = async function() {

  const backgroundColour = await client.getStringValue("background-colour", "#FFFFFF", { });
  
  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 placeholder with real hex value
  var finalHtml = html.replace(/BACKGROUND-COLOUR/g, await getBackgroundColor());
  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!");
```

Instead of returning a static value, the code above uses [OpenFeature](https://openfeature.dev) and the [Flagd provider](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/providers/flagd) (feature flag backend) to retrieve flag values (we will see how flagd reads the JSON file soon). If the feature flag system is offline or unavailable for whatever reason, a safe fallback value of `#FFFFFF` is returned.

Now modify `package.json` to look like the code below:

```
{
    "name": "MyApp",
    "version": "0.2.0",
    "private": true,
    "dependencies": {
      "@openfeature/flagd-provider": "^0.12.0",
      "@openfeature/server-sdk": "^1.13.2"
    },
    "scripts": {
      "start": "node app.js",
      "dev_linux": "export FLAGD_RESOLVER=in-process && export FLAGD_OFFLINE_FLAG_SOURCE_PATH=flags.json && node app.js",
      "dev_windows": "set FLAGD_RESOLVER=in-process&&FLAGD_OFFLINE_FLAG_SOURCE_PATH=flags.json&&node app.js"
    }
}
```

The only difference is that we've added `dependencies` for `OpenFeature` and `flagd`. We also have 2 additional scripts, which set some important environment variables: `FLAGD_RESOLVER` and `FLAGD_OFFLINE_FLAG_SOURCE_PATH`. This is how flagd knows to read from a local file. The `in-process` resolver is the "magic" that means you have a feature flag system "inside" your code logic.

## Run Feature Flag Enabled App

Save the files and install the dependencies by running `npm install` once.

Fire up the app by running the appropriate command (depending on your OS): `npm run dev_linux` (for linux and macos) or `npm run dev_windows` (for windows).

You should see:

```
% npm run dev_linux

> MyApp@0.2.0 dev_linux
> export FLAGD_RESOLVER=in-process && export FLAGD_OFFLINE_FLAG_SOURCE_PATH=flags.json && node app.js

Server running at http://127.0.0.1:9123/
Info Service is up and running - feed me with data!
```

![Feature flag enabled application](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nk8djp1v0jgo1y4mjido.png)

The application is green because the `defaultVariant` in `flags.json` is `green` which maps to the hex value `"#73A53E"`.

## Change App Colour at Runtime

Change the `defaultVariant` in `flags.json` to a different `variant` key such as `orange`. Save `flags.json` and refresh the browser window (do not restart the node app).

You should see this:

![App is Orange](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uiwr57b6cwmkccw2fp5b.png)

## Add Another Colour Choice at Runtime

Leave the node app running and add a new `Key: Value` pair to the `variants` block in `flags.json`. Set the `defaultVariant` to your new `Key` and refresh the browser page.

For example, I'm adding a new `red` variant:

```
{
    "$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"
     }
    }
}
```

![Colour Variant Added at Runtime](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nu41swty4obyc5x4ovdn.png)

## Migrate to another Feature Flag Vendor

One of the powers of OpenFeature is that it allows you to easily migrate to different vendors.

For example, migrating to DevCycle (or any other provider) is almost just a case of replacing references to `flagd` with `devcycle`:

```
OpenFeature.setProvider(new FlagdProvider());
```

to

```
OpenFeature.setProvider(devcycleClient.getOpenFeatureProvider());
```

All compatible feature flag vendors and integration instructions can be found on the [OpenFeature ecosystem page](https://openfeature.dev/ecosystem?instant_search%5BrefinementList%5D%5Bcategory%5D%5B0%5D=Server&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=JavaScript).

See [part two to add runtime contextual awareness](https://dev.to/agardnerit/context-aware-feature-flags-in-with-flagd-and-openfeature-in-nodejs-143j) to your feature flagging.


## 🎉 Happy Feature Flagging! 🎉