GOTTH for people in a rush

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.