Single Executable Web Apps With Go Binary Assets

Posted on · Tagged in

Go is becoming increasingly popular with building web applications. It’s fast, lightweight and easy to learn. Another great feature is to compile your applications into a single redistributable binary. A lot of web applications depend on external assets, however. For the application to work you still need to package your css, JavaScript and image files along with the executable. Let’s see if we can fix this.

Imagine you have a single page web application. Your application code is saved in main.go. Your frontend code is saved in a folder called assets and called app.js. You also have a style.css and a few images. Normally you need to make sure that assets folder exists along side your main executable wherever you have it deployed. If not your application will complain with a bunch of 404 not found errors.

In Go, it’s possible to convert all of those files to pure Go code and then serve them up like they were sitting on the filesystem. We can accomplish this with two packages jteeuwen/go-bindata and elazarl/go-bindata-assetfs.

The first thing we need to do is install the packages.

$ go get github.com/jteeuwen/go-bindata/...
$ go get github.com/elazarl/go-bindata-assetfs/...

Now we can convert our assets to Go code using the go-bindata CLI tool.

$ go-bindata assets/

This will create a file called bindata.go in your current directory. All the code in that file will be scoped to the main package. To access any of our files we can use the Asset(string) ([]byte, error) function. Calling Asset("assets/app.js") will return a []byte slice of the app.js file which we can echo through our web application manually if we wish. An easier method would be to create an endpoint that serves all of those assets as if they were on a fileserver. This is where go-bindata-assetfs comes in.

Here is an example.

package main

import (
	"log"
	"net/http"

	"github.com/elazarl/go-bindata-assetfs"
)

func main() {
	// Use binary asset FileServer
	http.Handle("/",
		http.FileServer(
			&assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: AssetInfo, Prefix: "assets"}))

	log.Println("http server started on :8000")
	err := http.ListenAndServe(":8000", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

The go-bindata-assetfs package includes a Struct type called AssetFS that implements the http.Filesystem interface which looks like this.

type FileSystem interface {
        Open(name string) (File, error)
}

To initialize a new AssetFS we need to pass a few of the helper functions generated by go-bindata. The final property is Prefix which sets the root of the FileServer as whatever we specify.

In this example we are now serving files at / and any files that were in assets/ are now accessible from that path. It would be simple to place an index.html file to use as an entrypoint for a single page application.

Now we no longer have to distribute our assets along with the final executable. This makes our deployments just a little bit easier and more managable.

Comments

comments powered by Disqus
Subscribe to my newsletter and get a free copy of my book, Aspect Oriented Programming in PHP.