Learning to Go: Part 3 - HTML Templates

Search for a command to run...

No comments yet. Be the first to comment.
Learn to write Go code by creating a basic web app that will provide a front-end for a public API
Oh hi! This is a multi-part tutorial series, please make sure you reads the posts in order! You can find the list at the bottom of the page. Now we’ve got a basic page up and running, in this step we’ll add the Search handler, which will actually dea...
Next up in my quest to Learn All The Things™ on the CNCF graduated projects page, we’re going to take a look at a lesser known Argo project: Argo Rollouts. In a nutshell, Argo Rollouts are a drop-in replacement for the Deployment object that provide ...

Continuing my efforts to explore and Learn All The Things™ on the CNCF graduated projects page, today I’m going to take a look at Argo Workflows, which is one of 4 tools in the Argo project overall (see my previous post on ArgoCD). Argo Workflows is ...

In my never-ending quest to Learn All The Things™, I’ve decided to take a tour through the Cloud Native Computing Foundation’s graduated projects page. I’m doing this for a few different reasons: Lea

This is the tenth post in a series exploring the features of GKE Enterprise, formerly known as Anthos. GKE Enterprise is an additional subscription service for GKE that adds configuration and policy management, service mesh and other features to supp...

This is the ninth post in a series exploring the features of GKE Enterprise, formerly known as Anthos. GKE Enterprise is an additional subscription service for GKE that adds configuration and policy management, service mesh and other features to supp...

Oh hi! This is a multi-part tutorial series, please make sure you reads the posts in order! You can find the list at the bottom of the page.
Welcome back fellow Go newbies! In this stage of our tutorial we’ll use a templating library to help us render more stylish HTML, without having to embed a bunch of HTML and CSS directly into our Go code.
As this is not an HTML or CSS tutorial, I’ve written these files for you. First copy the index.html file into the same directory as your main.go file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>TNG Search</title>
<link rel="stylesheet" href="/assets/style.css">
</head>
<body>
<main>
<header>
<a class="logo" href="/">TNG Search</a>
<form action="/search" method="GET">
<input autofocus class="search-input" value=""
placeholder="Enter a word" type="search" name="q">
</form>
</header>
</main>
</body>
</html>
Next, create a sub-directory called assets and put the following style.css file in there:
html {
box-sizing: border-box;
}
*, *::before, *::after {
box-sizing: inherit;
margin: 0;
padding: 0;
}
:root {
--operations: #FFC300;
--command: #900C3F;
}
body {
font-family: sans-serif;
}
a {
text-decoration: none;
color: #333;
}
a:hover {
text-decoration: underline;
}
header {
width: 100%;
height: 50px;
position: fixed;
top: 0;
left: 0;
right: 0;
display: flex;
justify-content: space-between;
background-color: var(--command);
padding: 5px 10px;
align-items: center;
}
.logo {
font-weight: bold;
font-size: 25px;
color: #ffffff;
}
form {
height: calc(100% - 10px);
}
.search-input {
width: 500px;
height: 100%;
border-radius: 4px;
border-color: transparent;
background-color: var(--operations);
color: #ffffff;
font-size: 16px;
line-height: 1.4;
padding-left: 5px;
}
view raw
Now we need to make some updates to our main.go file. First we need to add an extra import for the html/template library. Since we now have multiple imports, we can group them together inside brackets. Replace your previous import statement with this:
import ( "html/template" "net/http" )
Then under the imports, add this new line:
var tpl = template.Must(template.ParseFiles("index.html"))
Here we’re defining a global variable called tpl which uses the template library’s ParseFiles function to load our index.html. It wraps this in the Must function which just means that our program will exit if for any reason it can’t load this file.
Inside of our indexHandler function, removing line containing the “Hello World” statement and replace it with:
tpl.Execute(w, nil)
Here we’re running the Execute function on our tpl variable, and sending it the http.ResponseWriter that’s assigned to the variable w. In plain english, this causes the template to be rendered, and the output sent to the HTTP response.
We can try running our updated program now, just like before:
go run main.go
If we load http://localhost:8080 in a browser we can see our new index.html file, but it doesn’t look quite right. Our index.html file refers to /assets/style.css, but that’s not loading because our program forwards all requests to our indexHandler.
Rather than create a new handler for our CSS file, we’ll create a static file server that can serve anything out of our /assets directory by default. We do this by adding the following lines inside the main function, just after we create the mux variable:
fs := http.FileServer(http.Dir("assets"))
mux.Handle("/assets/", http.StripPrefix("/assets/", fs))
Here we’re creating a variable called fs which is a FileServer from the http library. Then we add it as a handler to our mux variable. So now any requests that start with /assets will be dealt with by the static file server, rather than needing their own handler and function.
If your Go program is still running, use Control+C to quit out of it, then run it again once you’ve saved your changes.
You should now see an updated index page, complete with CSS styling!

Nice work! We’re now using multiple libraries, and our app can serve different paths with different functions. Now move on to part 4.
Here’s the complete code of main.go if you need to check yours for errors:
package main
import (
"html/template"
"net/http"
)
var tpl = template.Must(template.ParseFiles("index.html"))
func indexHandler(w http.ResponseWriter, r *http.Request) {
tpl.Execute(w, nil)
}
func main() {
mux := http.NewServeMux()
fs := http.FileServer(http.Dir("assets"))
mux.Handle("/assets/", http.StripPrefix("/assets/", fs))
mux.HandleFunc("/", indexHandler)
http.ListenAndServe(":8080", mux)
}