Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
197: Auto-Escaping with html/template
If you've spent any time building web apps, you know that trusting user input is a recipe for disaster. The classic nightmare is Cross-Site Scripting (XSS), where a user injects a <script> tag into a form, and suddenly every other user visiting that page is executing arbitrary JavaScript. You could write a bunch of regex to scrub the input, but that's a losing battle. In Go, we have a much more elegant solution: the html/template package.
Building a simple profile renderer
Let's imagine we're building a basic user profile page. We have a User struct, and we want to display their name and a short bio. I'll start by defining a simple data structure and a template string that looks like standard HTML.
package main
import (
"os"
"text/template" // I'm starting here on purpose...
)
type User struct {
Name string
Bio string
}
func main() {
user := User{
Name: "Alice",
Bio: "I love Go! ",
}
tmplStr := `<div>
<h1>User: {{.Name}}</h1>
<p>Bio: {{.Bio}}</p>
</div>`
tmpl, _ := template.New("profile").Parse(tmplStr)
tmpl.Execute(os.Stdout, user)
}
Wait, I used the wrong package
I just ran the code above and noticed something terrifying. The output was exactly what I wrote in the Bio: <p>Bio: I love Go! <script>alert('Hacked!');</script></p>. If this were rendered in a browser, that alert box would pop up immediately.
My mistake was using text/template. While text/template is great for generating configuration files or emails, it has zero awareness of HTML. It treats everything as a raw string and just shoves it into the output. When you're dealing with the browser, this is a huge security hole.
Switching to contextual auto-escaping
The fix is simple: replace text/template with html/template. These two packages have nearly identical APIs, but html/template is "context-aware." It looks at where the variable is being placed—whether it's inside an HTML tag, an attribute, or a URL—and applies the appropriate escaping for that specific context.
package main
import (
"os"
"html/template" // Switched this from text/template
)
type User struct {
Name string
Bio string
}
func main() {
user := User{
Name: "Alice",
Bio: "I love Go! ",
}
tmplStr := `<div>
<h1>User: {{.Name}}</h1>
<p>Bio: {{.Bio}}</p>
</div>`
tmpl, _ := template.New("profile").Parse(tmplStr)
tmpl.Execute(os.Stdout, user)
}
Now, when I run this, the output changes to:
<div>
<h1>User: Alice</h1>
<p>Bio: I love Go! <script>alert('Hacked!');</script></p>
</div>
Notice how the < and > characters were converted to < and >. The browser will now display the script as literal text rather than executing it. This happens automatically. You don't have to call an EscapeHTML() function on every single variable; the package handles it based on the surrounding HTML.
Handling "Trusted" HTML
Every now and then, you'll actually want to render HTML—maybe you're using a Markdown parser that generates <b> or <i> tags. If you use html/template, those will be escaped too. To tell Go "I trust this specific string, don't escape it," you wrap the string in the template.HTML type.
If I change my user bio to template.HTML("I love Go!"), the package will see that type and leave the tags intact. Just be extremely careful with that; only use it on data you've already sanitized yourself.
📋 Practical Task
Exercise: Creating a Secure Comment Previewer
You are building a preview feature for a blog comment section. Users can enter a comment, and you want to show them a preview of how it will look on the page.
Create a program that does the following:
- Define a
Commentstruct with aUsernameand aTextfield. - Create a template that renders these into a
<div class="comment">, with the username in a<strong>tag and the text in a<span>. - Test your program with a "malicious" comment:
<img src="x" onerror="alert('XSS')">. - Ensure you are using the correct template package so that the output is safely escaped and would not execute the JavaScript in a browser.
- Print the final rendered HTML to the console.
There are no comments for now.