Embedding a Web application in a Golang binary
By: Percy Bolmér
Tags: React, Software Development, Grpc Web, JavaScript, Golang

Photo by Heliberto Arias on Unsplash

Some time ago I wrote an article on how to run gRPC through a web application without Envoy. I’m going to use the project in this article, and embed the web application into a binary. If you’re not familiar with the project I suggest reading part one. But if you only want to learn how to use the embed package in Golang you should be able to follow this article without prior knowledge.

Why would we want to include things like an web application in a binary?

For me, it’s all about the deployment. Being able to just send a single binary to a server is a big time saver.

Before Golang 1.16 (this is when the embed package is released), we would need to deploy the binary, and the web application, or any other files we needed that wasn’t compiled code.

To solve this I tend to use Docker, I know a lot of people also recommend Packer. Don’t get me started on docker, I love it, I’m a super fanboy.

But, lets be honest, sometimes it would make much more sense with a binary than a docker.

Lets get started setting the project up

Before we start coding lets grab the project that we will modify.

mkdir embedding && cd embedding

git init

git pull https://github.com/percybolmer/grpcexample

Its a super simple gRPC API that has an Ping function, and a React application that is hosted on localhost:8080 which will call the Ping function each third second. This is not important for the tutorial, you can use any other web application. If your interested in reading more about the project, see part one of this series.

If you are using the project you need to generate certificates, if you haven't already. Go into the cert folder and run the certgen.sh script. This will require you to have openssl installed.

cd cert

bash certgen.sh

We will also need to compile the react application that we are embedding. The application is located inside the ui folder.

Before running the build commands make sure you add two lines to the package.json file. We need to tell the React application that these files will be served without a server.

Add the two lines below to ui/pingpongapp/package.json

“homepage”: “.”,

”proxy”: “https://localhost:8080",

Run the following commands.

npm install

npm run build

npm install will download any needed dependencies, and npm run build will build a static application located inside a new folder called build. This is the folder that we will embed later.

You will also need to run at least Golang 1.16. This isn’t currently released so let’s download Golang 1.16 beta.

go get golang.org/dl/go1.16beta1

go1.16beta1 download

Running the download command will download and install the new golang binary for you. Test your installation by running

go1.16beta1 version

You should see an output that mentions the current version

Lets embed

Once your all setup, open main.go.

You can read about the embed package here.

What we need is to embed our build folder and embed that into our binary as an filesystem. I recommend reading the linked package docs, its really short and explains everything very well.

The embed package differs from the rest of the packages I’ve seen before. We will actually use comments to tell the compiler what action to perform.

The syntax is short and concise.

//go:embed $PATH

var content $WANTED_DATA_TYPE

There is 2 lines of code, one comment and one variable declaration. They have to be directly after each other, starting with the comment.

See the $PATH and $WANTED_DATA_TYPE? Those needs to be replace.

$PATH should be replaced with the path to the files or directories you want to include. A side note is that if you use the data type embed.FS, it accepts multiple paths in one comment, and also many comments at the same time.

$WANTED_DATA_TYPE is to be replaced with the any of the available data types:

String = Accepts a single file and upon building will read the content of that file into the variable as a string

= Accepts a single file and upon building will read the content of that file into the variable as a string []Byte = Same as String, but as a []Byte

= Same as String, but as a []Byte embed.FS (Short for filesystem) = Now this is where embed gets exciting for me. We are allowed to embed multiple directories and files. And the best part, they are part of the io/FS interface. This means net/http package can use this out of the box.

With this new knowledge, lets update main.go to embed the build folder at ui/pingpongapp/build.

In the old main.go the path is hard coded, that needs to be changed into an embedded filesystem.

Lets build the binary and run it

go1.16beta1 build -o api . && ./api

Visit localhost:8080 and watch the status change from false to true after 3 seconds!

That’s it! I’ve gotta hand it to the team at Golang, they’ve made this super simple. We have in just 2 new lines of code added a embedded web application to our binary.

If your interested, feel free to read Part 2 about implementing gRPC interceptors.