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.