Golang Feature Flags: A step-by-step guide with a working application

By Abhishek Agarwal on October 19, 2022

Introduction to Golang feature flag

This is a simple tutorial that demonstrates the enormous amount of power that Flagsmith feature flags can add to your Golang application with very minimal integration steps. This blog is a 0 prerequisite doc, which covers everything from scratch. We will first build a basic GoLang server application, then we will add Flagsmith feature flags to make it more robust and tolerant.

So let's dive deeper into the Golang feature flag tutorial!

Golang feature flag Flagsmith

What are Feature Flags? 

Before we dive into the Golang feature flag development work, let’s first understand what Feature flags are and why they are required. 

In simple words - a feature flag can be thought of as an electric switch (which operates an electric appliance). If the switch is turned on - the appliance would function as we expect it to when the switch is off - it would be in rest mode and won’t do anything i.e. it won’t consume any electricity and stop all its internal functionality. Analogously, in a computer application feature flags are required to pause the system from making any computations, or external API calls, and make it return just a default response indicating that the application has been currently turned off. 

The above definition tells about Feature flags usage as a “Kill-switch”. In addition to this, feature flags can add much more power by actually functioning as a non-binary tool, and allowing you to switch to different behaviors of the application as per the requirement. Likewise, an example in the real world would be the different modes of an air conditioner. Based on the weather you decide which mode to choose, similarly based on the user or any other external factors feature flags let you decide the application behavior.

Why are Feature Flags required?

Let’s understand this through a very simple real-life example: 

Imagine that your Air Conditioner is not cooling at all for the past 30 minutes. What would you do as a first thing? I’m sure you would turn it off(to save electricity, and prevent any unwanted damage) and call a technician for help. As otherwise you would just be wasting your resources(electricity) and delivering a bad experience to all the members in the house. Once the mechanic fixes the issue, you would turn it back on.

This is exactly how a feature flag helps your application in case of any unexpected scenarios. It allows you to turn the malfunctioning component off and give developers the time to fix it behind the scenes. Once the issue is mitigated and fixed, it can be turned back on. 

Now a basic question that comes to mind is, why not just kill the application in such cases? It is never a good idea to kill the whole application for one bug. It offers a very bad customer experience, giving the impression that this application is not reliable and tolerant. Instead, displaying a simple message such as “We will be back soon” on the particular page will be much better, since the rest of the application still functions as normal.

Why Flagsmith?

It’s FREE! Flagsmith provides an all-in-one platform for developing, implementing, and managing your feature flags. It offers you the capability to leverage feature flags to their maximum potential by storing values inside them. These values can be used to toggle multiple kinds of behaviors as illustrated in the “What are Feature Flags” section. 

Flagsmith is an open-source tool which makes it very trustworthy. 

Flagsmith provides very seamless integration with well-maintained documents for multiple programming languages across web, mobile, and server-side applications.  It offers various customizations which makes it very convenient to use. 

Let’s get started with Golang feature flags!

As a first step for the Golang feature flag process, we need to create an account on https://flagsmith.com/. It offers simple sign-in with Google or GitHub options. Or you can just fill in your basic details at https://app.flagsmith.com/signup. The below screenshot shows the signup process:

Feature Flagging Tool

It will then ask you to create an organization. For the purpose of this tutorial, I’ll name it GoLang Tutorial. You can name it anything you prefer.

Golang feature flag tool

Next, we need to create a project inside this organization:

Golang Feature Flag software

Click on “Create A Project”. The below screen should pop up. Enter your project name. I’m creating a project with the name “My GoLang Server”. Feel free to name it as you want.

Golang feature toggle

Congrats! You have already completed the Flagsmith setup process. You should see the below screen now.

Golang feature flagging

We will come back to this screen when we want to add a feature flag to our application. 

Now, let’s start by creating a GoLang server application. 

Let’s code the Golang feature flag!

To keep this tutorial simple, we will create a rudimentary Go server application. It will be exposed on localhost and will be using our local system storage.  This server maintains a list of books and exposes two APIs:

  1. to add a new book and
  2. to query the books present in its records. 

If you do not have "go" installed on your system, download and install the latest stable version from this link.

TL;DR: All the code is present in https://github.com/abhishekag03/flagsmith-go-feature-flag-demo with step-by-step commits. Feel free to refer to it at any time if you feel confused.  

STEP 1: Go setup 

Make sure you have "go" installed by running this command on your terminal:

1go version
2

I’m on version 1.19.1, thus getting the below output. 

Go feature flag

STEP 2: Code folder setup

Create a new folder in your workspace by running the below command(Feel free to change the folder name):

1mkdir flagsmith-app
2

Change your current directory to the “flagsmith-app” folder by running:

1cd flagsmith-app

Make a new empty go file in this folder. This file will contain all our logic and API definitions.

1touch app.go

Create a go.mod file. Go mod files are mandatory for each go module to run. These describe the module’s properties like dependencies. Further, read here

1go mod init app.go
2
3

Your directory must now contain 2 files like shown in the image below(run “ls” command to check):

STEP 3: Familiarise yourself with the magical Gin 

We will use the Gin framework to create a server for easy bootstrapping. If you’re not familiar with Gin, I would highly recommend reading its documentation

Installing Gin is pretty simple, just run this command:

1go get -u github.com/gin-gonic/gin

STEP 4: Create a GET API Endpoint

Paste the below code to your app.go file. A detailed line-by-line explanation has been added as comments in the code. 

1package main
2
3import (
4	"net/http" // for returning standard defined api response codes
5	"github.com/gin-gonic/gin" // to easily bootstrap api server
6)
7
8// a book struct(class) which contains attributes describing a book
9type book struct {
10	ID     string  `json:"id"`
11	Title  string  `json:"title"`
12	Author string  `json:"author"`
13	Price  float64 `json:"price"`
14}
15
16// a list with 3 distinct books defined as per the struct above
17var books = []book{
18	{ID: "1", Title: "Harry Potter", Author: "J. K. Rowling", Price: 26.99},
19	{ID: "2", Title: "War and Peace", Author: "Leo Tolstoy", Price: 17.99},
20	{ID: "3", Title: "The Kite Runner", Author: "Khaled Hosseini", Price: 29.99},
21}
22
23// getBooks responds with the list of all books as JSON.
24func getBooks(c *gin.Context) {
25	c.IndentedJSON(http.StatusOK, books) // IndentedJSON is like pretty-print which makes it more readable
26}
27
28func main() {
29	router := gin.Default()        // creates a gin engine instance and returns it
30	router.GET("/books", getBooks) // registering the function "getBooks" that will be called when /books endpoint is hit
31	router.Run("localhost:8080")   // running the server on port 8080
32}

Run the go mod tidy command so that it can pick up the dependencies from your code and import them.

1go mod tidy

Now you should magically see a “go.sum” file with 80+ lines of code appearing in your directory. What is this file?

This file contains the checksums for each direct/indirect dependency. This helps Go validate if any of the dependencies have changed when we are running the code again.

Golang Feature Flag Tutorial

Let’s run the server now. Simply run:

1go run app.go

You should see an output below:

Feature Flag Golang Guide

The warnings can be ignored. The main line of relevance here is “[GIN-debug] Listening and serving HTTP on localhost:8080

STEP 5: Testing the GET Endpoint

Open a new terminal window and call the /books endpoint using curl. You should see a response like below:

Feature Flag Management

This is the same list of books that we hard coded in our books list in the code. 

OR You can simply go to your browser window and type localhost:8080/books. The same response should be visible. 

STEP 6: Add a POST endpoint

Simply add the below lines to the main function. This is a very basic implementation that adds a new book to the existing list of books. 

1router.POST("/books", func(c *gin.Context) { // defining the function to be executed when /books POST endpoint is hit
2		var newBook book
3		if err := c.BindJSON(&newBook); err != nil {
4			return
5		}
6		books = append(books, newBook)
7		c.IndentedJSON(http.StatusCreated, newBook)
8	})

Your main function should now look as follows:

1func main() {
2	router := gin.Default()                      // creates a gin engine instance and returns it
3	router.GET("/books", getBooks)               // registering the function "getBooks" that will be called when /books endpoint is hit
4	router.POST("/books", func(c *gin.Context) { // defining the function to be executed when /books POST endpoint is hit
5		var newBook book
6		if err := c.BindJSON(&newBook); err != nil {
7			return
8		}
9		books = append(books, newBook)
10		c.IndentedJSON(http.StatusCreated, newBook)
11	})
12	router.Run("localhost:8080") // running the server on port 8080
13
14}

STEP 7: Testing the POST endpoint

We will now try to add a new book to our list. Feel free to add a book of your choice, I’m adding a book named “Famous Five” by Enid Blyton that costs $20. 

Bring up the terminal window and restart your main server.(In case it was already running using Ctrl + C to stop it). 

To run the server again

1go run app.go

In a new terminal window, paste the below command to add a new book:

1curl http://localhost:8080/books \
2    --include \
3    --header "Content-Type: application/json" \
4    --request "POST" \
5    --data '{"id": "4","title": "Famous Five","author": "Enid Blyton","price": 20}'

You should see an output similar to the one shown below:

Feature Toggle Management

This is a successful response that prints the book that just got added to the list. 

In the other terminal(where the server is running), you should see 

Feature Toggle Tool

A 201 response indicates that the post request successfully created the requested object. More details here

Congratulations, our application is ready to serve all the traffic now. 

STEP 8: Understand the need of Flagsmith in our application

Now that our service is ready, imagine you want to restrict the time at which this API can be invoked. Probably we do not want our library to be active all day long, just during the day hours and on working days. 

How can we add such functionality with the click of a button? This button would not only help us in the above scenario but also when we have an unexpected DB failure or any other downstream service issue or an attack.  This button would help us to stop adding new books with just a click. This is the kind of power Flagsmith offers without having the need to host our own Feature flag server. 

Time to return to our Flagsmith UI. Click on the “Create your first feature” button on the screen we left off above. 

Add a new feature flag by the name “enable_new_books”. This will control whether or not we allow adding new books. Turn enabled by default to “true”. The below screenshot shows the configuration. 

Golang Feature Flag Management

Value can be left blank, we will come back to it later. Click on “Create Feature”(Make sure you have enabled it). The UI should show the following:

go feature flag

We will also need a Flagsmith Server-side environment key to initialize the feature flag client. Let’s generate that. 

Navigate to “Settings” from the left bar on the Flagsmith page. Click on “Create Server-Side Environment Key”. Give it a name for e.g: “test-app-key”

Golang Feature Flag Guide for Beginners

Keep the key handy in your notes. It should be of the form “ser.******....”

STEP 9: Add Flagsmith integration to the code

Let’s add the Flagsmith client code to our code. The Go server-side documentation on the Flagsmith website here lists all the steps required to do so. We will cover them here as well. 

First, you need to install the flagsmith go client by running(in your terminal):

1go get github.com/Flagsmith/flagsmith-go-client/v2

Now, we need to import this in our go file and initialize it. Add the below import statement.

1import (
2  flagsmith "github.com/Flagsmith/flagsmith-go-client/v2"
3)

Initialize the client in your main function by adding the following lines of code:

1ctx, cancel := context.WithCancel(context.Background())
2defer cancel()
3
4// Initialise the Flagsmith client
5client := flagsmith.NewClient('<FLAGSMITH_ENVIRONMENT_KEY>', flagsmith.WithContext(ctx),)

We will come to <FLAGSMITH_ENVIRONMENT_KEY> in a moment, but for now, your main function must look like this:

1func main() {
2	ctx, cancel := context.WithCancel(context.Background())
3	defer cancel()
4
5	// Initialise the Flagsmith client
6	client := flagsmith.NewClient("<FLAGSMITH_ENVIRONMENT_KEY>", flagsmith.WithContext(ctx))
7	
8	router := gin.Default()                      // creates a gin engine instance and returns it
9	router.GET("/books", getBooks)               // registering the function "getBooks" that will be called when /books endpoint is hit
10	router.POST("/books", func(c *gin.Context) { // defining the function to be executed when /books POST endpoint is hit
11		var newBook book
12		if err := c.BindJSON(&newBook); err != nil {
13			return
14		}
15		books = append(books, newBook)
16		c.IndentedJSON(http.StatusCreated, newBook)
17	})
18	router.Run("localhost:8080") // running the server on port 8080
19}
20

The best way to manage the environment and secret keys is to create a separate file and add them. We will create a config.yml file in the same directory and read it in our go code. 

STEP 9.1: Create config.yml file

In the same project directory, create a new file and add a config key value pair inside it like shown below(I have hidden my secret key):

Feature Flag Management Golang

STEP 9.2: Read config in code

We would need a golang yaml library to parse a yaml file. It can be added by running:

1go get gopkg.in/yaml.v3

Add the below import:

1import (
2   "gopkg.in/yaml.v3"  
3)

To read the config file, add this method to your go file:

1func loadConfigMap() (map[string]string, error) {
2	yfile, err := ioutil.ReadFile("config.yml")
3
4	if err != nil {
5		return nil, err
6	}
7
8	config := make(map[string]string)
9
10	err = yaml.Unmarshal(yfile, &config)
11
12	if err != nil {
13		return nil, err
14	}
15	return config, nil
16}

We then need to call this method just before initialising the flagsmith client. 

1config, err := loadConfigMap()
2if err != nil {
3	panic("could not read config")
4}

The environment key can now be obtained from this config by simply doing config[“key”]

We can update the placeholder while initializing the Flagsmith client. Thus, our main function should look like:

1func main() {
2	ctx, cancel := context.WithCancel(context.Background())
3	defer cancel()
4
5	config, err := loadConfigMap()
6	if err != nil {
7		panic("could not read config")
8	}
9	// Initialise the Flagsmith client
10	client := flagsmith.NewClient(config["key"], flagsmith.WithContext(ctx))
11
12	router := gin.Default()                      // creates a gin engine instance and returns it
13	router.GET("/books", getBooks)               // registering the function "getBooks" that will be called when /books endpoint is hit
14	router.POST("/books", func(c *gin.Context) { // defining the function to be executed when /books POST endpoint is hit
15		var newBook book
16		if err := c.BindJSON(&newBook); err != nil {
17			return
18		}
19		books = append(books, newBook)
20		c.IndentedJSON(http.StatusCreated, newBook)
21	})
22	router.Run("localhost:8080") // running the server on port 8080
23}

STEP 10: Use Flagsmith power to toggle API behavior

With one line of code, we can check if the feature is enabled or disabled. We will have a simple logic saying, if it is enabled add the book to the list, else give a response saying “sorry, please come back later”.

Feature flag enabled/disabled can be checked with:

1isEnabled, err := flags.IsFeatureEnabled("enable_new_books")

Our modified POST method implementation looks like the following:

1router.POST("/books", func(c *gin.Context) { // defining the function to be executed when /books POST endpoint is hit
2	flags, err := client.GetEnvironmentFlags()
3	if err != nil {
4		return
5	}
6	isEnabled, err := flags.IsFeatureEnabled("enable_new_books")
7	if err != nil {
8		return
9	}
10	// Add the new book to the list if feature flag is enabled
11	if isEnabled {
12		var newBook book
13		if err := c.BindJSON(&newBook); err != nil {
14			return
15		}
16		books = append(books, newBook)
17		c.IndentedJSON(http.StatusCreated, newBook)
18	} else {
19		c.JSON(http.StatusMethodNotAllowed, gin.H{
20			"code":    http.StatusMethodNotAllowed,
21			"message": "sorry, please come back later",
22		})
23	}
24})

STEP 11: Let’s test the working of the Golang feature flag

Make sure to save your code. 

Since we added new imports, we must run:

1go mod tidy

Restart the server by running:

1go run app.go

Try adding a book to the list by running the same curl command:

1curl http://localhost:8080/books \
2    --include \
3    --header "Content-Type: application/json" \
4    --request "POST" \
5    --data '{"id": "4","title": "Famous Five","author": "Enid Blyton","price": 20}'

This should be successful. 

Now, navigate to the Flagsmith UI and toggle the “enable_new_books” feature OFF as shown below:

Feature Flagging Process

Running the same command as above this time should give you an error message:

1{"code":405,"message":"sorry, please come back later"}
Feature Toggling process

Did you see how powerful this was? This is a very basic application; imagine the amount of control it can add to a large-scale application with just the click of a button. 

STEP 12: Using Flagsmith Local Evaluation mode

First, let’s talk about what is the “Local Evaluation” mode in Feature flags. 

The way in which we added the feature flag above, it always makes a “BLOCKING” network request to the Flagsmith server for getting the feature flag value. Now, imagine having too much traffic on your application. A blocking network call every time leads to increased latency for all the requests. Also, our application may not be so critical that we want to get the updated feature flag value within a fraction of a second. Most applications can still function if the updated value is not reflected up to a certain time.

Let us consider that our application is ok up to 30 seconds after the feature flag value update has taken place. In that case, there is no need to make a call on every POST request. What if we just make an async call every 30 seconds and use the value directly from there? It will save us huge numbers on latency. 

Flagsmith allows us to add a configurable “Environment refresh interval” when running in the Local evaluation mode that solves exactly the use case described above.

All we need to add is the following while initializing the Flagsmith client:

1client := flagsmith.NewClient(config["key"],
2		flagsmith.WithLocalEvaluation(), // for local evaluation
3		flagsmith.WithEnvironmentRefreshInterval(30*time.Second),
4		flagsmith.WithContext(ctx),
5	)

STEP 13: Testing Flagsmith local evaluation mode

The latency difference would not be visible in just a few manual requests. Thus, I have written a small python script that would make it very evident how powerful this feature is. 

Remember to have the feature flag enabled, update the client as defined above and restart the server. 

Save this python script in the same directory:

1import requests
2import time
3
4
5data = {
6    'id': '4',
7    'title': 'Test book',
8    'author': 'Betty Carter',
9    'price': 49.99,
10}
11
12start = time.time()
13for i in range(50):
14    x = requests.post('http://localhost:8080/books', json=data)
15
16end = time.time()
17print("Total time taken: ", end - start)

Run this script in both modes - (a) with Local Evaluation turned off, and (b) with Local evaluation turned on. 

Notice the difference in time taken.

1. Without Local Evaluation

Golang Feature Flags

2. With Local Evaluation

Golang Feature Flagging

The job got done in FRACTIONS of seconds. Take a note of the amount of time saved in just 50 requests. Imagine a massive application serving thousands of requests per second. This is one of the most powerful features that Flagsmith offers. 

20 seconds vs 0.1 second. HUGE!

STEP 14: Leveraging feature values

While creating a new feature, we saw an option to add a feature value. Let’s understand how that can be useful. 

For instance, due to some requirements, we only want to accept books that have a price greater than some value. This is to avoid filling up our library with cheap and less-selling books. Also, helps us maintain a standard. 

However, we want to keep varying this threshold price based on the rush and staff capacity. It is definitely not feasible to amend the code every time as per the requirement.

 What if this can also be achieved with the click of a button? Yes, with Flagsmith it can be.

Go ahead and update the feature flag value to 30 as shown in the below image. Also, do enable the feature back so that we can continue testing.

Flagsmith feature toggling tool

Click on “Update Feature Value” to confirm. 

To read this feature value, all we need to do is:

1minValStr, err := flags.GetFeatureValue("enable_new_books")
2if err != nil {
3   return
4}
5minVal := minValStr.(float64)

Now, let’s add a check to ensure all the new books added are of a price greater than $30. 

The updated POST method should now look like the one below. Notice the changes inside the “isEnabled” if condition.

1router.POST("/books", func(c *gin.Context) { // defining the function to be executed when /books POST endpoint is hit
2	flags, err := client.GetEnvironmentFlags()
3	if err != nil {
4		return
5	}
6	isEnabled, err := flags.IsFeatureEnabled("enable_new_books") // read if feature flag is enabled
7	if err != nil {
8		return
9	}
10
11	minValStr, err := flags.GetFeatureValue("enable_new_books") // get feature value
12	if err != nil {
13		return
14	}
15	minVal := minValStr.(float64) // convert value to float
16
17	// Add the new book to the slice if feature flag is enabled
18	if isEnabled {
19		var newBook book
20		if err := c.BindJSON(&newBook); err != nil {
21			return
22		}
23		if newBook.Price >= minVal { // check if price is greater than minimum threshold
24			books = append(books, newBook)
25			c.IndentedJSON(http.StatusCreated, newBook)
26		} else {
27			c.JSON(http.StatusNotAcceptable, gin.H{
28				"code":    http.StatusNotAcceptable,
29				"message": "sorry, book price does not meet the minimum threshold",
30			})
31		}
32	} else { 
33		c.JSON(http.StatusMethodNotAllowed, gin.H{
34			"code":    http.StatusMethodNotAllowed,
35			"message": "sorry, please come back later",
36		})
37	}
38})

STEP 15: Check the working of the Golang feature flag value

Run the app in one terminal.

1go run app.go

In another terminal run the command with the price of the book as $20:

1curl http://localhost:8080/books \
2    --include \
3    --header "Content-Type: application/json" \
4    --request "POST" \
5    --data '{"id": "4","title": "Famous Five","author": "Enid Blyton","price": 20}'

This should throw an error as below:

1{"code":406,"message":"sorry, book price does not meet the minimum threshold"}

If you increase the book price to say $40, it will work. 

1curl http://localhost:8080/books \
2    --include \
3    --header "Content-Type: application/json" \
4    --request "POST" \
5    --data '{"id": "4","title": "Famous Five","author": "Enid Blyton","price": 40}'

This should give a 201 response code. 

Now it is so convenient to update the minimum allowed price directly from the Flagsmith UI. Feel free to play around with these values and toggling the switch on or off.

If you want to start using Flagsmith as your Golang feature flag software- just create your account here or contact us for the on-prem solution.

P.S. If you like this "Golang feature flag" guide, you can check out our other guides: