Most software concepts have multiple levels, like an onion (or an ogre). Feature flags are no different. You slap a conditional in your code to decide if it gets executed or not. Done and done. However, you can go deeper than that by using feature flag management. This represents the deeper level where feature flags are a strategy for making your software better rather than a forgotten conditional that might've gotten there with no plan. This post will specifically tackle feature flag management in JavaScript.
JavaScript in today's world means more than just a snippet of code in the browser. Node.js has put JavaScript on the map as a server-side technology, and it's growing in popularity. Therefore, this post will show you how you can manage feature flags on the server and client side of the JavaScript equation. You'll also find out how they can work in harmony to deliver features safely.
We'll open up the post with some fundamentals, giving you a high-level view of what feature flags are and why you would want to use them. Then, you can roll up your sleeves because after the brief theoretical introduction we dive straight into the code with our hands-on guide. Let's get started.
Table of Contents
The What and Why of JavaScript Feature Flags
Basic Feature Flags For JavaScript
Why Do We Need To Manage Flags In JavaScript?
Centrally Managing Feature Flags
The Power of Feature Flags
The What and Why of JavaScript Feature Flags
So, let's start by getting the "what" out of the way. Feature flags are mechanisms you can use to disable/enable portions of the code in an application without having to change the code itself and redeploy the application. This ability is powerful, and you gain a lot of capabilities by adopting it:
you're able to "hide" partially completed features, so engineers are free to integrate their work frequently
also, you can allow only a select group of users to access a feature, allowing for testing in a QA environment or even production
you can serve different versions of a feature to two different groups of users for test purposes
The list above isn't exhaustive, but it's enough to give you an idea of the kinds of feats you can achieve by adopting feature flags.
Basic Feature Flags for JavaScript
For this example, I'm using an application that I created called the Banker API. You can grab it from here if you want a local copy to use to follow along. The API allows you to view your account balance and withdraw money (for now). It's simple, but it's enough for our purposes.
Now we want to add a new feature to our application. For now, we'll keep it simple to illustrate the concept. We'll make a more complex change later when we use the feature flag management I spoke of earlier. The new feature will use feature flags to decide whether the client sees it or not. For now, let's just return a simple message to show that the flag is working.
The UI is as basic as it gets. It's a couple of buttons, and when you click them, you see a message. This isn't smoke and mirrors, as the messages are coming from the server. It's simple enough to show the concept but just enough to mirror what a real app looks like. The UI looks like the following to start:
Now let's add the functionality of depositing money. We'll add a button to deposit money and a method to the server code to handle depositing money. Only this time, we'll control whether or not the feature is available using a simple feature flag. First, we'll add the button to the UI:
<div class="col-xs-12 col-md-3"><button type="button" class="btn btn-success">Deposit $200</button></div>
Now, we'll add the code to the server code in app/bank/index.js.
deposit: function(req, res) { res.json({ newBalance: "Deposited $500!" }); }
Okay, time for the feature flag. We'll start on the server side. The best place to put this code is in the server.js file in the main directory. It will turn off the deposit functionality based on the feature flag. We'll add a flag and set it to true, then call it from the client to show that it works.
var depositTurnedOn = true; if(depositTurnedOn){ router.post('/deposit', function(req, res) { routes.deposit(req, res); }); }
When you click the deposit button, you can see that the deposit is working.
Congrats on your first feature flag. If we turn it off in the code and restart the server, we see that the deposit functionality is unavailable.
var depositTurnedOn = false;
Now let's see what we can do with the client-side code. Set the flag back to true on the server side. Now we'll tell the client code to only show the deposit button if the feature flag is set to true. I'm using Bootstrap for the UI code to keep things simple, and if you're using the same, you'll simply add the "invisible" class to the deposit button to make it disappear. Now, let's add the code that checks for a feature flag before showing the button to the user.
var depositFeatureOn = true; if(depositFeatureOn) { $('#deposit-btn').removeClass('invisible'); }
So there you have it. The basic feature flag on the client side that shows the button only if the feature is turned on.
Why Do We Need To Manage Flags In JavaScript?
I'm sure that many of you are thinking, "Wait a minute. I have to change code and restart the server just to flip a flag?" You're totally correct. This is not really a fully realized feature flag yet. We have to peel another layer of the onion and dive deeper into the world of feature flag management. Our end goal is the ability to flip features on and off without touching code at all. We want to avoid the added technical debt of random variables and conditionals thrown around the code without any management. We'll touch on the management piece to a great degree later. In the meantime, we can still improve our code with one good change.
Getting the Configuration Out of Code
The first step to good feature flag management is to pull the feature flag configuration out of code. It simply is not maintainable for developers to have to open up a code file and change code in order to update the feature flag. On the server side, this is pretty easy. We'll set up a config.json file like this:
{ "depositTurnedOn": true }
Place it in the config folder of the project and reference it using a require statement in the server.js file. With that, you're ready to use it in code by reading in the configuration when the application starts up.
config = require('./app/config/config.json'); var depositTurnedOn = config.depositTurnedOn; if(depositTurnedOn){ router.post('/deposit', function(req, res) { routes.deposit(req, res); }); }
This moves the configuration out of the code for the server code. What about the client code? How can we remove that? Well, this gets interesting. In order for the client code to grab the config file, you need to replicate the config.json file in the directory where the index.html page is. Once the config.json file is in the client folder, you can now get it by requesting the file and parsing it using the $.getJSON function provided by jQuery. Behind the scenes, this is simply an ajax call to the server to grab the file. Add this to your client code.
var depositTurnedOn; var config = $.getJSON("http://localhost:3000/config.json", function(data){ depositTurnedOn = data.depositTurnedOn; if(depositTurnedOn) { $('#deposit-btn').removeClass('invisible'); } });
Don't forget to add the removeClass call to the callback function since this is executing asynchronously. If you don't, you may not have the value of the configuration before the if statement fires.
This Approach Has Downsides
I personally don't like grabbing a file off of the server like this to use as a configuration file. It feels clunky, and the next developer looking at your code will have a hard time understanding it. Another issue is the fact that the config.json file now has to reside in two places: one where the server code can reach it and another that's accessible over the internet. This is a maintenance nightmare. There's a way the config.json can stay where it is and the client can still find out what feature flags to activate. We're going to create a new REST endpoint with the purpose of retrieving config information. This will allow the client to ask the server what the current configuration is for feature flags and make decisions based on that.
A Better Way to Grab Feature Flag Configuration
First, we'll create a new function inside of app/bank/index.js. This function will simply return the configuration from the safe location on the server.
config = require('../config/config.json'); config: function(req, res){ res.json(config); }
Next, let's expose this service by placing the route for it in the server.js file.
routes = require('./app/bank') router.get('/config', function(req, res){ routes.config(req, res); });
Now the client can ask the server what the current feature flag configuration is. We'll illustrate this using Postman. When you access this URL, you 'll receive the JSON configuration flags.
This technique has a couple of benefits. First, you don't have to expose the configuration of your application in a static file that anyone can grab from the server. If you use an endpoint like this, you can use authentication and authorization techniques to control who can ask for configuration information. You also have the option to use business logic in the REST endpoint for A/B testing. The client asks, "Can this customer see this experiment?" and the endpoint responds with a yes or no. This is useful for segmenting experiment populations based on business-specific criteria.
Centrally Managing Feature Flags
At this point, you've created a simple feature flag that's controlled by changing code. Then you took the flag out of code and put it into a configuration file. You created a service so that a client can get the configuration information from the server in a centralized location. This means that you go to one place to set all of the flags, and then you don't have to worry after that. We now can peel back another layer of that onion. Companies like Facebook make use of feature flags on a regular basis to provide "dark launches," or features that are active but not yet visible to the customer. By using feature flags on the client and server side, you can accomplish the same thing, and you don't have to be Facebook to do it. What you just did in the last section was a simple version of what centralized feature flag management is all about. You don't need to have configuration files in several locations for client and server. We can do this in JavaScript using CloudBees Feature Management.
Getting Started With CloudBees Feature Management
You can create a CloudBees Feature Management account for free to get started. Since we have a client and server, we'll need two applications in CloudBees Feature Management. Create a new app and choose JavaScript as the language and Node.js as the platform on the new app screen.
Next, the website will show you how to install CloudBees Feature Management in your application. We'll walk through it quickly here.
First we install CloudBees Feature Management.
npm i rox-node --save
Configuring The Server
Then we require the "rox-node" module in our server.js file. This will allow us to set up CloudBees Feature Management for use in our API.
const Rox = require("rox-node"); ... app.listen(port, hostname, () => { Rox.setup(config.roxAPIKey); console.log('Server started on port ' + port); });
Now, on CloudBees Feature Management's administration screen, we get the good check to show that everything is ready to go!
Configuring The Client
For the client, install the 'rox-browser' package. Then place the rox-browser.min.js file from the node_modules/rox-browser/dist folder in the client directory with index.html. We'll use that in a minute. We could create a second application for the client code. That's totally valid, especially if the client is in a separate repository or maintained by a separate team than the one maintaining our API code. For our purposes, since the client and server code is together, we'll have both the client and server feature flags managed through one application. Let's first create a feature flag to see how it works. Add the following code to server.js:
const bankerFlags = { depositTurnedOn: new Rox.Flag() } app.listen(port, hostname, () => { Rox.register('BankerFlags', bankerFlags); Rox.setup(config.roxAPIKey); console.log('Server started on port ' + port); });
This code creates a container to hold our centrally managed feature flags. Now when you run the application, you'll see the flags in the CloudBees Feature Management interface. Pretty cool.
We'll have the client request the config from the server now to retrieve the API key. This way, the client code can make decisions based on feature flags in CloudBees Feature Management. I'm designing the system this way so the API key isn't on a publicly readable directory, which it would have to be if the client wants to grab it directly. Also, the call to the endpoint would be made over HTTPS in a real application (though I'll forgo that here) so that the API key can be safely transferred to the client code. Now just add some code to the client to retrieve the API key and talk to CloudBees Feature Management.
const bankerClientFlags = { depositTurnedOn: new Rox.Flag() } Rox.register('BankerFlags', bankerClientFlags) Rox.setup("<Your API Key Here>"); jQuery(document).ready(function(){ ... });
This code connects to the CloudBees Feature Management API. The connection works best when initialized before the call to jQuery. But this doesn't actually do anything yet. If you load the page, the button is gone. So let's go change that now. CloudBees Feature Management uses the concept of experiments to control feature flags in production. Experiments allow you to easily turn feature flags on or off. Click on "Production" in the left-hand menu and go to "Experiments."
Creating a New Experiment
There will be a "New Experiment" button at the bottom of the page. Go ahead and click that to bring up a dialog window where you can create a new experiment.
Click the "Set Audience" button to finish setting up the experiment.
Now back to the code. Right now, we still have a variable determining if we should allow the deposit functionality to be active. We'll remove that and instead use the CloudBees Feature Management flag we just created. First, let's do that on the server:
const bankerFlags = { depositTurnedOn: new Rox.Flag() } ... if(bankerFlags.depositTurnedOn.isEnabled()){ router.post('/deposit', function(req, res) { routes.deposit(req, res); }); }
Then we'll do that on the client:
const bankerClientFlags = { depositTurnedOn: new Rox.Flag() } Rox.register('BankerFlags', bankerClientFlags) Rox.setup("5a93fb4201109414508318fa"); jQuery(document).ready(function($){ if(bankerClientFlags.depositTurnedOn.isEnabled()) { $('#deposit-btn').removeClass('invisible'); } ... });
We now call the isEnabled() function on the CloudBees Feature Management flag to determine whether or not we should activate the functionality for the user. CloudBees Feature Management feature flags default to false, so let's go the CloudBees Feature Management administration screen and turn this feature flag on. Don't forget to click the "Update Audience" button to save.
Now let's load up the application and see if our feature flag worked.
There's our button. And when we click it, we see our happy message again.
The Power of Feature Flags
Feature flags can help your development team in several ways. You can use A/B testing to find out what UI treatments will work before you make a permanent change. You can smoke test and even test performance of services in production before users see them. If something goes wrong, simply revert the change by switching the flag back to false. Now you know how feature flags work with JavaScript, both in the browser and in the server. We saw how simple a feature flag can be. But remember, feature flags can grow over time and become a maintenance problem. Peel back the onion, and use the layer that will give you the best benefit without the hassle: centralized feature flag management with CloudBees Feature Management.