paint-brush
How to Embed Static Resources Into a Go Projectby@kibizovd
837 reads
837 reads

How to Embed Static Resources Into a Go Project

by David KibizovNovember 28th, 2023
Read on Terminal Reader
Read this story w/o Javascript

Too Long; Didn't Read

Using a standalone application simplifies deployment and distribution because it's just an executable binary. Rice, Statik and Bindata are all available for embedding resources into a Go application. Let's create an index.html template and test it with a static resource to test the Go code.
featured image - How to Embed Static Resources Into a Go Project
David Kibizov HackerNoon profile picture

I wanted to create a small standalone web application in Go, the opposite of a regular web application where resources would be served separately via a CDN or HTTP server such as Nginx.


However, if performance is not a critical concern or the application is intended for low traffic, using a standalone application simplifies deployment and distribution because it's just an executable binary.


Several packages are available for embedding resources into a Go application:



I won't delve into the specifics of each library, but I prefer the bindata approach due to its ease of use and active support.

Getting Started

First, let's create an index.html inside the frontend/ directory in your project:

<html>
    <body>
        Hello, World!
    </body>
</html>


Now that we have a project setup and a static resource to test, let's install bindata using the command:

go get -u github.com/jteeuwen/go-bindata/...


We're ready to run the backend code of the web application. Create a main.go file, and copy the following code:

package main

import (
	"bytes"
	"io"
	"net/http"
)

//go:generate go-bindata -prefix "frontend/" -pkg main -o bindata.go frontend/...

func static_handler(rw http.ResponseWriter, req *http.Request) {
	var path string = req.URL.Path
	if path == "" {
		path = "index.html"
	}
	if bs, err := Asset(path); err != nil {
		rw.WriteHeader(http.StatusNotFound)
	} else {
		var reader = bytes.NewBuffer(bs)
		io.Copy(rw, reader)
	}
}

func main() {
	http.Handle("/", http.StripPrefix("/", http.HandlerFunc(static_handler)))
	http.ListenAndServe(":3000", nil)
}


Important line in this code:

//go:generate go-bindata -prefix "frontend/" -pkg main -o bindata.go frontend/...


The line above allows us to run the go-bindata command when go generate is called. As of version 1.4, you can run custom commands during the generation phase. It's just a matter of adding //go:generate command argument... to your go file.


The go-bindata command line has several parameters, so check the documentation on how to use it. In our case, we say:


  • -prefix "frontend/" define part of the pathname for static


  • -pkg main define the package name used in the generated code


  • -o bindata.go define the name of the generated file


After running the go generate command, you should see a generated file named bindata.go. The structure of your project should look like this:

.
│ 
├── bindata.go (auto-generated file)
├── frontend
│   └── index.html
└── main.go


The logic for serving static files is in the static_handler function, which receives the request and checks if the path matches the static path. The check is done using the Asset function, automatically exported by the bindata.go user. If the resource does not exist, we return a 404, otherwise, we return the contents of the resource.


The rest of the code is for creating the web application and binding our static_handler template to match all incoming requests for /. If you have trouble understanding this code, check out the official Go documentation for http package.


As a quick reminder of how Go handles packages, all identifiers will be automatically exported to other packages with the same name if the first letter of the identifier name starts with a capital letter.


Based on this rule, the bindata.go file provides the Asset function for the main package. This loads and returns an asset for the given name. It returns an error if the resource cannot be found or cannot be loaded.