How to use go:embed in Golang
The embed package is a compiler command directive that allows a program to include all sorts of files and folders in the Go binary during compiling. The go:embed method authorizes us to embed the contents of a file into a defined variable directly. This method allows Golang to read the file’s content while compiling. The embed package is only available in Go 1.16 and later. To check the Go version, we can use the following Go command:
go version
How does go:embed work?
The go:embed compiler directive and the embed package, together, include static files in a Go binary. It is done by putting the //go:embed comment above the declaration of the variable used for embedding.
//go:embed file_namevar fileName embed.FS
By this command, the compiler will search for a file with the specified name the file_name, and include its assets in the resulting binary. The declared variable the fileName after the go:embed directive will act as a container for the contents embedded. The variable can be of different types such as the string, []byte, or embed.FS, depending on how we want the embedded content to be handled. Through the following table, we can check which variable type should be used:
Variable Type | Access | Modifications | When to Use? | Example |
| The variable holds the embedded content as a string | In the Go application, resources embedded as | When a single file such as configuration data, a small template, or a piece of text needs to be embedded |
|
| The variable ends up as a slice of bytes containing the embedded content | The content of the | When individual files, especially binary files like images, fonts, or other non-text data, need to be embedded |
|
| The variable can be accessed as a part of the file system | In the Go application, resources embedded using the | When multiple files or an entire directory needs to be embedded as a read-only file system |
|
Embed a text file
Here, we’ll embed contents of a text file into a []byte. The file below is a text file named the test.txt. In this text file, we have inserted a list of things, as mentioned below:
List of thingsAppleBananaTableChair
Here is the Golang program that we will use to embed the text file:
package mainimport (_ "embed""fmt")var (//go:embed test.txttext_file []byte)func main() {fmt.Println(string(text_file))}
Code explanation
Line 1: The
package mainspecifies that the Golang program has started, and the code written after this line is a part of thepackage main.Lines 2–5: This shows import of necessary packages. Here, the
fmtpackage is imported for printing, and the_ "embed". The_before the"embed"is to tell the Go compiler that the package is being imported for initializing embedded content, instead of directly using it in code.Lines 6–9: A variable named the
text_fileis declared which is of type[]byte. The comment//go:embed test.txtis a command that instructs the Go compiler to embed contents of thetest.txtinto thetext_filevariable during compilation.Lines 10–12: In the
mainfunction, thefmt.Println()function is used to print the content of thetext_fileandstring(text_file)to convert byte slice to a string before printing it.
Embed static files
We can embed static files into a binary of a web application using the go:embed method. Here is an HTML file named the index.html that we will be embedding:
<!DOCTYPE html><html><head><title>Sample HTML Page</title></head><body><h1>Hello, World!</h1><p>Here is a sample static file embedded using Golang!</p></body></html>
The main.go file that we will use to embed a static file is as follows:
package mainimport ("embed""fmt""log""net/http""os""strings")//go:embed publicvar static_files embed.FSvar staticDir = "public"func rootPath(h http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {w.Header().Set("Cache-Control", "no-cache")if r.URL.Path == "/" {r.URL.Path = fmt.Sprintf("/%s/", staticDir)}else {b := strings.Split(r.URL.Path, "/")[0]if b != staticDir {r.URL.Path = fmt.Sprintf("/%s%s", staticDir, r.URL.Path)}}h.ServeHTTP(w, r)})}func main() {var staticFS = http.FS(static_files)fs := rootPath(http.FileServer(staticFS))http.Handle("/", fs)port := os.Getenv("PORT")if port == "" {port = "3000"}log.Printf("Listening on :%s...\n", port)err := http.ListenAndServe(fmt.Sprintf(":%s", port), nil)if err != nil {log.Fatal(err)}}
Code explanation
Lines 10–11: The
//go:embeddirective indicates thatpublicdirectory should be embedded to thestatic_filesvariable using theembed.FStype. It will be embedded during compilation and can be accessed via thestatic_files.Line 13: The
staticDirvariable is declared with valuepublic, which is the directory that will be used to serve static files.Line 15: The
func rootPath(h http.Handler) http.Handlerintroduces therootPath()function, which is middleware for processing incoming HTTP requests to ensure proper URL paths. It takes thehttp.Handleras an argument and returns anhttp.Handler.Line 16: The
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request)defines an anonymous function that takesw(thehttp.ResponseWriter) andr(thehttp.Request) as parameters.Line 17: The
w.Header().Set("Cache-Control", "no-cache")sets theCache-Controlheader in the response to the"no-cache", which is used to prevent the caching of static files.Lines 19–21: This block of code checks if the URL path is
/. If the URL path is/, it will redirect the request by modifying the URL path to the"/{staticDir}/". This will effectively redirecting requests to thestaticDirroute (e.g., from/to/public/).Lines 22–23: The
b := strings.Split(r.URL.Path, "/")[0]is executed if the URL path is not/. It checks if the first part of the URL path (before the first/) is not equal to thestaticDir.Lines 24–27: If the first part of the URL path is not equal to the
staticDir, it adds thestaticDiras a prefix to the URL path. This is done to ensure that requests are directed to the proper paths within thestaticDir.Line 28: The modified request
r, along with response writerw, is passed to the next HTTP handlerhfor further processing.Lines 29–30: The anonymous function is closed and the
rootPathmiddleware function is returned.Lines 32–49: In the
main()function, thestatic_filesvariable is converted intohttp.FStype and used to create anhttp.FileServer. TherootPathmiddleware is applied to the file server for it to handle URL path modifications. The root path is handled by thefsfile server, and is set up to listen on the specified port inPORT. If thePORTenvironment variable is not set, it defaults to port 3000. The server is started using thehttp.ListenAndServe(), and any errors are logged using thelog.Fatal()function.
Note: We can embed multiple static files in the same way.
To practice hosting embedded static files, press "Run" below and access the webpage on the given link.
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML Page</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<h1>Hello World</h1>
<p>Here is a sample static file embedded using Golang!</p>
</body>
</html>Advantages of the go:embed
We can find several advantages of the go:embed in the following table:
Simplicity: The
go:embedsimplifies embedding static assets, configuration files, and other resources into the Golang code.Type safety: The
go:embedprovides type safety, ensuring that the embedded files are of the expected type (e.g.,string,[]byte, orembed.FS). This helps prevent runtime errors related to type mismatches.Performance: Accessing embedded resources is often faster than reading files from disk, especially in scenarios where there are many small assets. This can improve the overall performance of the Go application.
Maintainability: Embedding resources in the Go code makes managing and versioning resources alongside source code easier. It simplifies the development workflow and reduces the risk of missing or mismatched assets.
Limitations of the go:embed
No dynamic embedding: The
go:embedrequires specifying the files or directories to embed at compile-time. We cannot dynamically embed files based on runtime conditions.Read-only resources: Embedded resources are typically read-only within the application. While this benefits security and consistency, the embedded resources cannot be modified at runtime.
File size: Embedding large files or directories can significantly increase the size of the Go binary, potentially impacting startup times and memory usage.
No encryption: Embedded resources are not automatically encrypted or obfuscated. If they contain sensitive data, additional steps need to be taken to secure them.
Conclusion
Despite the limitations, the go:embed remains a convenient and resilient feature for embedding static files and resources in Go applications, especially for web development.
Free Resources