Skip to Content
Course content

195: Rendering Templates with text/template

Click on the "Edit" button in the top corner of the screen to edit your slide content.

When you need to generate dynamic text—like a personalized email, a configuration file, or a CLI report—you don't want to be manually concatenating strings with fmt.Sprintf. It gets messy fast. That's where text/template comes in. It lets you define a blueprint of your text and then plug in data at runtime.

To see this in action, let's build a simple notification generator for a task management app. We want to send a user a summary of their pending tasks.

Defining our notification data

First, we need a structure for the data we're passing into the template. I'm going to create a User struct and a Task struct. The key here is that the template engine needs to be able to "see" these fields, so they must be exported.

type Task struct {
    Description string
    DueDate     string
}

type User struct {
    Name  string
    Tasks []Task
}

Crafting the template blueprint

Now, let's write the template. In Go, templates are just strings (or files) containing "actions" wrapped in double curly braces {{}}. The dot . represents the current data object being passed in.

const emailTmpl = `Hi {{.Name}},

You have the following tasks pending:
{{range .Tasks}}- {{.Description}} (Due: {{.DueDate}})
{{end}}

Keep up the great work!`

I used the {{range}} action here. It's basically a for loop inside the template. While inside that range block, the dot . shifts its context from the User to the individual Task.

The "blank space" bug

I'll be honest: every time I start a new project, I occasionally forget one specific Go rule and end up staring at my output wondering why my variables are empty. Let me show you what I mean. I initially wrote my struct like this:

type User struct {
    name  string // lowercase 'n'
    Tasks []Task
}

When I ran the code, the output was: Hi , You have the following tasks.... The name was missing entirely. Why? Because text/template uses reflection to access fields, and it can't access unexported (lowercase) fields. I had to change name to Name to make it visible to the template engine. Always double-check your capitalization when your templates come back blank.

Executing the render

To actually get the text, we have to parse the template string and then execute it against our data. We'll use os.Stdout as the destination so we can see the result in the console.

func main() {
    user := User{
        Name: "Alice",
        Tasks: []Task{
            {"Finish report", "Tomorrow"},
            {"Email the client", "Friday"},
        },
    }

    // Create a new template and parse the string
    tmpl, err := template.New("notification").Parse(emailTmpl)
    if err != nil {
        panic(err)
    }

    // Execute the template, passing in the user data
    err = tmpl.Execute(os.Stdout, user)
    if err != nil {
        panic(err)
    }
}

I used template.New("notification") to give the template a name. This doesn't affect the output, but it's incredibly helpful for debugging when you start using multiple associated templates. The Execute method does the heavy lifting, merging the user object into the emailTmpl blueprint.




📋 Practical Task

Build a Server Status Report Generator

Create a program that uses text/template to generate a system status report. Your program should:

  • Define a Server struct with fields for Hostname, IPAddress, and Status (e.g., "Online" or "Offline").
  • Define a Report struct that contains a slice of Server objects and a GeneratedBy string (the admin's name).
  • Write a template that greets the admin and lists each server's hostname and status in a list format.
  • Ensure all necessary fields are exported so the template can render them.
  • Execute the template and print the final report to the console.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.