A simple guide to build a web app with Go
This article aims to be a guide for building a web application completely in Go. I think the stack (introduced in the next section) is pleasant to work with and a breath of fresh air if you are coming from a JS background. We will be building a small "Hello World"-application, so it can be used a starting point for more complex applications.
The Stack
The main libraries we will be using are:
- Fuego - a modern web framework, which comes with free OpenAPI generation
- Templ - a HTML templating language, which allows to write Go code inside HTML
- Pico - a minimal CSS framework with zero dependencies
- htmx - an option to access AJAX directly in HTML
Building the App
Creating a Web Server with Fuego
Fuego is a Go web framework that is relatively new. It is based on the new Go 1.22 router
and supports OpenAPI generation from code out of the box. It is also compatible with net/http
from the standard
library, so that every middleware or handler for net/http
can be used with Fuego.
Some say the standard library is sufficient, but personally I like the structured approach of a framework a bit more. Another reason is the centralized error handling and the nice integration with Templ. So let´s get started and create a simple web server. The following is an example from the official documentation.
package main
import "github.com/go-fuego/fuego"
func main() {
s := fuego.NewServer()
fuego.Get(s, "/", func(c fuego.ContextNoBody) (string, error) {
return "Hello, World!", nil
})
s.Run()
}
As you can see, Fuego offers a Get
method for registering a route which accepts GET requests. Analogously, there exist
methods for all other HTTP operations like POST and DELETE. These routing methods always expect the server instance,
the path and a function, also called controller.
Controllers handle the requests and responses and can come in different types depending on what you expect and return. They
always expect a context (either with a body or without one) and return a value and an error.
For example, the controller above simply returns a string and does not expect any body.
Now we want to return some more complex HTML and not just a string. For creating such HTML one could use html/template
from the Go standard library. However, in this guide we will be using Templ. Templ allows
us to write Go Code inside a template and make use of Go´s type safety. This has the advantage
that you notice problems with your templates at compile time and not at runtime, which is especially handy for larger
applications.
Composing the Frontend with Templ
First, we need to install Templ, so we can compile our Templ components to Go code.
go install github.com/a-h/templ/cmd/templ@latest
Be sure to add the installed Templ binary to your path, so you can access it later from all locations. To add the dependency to your project, run
go get github.com/a-h/templ
At this point we are ready to create our first Templ component. Let´s skip the simple "Hello World" component and assume we want to build a site with a common element like a navigation. This is a little bit more interesting and shows the composition feature of Templ in action. Have a look at the following code:
import "appname/components"
templ Base(title string) {
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/static/css/styles.css" />
<title>{title}</title>
</head>
<body>
@components.Header()
<main class="container">
{ children... }
</main>
</body>
</html>
}
It demonstrates a few interesting things
- a Templ component gets defined like a normal Go function but with the
templ
keyword instead offunc
- it can take some data as an argument like the title
- a component can call other components, here it renders the Header component
- other components must be imported via the fully qualified path of the package
- rendering child components is possible via the
{children...}
tag - Templ code is written in a
*.templ
file
Now, we can use the base layout in all our pages, so the header stays on every page. In fact, the homepage of this very site is a Templ component:
templ Home() {
@Base("Nik´s HQ") {
<article>
<h2>Hello, friend.</h2>
<section>
My name is Niklas and I am a software engineer from Germany.
<br/>
This is the place where I share my thoughts on different topics, which are mainly tech
and programming related.
</section>
<blockquote>
“Only the disciplined are free in life.”
<footer>
<cite>— Eliud Kipchoge</cite>
</footer>
</blockquote>
</article>
}
}
The title is "Nik´s HQ" and everything between the curly braces represents the children and will be put inside the main tag.
As the last step we need to compile our templates to Go code to get the type safety. For that, run the command templ generate
in the root directory of your project. This will create a *_templ.go
file for all of your *.templ
files.
Connect Fuego with Templ
Currently, we have a simple Fuego web server and a pair of Templ components, so the next step is to put them together. Fuego comes with built-in support for Templ components. To render one, we can register a new route with a controller that returns the component.
fuego.Get(s, "/home", func(c fuego.ContextNoBody) (fuego.Templ, error) {
return Home(), nil
})
If our Templ component sits in the same Go package like our server, we can just call the component via its name. And
that´s it, navigating to /home
will render our Home component.
Summarized, the workflow from now on is to write a template, generate the go code, build the app and repeat.
Styling and static files
Every app needs some styling but for many people (mostly backend developers) writing CSS is also the most boring task. For that reason I especially like Pico, which brings in some nice defaults and is very minimalistic. To bring it in, we download the minified css of pico from their website and add the following line to our base layout:
<link rel="stylesheet" href="/static/css/pico.min.css" />
We are referring to a /static
folder, which isn´t yet handled by our web server. Therefore, we need to add a handler
for all the static content, that should be served. Go has a package in its standard library called
embed, which allows you to embed files into the Go program itself. The nice thing is that
the end result is still a single executable, so you can share and deploy it as easy as without the static files.
I like to have an embed.go
file inside the static
folder, which contains the handler:
//go:embed *
var static embed.FS
// StaticHandler returns a http.Handler that will serve files from
// the given file system.
func StaticHandler() http.Handler {
return http.FileServer(http.FS(static))
}
Then we can add the route to the Fuego server:
// register route for static files
fuego.Handle(server, "/static/", http.StripPrefix("/static", static.StaticHandler()))
The StripPrefix()
method is needed because our handler sits inside the static
folder, which would lead to a
/static/static
path.
At this point, the CSS should be loaded and applied to our templates.
Adding interactivity
To make it a well-rounded app and not just a static site, we can add some interactivity. In recent years htmx gained more and more popularity. Htmx allows you to make AJAX calls directly from HTML without using JavaScript. For not so interaction-heavy apps, this is completely adequate and most of the time you just don´t need React or any other JavaScript Framework. It is also very easy to install, you only need to add a single script tag in the header, and you are done.
<script src="/static/htmx.min.js"></script>
Possible Project Structure
Sooner or later the app will grow and often the question after the best project structure arises. My advice is to take the last section of this official Go blog post as an orientation. It is a really simple structure, which does not overcomplicate things.
Also, the example project of Fuego can be a nice starting point. It follows a more structured MVC-like approach, which can help with bigger apps.
Next Steps
In the end, here are some thoughts on what could possibly be added:
- A database is clearly necessary if you want to persist some data. I would recommend to start with SQLite, as it is often enough and easy to use. You could also combine it with Sqlc, which lets you compile your SQL queries to type safe Go code.
- If you need more interactivity in your app, have a look at Alpine. Alpine is a minimalistic JavaScript framework, which comes with just enough components to get most things done.
- Use more features of Fuego. For APIs Fuego can generate the OpenAPI documentation directly from your code. It also comes with a generator, to quickly generate all CRUD operations for you and much more.
This concludes my guide and I hope you can get something out of it.
Bye for now :)
Niklas
Published on: 18. May 2024
Last edit: 22. August 2024
Back