wave flags
June 1, 2018

Feature Flags in Nodejs + react

Enable continuous delivery and feature testing in minute
feature flags

Feature toggle (or feature flag or feature switch) is the concept of adding a flag to a feature so that it is enabled or not to the user in the backend or the frontend, for instance:

  • change the display in the front

  • deny some request in the back

  • etc.

In this post I will share my experience on how I implemented feature flags for my NodeJS app and how I integrated it with React. Note however that the following tutorial does not depend on your frontend framework.

It can be used for several reasons:

  • feature testing or A/B testing. When you want to test a new feature over a small part of your audience;

  • continuous delivery of new features: the dev team keeps developing new features and deploying the code in production while the flags allow for preventing the user to see ongoing developments;

  • dark launch: to test the new feature in a small audience to get user feedbacks and test performance.

As soon as you want to create this kind of feature branching in your code, you want to be very cautious of keeping it as clean and tidy as possible. Features flags are temporary (feature testing for instance) and you will have to remove them one day or another. This can happen few days or several weeks after their development. In this latter case you want to make sure you know exactly what to remove and where. Fortunately there are some tools available to help you manage these operations.

Tools overview

The featureflags.io website presents some alternative tools for setting up feature flags in a NodeJS app:

  • LaunchDarkly: paid service. Lots of features and tools but probably overkill if you get started with feature toggle.

  • flipit: 9 stars and 2 forks only on GitHub. It is essentially a read in a config file.

  • fflip: free and proposes also an Express middleware

  • unleash: not tested because it requires NodeJS >= 8 while my project used NodeJS 6.*.

In the following of this tuto I am going with fflip for feature toggle because it is well rated on GitHub, maintained and free. On the other hand flipit seemed too light, and I felt I didn’t need a library for that. Also I planned to use fflip Express middleware because my app was using Loopbackframework.

Feature toggle with fflip

There are three things you need to do to set up feature toggle:

  1. somewhere: define a list of features and toggle rules;

  2. in your backend: access the features to modify the content of a response to the client;

  3. in your frontend: access the features to display and/or enable them.

Feature definition

Fflip configuration

Feature toggle requires you to define first some criteria that you will want to use for your feature flags. For instance from the documentation:

let ExampleCriteria = [
  {
    id: 'isPaidUser',
    check: function(user, isPaid) {
      return user.isPaid == isPaid;
    }
  },
  {
    id: 'percentageOfUsers',
    check: function(user, percent) {
      return (user.id % 100 < percent * 100);
    }
  },
  {
    id: 'allowUserIDs',
    check: function(user, allowedIDs) {
      return allowedIDs.indexOf(user.id) > -1;
    }
  }
];

Then you define features using these flag functions for enabling/disabling them:

let ExampleFeatures = [
  {
    id: 'closedBeta', // required
    // if `criteria` is in an object, ALL criteria in that set must evaluate to true to enable for user
    criteria: {isPaidUser: true, percentageOfUsers: 0.50}
  },
  {
    id: 'newFeatureRollout',
    // if `criteria` is in an array, ANY ONE set of criteria must evaluate to true to enable for user
    criteria: [{isPaidUser: true}, {percentageOfUsers: 0.50}]
  },
  {
    id: 'experimentalFeature',
    name: 'An Experimental Feature', // user-defined properties are optional but can be used to add important metadata on both criteria & features
    description: 'Experimental feature still in development, useful for internal development', // user-defined
    owner: 'Fred K. Schott <fkschott@gmail.com>', // user-defined
    enabled: false, // sets the feature on or off for all users, required if `criteria` is not present
  },
];

Note that:

  • the enabled key is very convenient for continuous delivery

  • with these functional criteria it is very straightforward to do feature testing

I won’t go further into details as the website is very clear on this topic. Rather I will focus next on how I implemented the solution on my app.

Provisioning

In my app we decided to move the features flags definition into a fflip_config.js file:

module.exports = {
  criteria:[
    {
      id: 'isPaidUser',
      check: function(user, isPaid) {
        return user.isPaid == isPaid;
      }
    },
  ],
  features:[
    {
      id: 'closedBeta',
      criteria: {isPaidUser: true, percentageOfUsers: 0.50}
    },
  ]
};

We also created one file for each environment (development, preproduction, production). With Ansible, we do the following:

  • define a environment variable FFLIP_CONFIG_PATH (we used /var/www/fflip/flip_config.js) in the.profile template of the www-data user

  • copy the right file depending on the configuration (dev, preprod, prod)

Loading features

The fflip package is imported in the server.js file. Then all the features from the fflip_config.js are loaded:

const fflip = require('fflip');
const fflipConfig = require(process.env.FFLIP_CONFIG_PATH);
fflip.config(fflipConfig);

From that moment on, anywhere in the backend it is possible to access the list of a user’s features by requiring fflip. Especially you can use these two methods:

const fflip = require('fflip');
const fflipConfig = require(process.env.FFLIP_CONFIG_PATH);
fflip.config(fflipConfig);

From that moment on, anywhere in the backend it is possible to access the list of a user’s features by requiring fflip. Especially you can use these two methods:

const fflip = require('fflip')
fflip.getFeaturesForUser(user);

to get the list of all the feature enabled for that user; and:

const fflip = require('fflip')
fflip.isFeatureEnabledForUser(featureName, user)

to check if a feature is enabled for a user.

Backend

An Express middleware can be used to access the available feature from the request object. It adds two fields under the fflip key of the request object:

req.fflip = {
  setForUser(user), // Given a user, attaches the features object to the request (at req.fflip.features). Make sure you do this before calling has()!
  has(featureName), // Given a feature name, returns the feature boolean, undefined if feature doesn't exist. Throws an error if setForUser() hasn't been called
}

Then you can adapt the response to an API call depending on the response of the fflip methods.

Frontend

We decided to add the list of the enabled features to the log-in response. In the log-in route, we do:

const createAccessToken = (user, ttl) => {
  return user.createAccessToken(ttl).then((token) => {
    token.__data.user = user;
    token.__data.features = fflip.getFeaturesForUser(user);
    return token;
  });
};

In the frontend we store that list into the local storage:

window.localStorage.setItem('features', JSON.stringify(loginResponse.features));

We also create a featuresService to access these features easily:

const currentUserHasFeature = (featureName) => {
  const features = JSON.parse(window.localStorage.getItem('features'));  return (
    !!feature && featureName in features && features[featureName]
  );
};

Note that by default we do not throw an error when the feature is not found. Instead we consider that it is not enabled. This can be used to avoid sending the whole list of features to the client so that it is not possible to know which features are not allowed. When doing feature testing especially, it could let the user access disabled features.

Because the features are defined in the backend, we use an auto-generated constant file in the front to access them. This file is generated in the provisioning running this script:

const convertCamelCaseToConstantCase = (name) => name.split(/(?=[A-Z])/).join('_').toUpperCase();
const fs = require('fs');
const util = require('util');

// # Import features from all environments
let features = [];
let fflip_config;
['production', 'preproduction', 'staging', 'vagrant'].map(
  (environment) => {
    fflip_config = require('../devops/provisioning/files/' + 
environment + '/fflip/fflip_config.js');
    features = [...features, ...fflip_config.features.map((feature) 
=> feature.id)];
  }
);

features = [...new Set(features)].reduce((featuresDictionary, 
feature) =>
  Object.assign(featuresDictionary, {
    [convertCamelCaseToConstantCase(feature)]: feature,
  }), {});

const file_content =
`// This is an auto generated file, don't edit by hand.
export const FEATURES =${util.inspect(features)};\n`;

fs.writeFile('../client/app/constants/features.js', file_content , 
'utf-8');

Then anywhere in your frontend app the featuresServices allows for accessing the info about the feature flags:

import featuresService from 'services/security/features';
import { FEATURES } from 'constants/features';
featuresService.currentUserHasFeature(FEATURES.A_FEATURE_NAME)

This is it! I have been quite surprised how easy it was to implement feature toggle in my app. I hope this can help other save time.

Will you release your next feature with feature flag as well? Start using dark launch? Any comments and other tips are welcome! And of course, a huge thanks to Fred K. Schott for this package.

Thanks to Raphaël Meudec, Tristan Roussel, and Pierre-Henri Cumenge. 

The best of AI of June 2019

The Best of AI: New Articles Published This Month (June 2019)

10 data articles handpicked by the Sicara team, just for you.

TensorFlow, AI, Docker, GPU

Set up TensorFlow with Docker + GPU in Minutes

Why Docker is the best platform to use Tensorflow with a GPU.

Edge detection, tutorial, knowledge

Edge Detection in Opencv 4.0, A 15 Minutes Tutorial

This tutorial will teach you, with examples, two OpenCV techniques in python to deal with edge detection.