Doing Frontend Development with Golang, JavaScript and Docker (Part The First)

Rob Thorne
8 min readJul 25, 2022

--

A white rabbit eagerly awaits mention of Monty Python and the Holy Grail
Photo by Satyabratasm on Unsplash

This series:

Go (aka: Golang) is Google’s general purpose programming language. It’s very popular, easy to code, and used a lot in backend web development. Go is especially good for coding web servers, since Go programs are extremely performant, so much so that you have much less need for web server software like Nginx or Apache.

Still, it’s not perfect. Nowadays, most web applications combine HTML, JavaScript and a variety of CSS libraries. Many of these libraries work best if there’s some kind of preprocessing step that converts files that are easy and convenient for developers to write and maintain, into files that are easy and convenient for your browser to process and display. Go, on the other hand, wants you to put your display data into its templating system. You can put easily put raw JavaScript or raw CSS into your templates, but that is the limit of it. Modern front end development tools like Typescript or SASS don’t really fit into this paradigm.

How do you use modern CSS and JavaScript tooling with Go?

This series of articles will show you how to use Vite 3, a JavaScript and web asset processing and bundling tool to teach the Go gopher a few new tricks. The Vite web site states:

“…as we build more and more ambitious applications, the amount of JavaScript we are dealing with is also increasing dramatically. It is not uncommon for large scale projects to contain thousands of modules. We are starting to hit a performance bottleneck for JavaScript based tooling: it can often take an unreasonably long wait (sometimes up to minutes!) to spin up a dev server, and even with Hot Module Replacement (HMR), file edits can take a couple of seconds to be reflected in the browser. The slow feedback loop can greatly affect developers’ productivity and happiness.

“Vite aims to address these issues by leveraging new advancements in the ecosystem: the availability of native ES modules in the browser, and the rise of JavaScript tools written in compile-to-native languages.”

We’re going to walk though the process of setting up a Vite-based workflow for Go using Docker and SSL. This will take a few articles to describe the whole process. This article will start us out by getting a Go web application talking to Vite. We’ll pull JavaScript and CSS out of the templates, and move them into a linked project that Vite manages for us. Let’s do a case study.

Introducing… The Join Our Quest App

Arthur Pendragon, a 5th century Briton and aspiring monarch, is recruiting knights for an exciting religious and spiritual opportunity: find an ancient drinking cup. His options for publicity are limited (it being the 5th century and all), so he decides to create a web app.

Screen capture of the main page for the Join Arthur app, with questions you might have heard from that Holy Grail movie.
You knew the image was a rabbit for a reason, right?

So far, it’s all back end work, with just a bit of Bootstrap 5 to make the form look neat. You can find the code up on GitHub, here. This being a demo, we won’t do too much validation or other processing in Go. Let’s assume that we will add that to Arthur’s app, but that’s beyond the scope of this article. What we will do is add JavaScript client side validation to the app.

Typically, you’d do that by adding the JavaScript into the template for this page. Right now, our setup looks like this:

├── README.md
├── go.mod
├── go.sum
├── main.go
├── templates
│ ├── base.layout.gotmpl
│ ├── quest.page.gotmpl
│ └── result.page.gotmpl

It’s a pretty simple app, as I’ve said. To get started converting over to Vite, let’s create a Vite project at the top level of the work directory; we’ll call it “frontend”. You can add pre-populated projects for Vue, React, Svelte, and more, with options to use or not use Typescript. I’m going to choose to create a plain project using Typescript:

$ npm create vite@latest
✔ Project name: … frontend
✔ Select a framework: › vanilla
? Select a variant: › - Use arrow-keys. Return to submit.
vanilla
❯ vanilla-ts

I choose “vanilla-ts”, and I now have a “frontend” directory next to my main.go file. Let’s see what Vite created for us:

$ cd frontend/
$ npm install

added 16 packages, and audited 17 packages in 3s

4 packages are looking for funding
run `npm fund` for details

found 0 vulnerabilities
$ npm run dev

and Vite starts serving HTTP to us on http://localhost:5173/:

Screen capture of the Vite 3.0 “I’m working here” screen
Vite dev server at your service

The page is simple, and looks like this:

Screen capture of a very simple web page mentioning Vite and Typescript
Standard page for Vite vanilla typescript sample page

The actual html is even more simple. It looks like this:

<!DOCTYPE html>
<html lang="en">
<head>
<script type="module" src="/@vite/client"></script>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

We won’t need this page or its formatting, but behind the scenes, Vite has added two script tags to the index page that loads the Vite hot loader (“HMR”, for “hot module replacement”). The HMR system in turn loads processed javascript into the page, along with CSS. This we do want. In order to get this loading w/o the HTML itself, we need to get our Go app to do the same thing for us.

This is what a Vite integration does: it adds tags to your web pages to bootstrap the loading process. I wrote vite-go, which does this for Golang apps. We first need to install the package, and then call the library in our Go code.

To install the module:

$ go get github.com/torenware/vite-go

Then we add code like this to our sample app. The code looks like this. In imports():

import (
// lots of stuff omitted...
vueglue "github.com/torenware/vite-go"
)

and add code like this to func main():

// We put our project in the frontend directory:
projDir := os.DirFS("frontend")
config := vueglue.ViteConfig{
Environment: "development",
FS: projDir,
}
// Initialize our Vite library
glue, err := vueglue.NewVueGlue(&config)
if err != nil {
log.Panicf("could not initialize vite library: %v", err)
}

The actual configuration is very brief, since vite-go can guess what your project likely looks like. If you use a stock project as I do here, it will very likely get it right. If you’re using custom settings, you may need to add more configuration; see the vite-go docs for details.

We pass the “glue” object to Go’s templating system, and then we modify the templates to render our Vite tags into the page:

In our render routine:

if data == nil {
data = map[string]any{
"vite": viteLib,
}
} else {
data["vite"] = viteLib
}
ts, err := template.ParseFiles(templateList...)
if err != nil {
log.Println(err.Error())
http.Error(w, "Internal Server Error", 500)
return
}
err = ts.Execute(w, data)
if err != nil {
log.Println(err.Error())
http.Error(w, "Internal Server Error", 500)
}
}

and in the template:

{{ $vite := .vite }}
{{ if $vite }}
{{ $vite.RenderTags }}
{{ end }}

Once we rebuild the Go app and restart it, this should be enough to get the correct tags rendered into our page. Make sure that Vite is still running on 5173; if you look at the network tab of your browser tools, you should see something like this:

Screen capture of Firefox dev tools when a Vite enabled application loads.
Browser tools network tab if Vite is actually working

Note that several items have loaded from the Vite server at 5173, including the rather odd blue item with code “101”. This last item is the HMR websocket that will be used to load newly changed and processed items. From this, you know that vite-go’s integration code is working.

Adding JavaScript and CSS

Now that Vite is hooked up, we can start directly on our front end development. The first step is to delete things you won’t need in the stock code in “frontend”. In particular, you should delete all of the CSS in “style.css”, and you can delete essentially everything in “src/main.ts”. This is where you will do most of your work. Once you’ve done this, your IDE should show you which items in the directory you no longer need.

I also add a (currently optional) Vite config file, vite.config.ts. This has one setting that isn’t even used for the dev server; the build.manifest setting is used for production builds. We’ll add more to this file as we continue the series.

I added client side validation code in main.ts, and corresponding CSS to style.css. Note that the code is in Typescript, which would not work in a Go template. Vite allows this by noticing when I change the code, compiling the Typescript into JavaScript, and delivering it up to the browser via the websocket. Pretty much automatically.

This is plenty to look over for a first pass. Since I like showing how things hook together, here’s what our network looks like right now when we run the current version of the app:

Network diagram showing the app, Vite and theirconnections to the browser
The app running directly on your personal computer

This looks simple, and is. Everything is running on your local computer’s network, with Go sending HTTP on port 80, and the Vite dev server operates both its asset serving and the HMR websocket (protocol “ws”) on port 5173 (more on that in a later installment). And now that we have this working locally, we will create docker containers for the app and for Vite in the next installment, and move our work load into Docker.

Rob Thorne is a full-stack developer who’s doing more and more devops these days. He’s available for hire. You can find Rob on Twitter as @torenware.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Rob Thorne
Rob Thorne

Written by Rob Thorne

Full stack engineer. DevOps. Kubernetes.

No responses yet

Write a response