Feature flag for A/B testing in Javascript
Feature flag for A/B testing in Javascript

In this post you will learn how to implement feature flag on the client and on the backend.

What is feature flag?

The first question here is obviously "What is feature flag and why do we need it?". At some point of developing your application you want to roll out new changes. But you can't be sure that these changes are working properly or you want to test if this feature is really good. This is called A/B testing.

A-B testing

A/B testing (split method testing) is a method of comparing two versions of webpage or app against each other to determine which one performs better.

Typically you want to test a feature with some users and not roll feature out for all users. This is exactly why we need feature flag. We want to enable some new feature not for the whole project but for the part. This is why in this post we will without any libraries we will implement feature flag on the client and on the backend.

Client part

For the client here I have a React application. But it doesn't really matter because this code will be the same in any framework or without framework. What we have here is home page and about page.

Initial project

These are just 2 routes. Let's say that our about page is a new feature and we want to hide this functionality. We want to enable access to this page only with feature flag.

What does it mean? We can use local storage or a cookie where we will set some parameter and we will check it to enable this feature. How we can implement it?

First of all we want to hide link to about page if we don't have a local storage property. Secondly we must prevent access to about route if we don't have a value.

For this I want to create a helper.

// src/featureFlag.js
export const hasFeatureFlag = featureName => {
  return localStorage.getItem(featureName)
}
export const hasAboutPageFeature = hasFeatureFlag('aboutPage')

First we have hasFeatureFlag which will help us to check for different enabled features. Secondly we created hasAboutPageFeature specifically for about page feature.

Now let's use this helper to hide our link.

import { hasAboutPageFeature } from "./featureFlag";
...
const App = () => {
  return (
    <div>
      <Link to="/">Home</Link>
      {hasAboutPageFeature && <Link to="/about">About</Link>}
    </div>
    ...
  )
}

We just wrapped our About link with hasAboutPageFeature property. So if we set a localStorage property with name aboutPage and any value our link will be shown.

The second this that we want is hiding our about page. We want to redirect a user to the home page if he doesn't have a feature flag.

// src/About.js
import { Navigate } from "react-router-dom";
import { hasAboutPageFeature } from "./featureFlag";

const About = () => {
  if (!hasAboutPageFeature) {
    return <Navigate to="/" />;
  }
  return <h1>About</h1>;
};

export default About;

Here we imported the same helper and we redirect the user to homepage if about feature is not enabled.

Disabled feature

As you can see in browser our About link is not rendered and if we try to jump to About route we will be redirected to home page.

If we want to enable our feature flag we must add in local storage key aboutPage. Then link will be visible and we can access the route.

As you can see we didn't use any libraries or services to implement feature flag. And this is exactly how you will typically do it in all client projects.

Backend part

But now the question is obviously how we can implement it with the backend. Because sometimes you have lots of clients and you need to implement your feature flags just in a single place on the backend. Then you will get this information from the API in all clients. For example to get a list of features which are enabled. Then you will do the similar logic like we did on the client but without localStorage.

How we can implement that? For this I want to create a small express server with API.

npm install express
npm install cors
// src/server.js
const express = require("express");
const app = express();
const cors = require("cors");

app.use(cors());

app.get("/", (_, res) => {
  res.send("API is UP");
});

app.get("/features", (_, res) => {
  res.send(["aboutPage"]);
});

app.listen(3001, () => {
  console.log("started server");
});

Here we defined an express API with url /features. Inside we just send an array with enabled feature flags. If this array is empty then we don't have any feature flags enabled. If we have aboutPage inside we must enable our feature on client.

Additionally we used cors package here to avoid cors errors because we will make requests from the client to our API from another port.

Now we must start our API and change client logic to use an API.

node src/server.js

Instead of local storage we must fetch an API and store a value inside the state.

const App = () => {
  const [features, setFeatures] = useState([]);
  const hasAboutPageFeature = features.includes("aboutPage");
  useEffect(() => {
    fetch("http://localhost:3001/features")
      .then((res) => res.json())
      .then((features) => {
        setFeatures(features);
      });
  }, []);
  ....
};

We fetched our features from API and create hasAboutPageFeature by checking the array. We don't need local storage helper anymore.

All our markup will work without changes but we must provide now hasAboutPageFeature property in About component.

const App = () => {
  return (
    ...
    <Route
      path="about"
      element={<About hasAboutPageFeature={hasAboutPageFeature} />}
    />
  );
};

and use this props inside instead.

import { Navigate } from "react-router-dom";

const About = ({ hasAboutPageFeature }) => {
  if (!hasAboutPageFeature) {
    return <Navigate to="/" />;
  }
  return <h1>About</h1>;
};

export default About;

As you can see in browser our project works exactly the same like before. And actually if you are interested to learn how to build real React project from start to the end make sure to check my React hooks course.

📚 Source code of what we've done