DEV Community

Clavin June
Clavin June

Posted on • Originally published at clavinjune.dev on

Serving Embedded Static File Inside Subdirectory Using Go

Photo by @iammrcup on Unsplash

Introduction

As embed package released on Golang v1.16, now you can include any of your files into your golang binary. You can also serve the embedded file as a static site. But when it comes to subdirectory, you need to handle it a bit different.

In this article, you will learn how to serve embedded files as a static site that stored inside a subdirectory.

Directory Structure

$ tree .
.
├── frontend
│   └── public
│       ├── assets
│       │   ├── css
│       │   │   └── style.css
│       │   └── js
│       │       └── index.js
│       └── index.html
├── go.mod
└── main.go
Enter fullscreen mode Exit fullscreen mode

HTML Content

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
    <script src="/assets/js/index.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

To test if the assets can be imported using absolute and relative URL, you can write the link rel and script src like the snippet above. You can put anything inside your style.css and index.js just to make sure it is loaded.

The Problem

Before embed package is released, you used to use http.Dir to serve a static site using Go like below:

package main

import (
    "net/http"
)

func dirHandler() http.Handler {
    return http.FileServer(http.Dir("frontend/public"))
}

func main() {
    http.ListenAndServe(":8000", dirHandler())
}
Enter fullscreen mode Exit fullscreen mode

And yes, it works like a charm. But, when it comes to embed:

package main

import (
    "embed"
    "net/http"
)

//go:embed frontend/public
var public embed.FS

func fsHandler() http.Handler {
    return http.FileServer(http.FS(public))
}

func main() {
    http.ListenAndServe(":8000", fsHandler())
}
Enter fullscreen mode Exit fullscreen mode

It doesn't work because you didn't specified which subdirectory will be served. http.FS will serve the embedded files from root, that's why instead of http://localhost:8000/, you need to access it using http://localhost:8000/frontend/public/.

But the other problem is, it makes your absolute import not working. /assets/js/index.js will not work because it accesses the file from the root.

The Solution

To solve those problems, you can use fs.Sub to get the subtree of the embedded files. So you can start get the frontend/public directory as a root.

//go:embed frontend/public
var public embed.FS

func fsHandler() http.Handler {
    sub, err := fs.Sub(public, "frontend/public")
    if err != nil {
        panic(err)
    }

    return http.FileServer(http.FS(sub))
}
Enter fullscreen mode Exit fullscreen mode

Now, you can access the site using http://localhost:8000/ and both your relative and absolute import works again!

Thank you for reading!

Top comments (0)