Beginner: Working with Templating
Alright, so now you've got a web server and a way to write
to the browser. Now, it's time to start sending over some
.html.
In this tutorial, we’ll explore different ways to use Go’s
html/template
package alongside
net/http
to serve dynamic HTML pages. Templating allows you to
separate your Go code from your front-end structure, making
your application easier to maintain and extend.
1. Serving a Single Template
The simplest scenario is serving a single HTML file as a
template. Suppose you have an index.html in
your project’s root or a templates directory:
// single_template.go package main import ( "log" "net/http" "html/template" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { tmpl, err := template.ParseFiles("index.html") if err != nil { log.Fatal(err) } tmpl.Execute(w, nil) }) http.ListenAndServe(":8080", nil) }
Note how we're using the walrus operator to get the value to
the parsed template, but also the error.
Note I use log fatal and panic(err) a lot, but there are
better ways to handle errors in Go. For example, if the
template doesn't parse, you could catch it and instead
redirect the user to a "440: resource not found" page.
Whenever a user visits
http://localhost:8080, the code above parses index.html and writes
it to the response. You can pass data by replacing
nil with a struct or map.
Here’s a very basic index.html that you might
serve:
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Single Template Example</title>
</head>
<body style="margin: 0; padding: 2rem; font-family: sans-serif;">
<h1>Hello from a single template!</h1>
<p>This is a simple static HTML page.</p>
</body>
</html>
2. Multiple Templates with ParseFiles
If you have multiple templates (e.g.,
home.html and about.html), you can
parse them together, then choose which to execute based on
the route or content you’re serving:
// multiple_templates.go
package main
import (
"log"
"net/http"
"html/template"
)
var tmpl *template.Template
func init() {
var err error
tmpl, err = template.ParseFiles("home.html", "about.html")
if err != nil {
log.Fatal(err)
}
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
tmpl.ExecuteTemplate(w, "home.html", nil)
}
func aboutHandler(w http.ResponseWriter, r *http.Request) {
tmpl.ExecuteTemplate(w, "about.html", nil)
}
func main() {
http.HandleFunc("/", homeHandler)
http.HandleFunc("/about", aboutHandler)
http.ListenAndServe(":8080", nil)
}
Below are minimal examples of home.html and
about.html to demonstrate how you might
structure them. Notice how each file includes an
<title> or <h1> that
differs:
<!-- home.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Home Page</title>
</head>
<body>
<h1>Welcome to the Home Page!</h1>
</body>
</html>
<!-- about.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>About Page</title>
</head>
<body>
<h1>Learn more about us here.</h1>
</body>
</html>
3. Using ParseGlob for an Entire Folder
If you keep many templates in a dedicated directory,
ParseGlob
can load them all in one go. For example:
// parse_glob.go
package main
import (
"log"
"net/http"
"html/template"
)
var tmpl *template.Template
func init() {
var err error
tmpl, err = template.ParseGlob("templates/*.html")
if err != nil {
log.Fatal(err)
}
}
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
tmpl.ExecuteTemplate(w, "home.html", nil)
})
http.HandleFunc("/about", func(w http.ResponseWriter, r *http.Request) {
tmpl.ExecuteTemplate(w, "about.html", nil)
})
http.ListenAndServe(":8080", nil)
}
You might place home.html and
about.html inside a
templates/ folder. The code above will parse
everything with a .html extension in that
folder.
4. Passing Data to Templates
Templates really shine when you need to inject dynamic data from Go. For instance, you can define a struct and pass it to your template:
// data.go
package main
import (
"log"
"net/http"
"html/template"
)
type PageData struct {
Title string
Message string
}
var tmpl *template.Template
func init() {
var err error
tmpl, err = template.ParseFiles("home.html")
if err != nil {
log.Fatal(err)
}
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
data := PageData{
Title: "Hello from Go Templates!",
Message: "This is a dynamic message passed from the server.",
}
tmpl.Execute(w, data)
}
func main() {
http.HandleFunc("/", homeHandler)
http.ListenAndServe(":8080", nil)
}
In home.html, you can reference
something like {{ .Title}} and
{{.Message}} for dynamic content:
<!-- home.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{.Title}}</title>
</head>
<body>
<h1>{{.Title}}</h1>
<p>{{.Message}}</p>
</body>
</html>
This approach allows your Go code to supply variables, slices, maps, or entire structs, and have them rendered neatly in your HTML files.