Serving static files and rendering HTML templates are essential for web applications. Go’s html/template package provides powerful templating capabilities, and the standard library makes serving static files straightforward. This guide covers both. For more context, see Go Installation Guide, Go Ecosystem Overview, Go Best Practices.
Serving Static Files
Basic Static File Serving
package main
import (
"log"
"net/http"
)
func main() {
// Serve files from public directory
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("public"))))
log.Fatal(http.ListenAndServe(":8080", nil))
}
Custom File Server
package main
import (
"log"
"net/http"
"path/filepath"
)
func main() {
// Serve specific file types
http.HandleFunc("/css/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/css")
http.ServeFile(w, r, filepath.Join("public", r.URL.Path))
})
http.HandleFunc("/js/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/javascript")
http.ServeFile(w, r, filepath.Join("public", r.URL.Path))
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
Serving with Cache Headers
package main
import (
"log"
"net/http"
"time"
)
func serveStaticWithCache(w http.ResponseWriter, r *http.Request) {
// Set cache headers
w.Header().Set("Cache-Control", "public, max-age=3600")
w.Header().Set("ETag", "\"123456\"")
// Serve file
http.ServeFile(w, r, "public"+r.URL.Path)
}
func main() {
http.HandleFunc("/static/", serveStaticWithCache)
log.Fatal(http.ListenAndServe(":8080", nil))
}
HTML Templates
Basic Template
package main
import (
"fmt"
"log"
"net/http"
"text/template"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles("template.html")
if err != nil {
log.Fatal(err)
}
data := map[string]string{
"Title": "Hello",
"Name": "World",
}
tmpl.Execute(w, data)
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
Template File (template.html)
<!DOCTYPE html>
<html>
<head>
<title>{{.Title}}</title>
</head>
<body>
<h1>{{.Title}}, {{.Name}}!</h1>
</body>
</html>
Template with Loops
package main
import (
"log"
"net/http"
"text/template"
)
type User struct {
Name string
Email string
}
func main() {
http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles("users.html")
if err != nil {
log.Fatal(err)
}
users := []User{
{"Alice", "[email protected]"},
{"Bob", "[email protected]"},
{"Charlie", "[email protected]"},
}
tmpl.Execute(w, users)
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
Users Template (users.html)
<!DOCTYPE html>
<html>
<head>
<title>Users</title>
</head>
<body>
<h1>Users</h1>
<ul>
{{range .}}
<li>{{.Name}} - {{.Email}}</li>
{{end}}
</ul>
</body>
</html>
Template with Conditionals
package main
import (
"log"
"net/http"
"text/template"
)
type Product struct {
Name string
Price float64
Stock int
}
func main() {
http.HandleFunc("/product", func(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles("product.html")
if err != nil {
log.Fatal(err)
}
product := Product{
Name: "Laptop",
Price: 999.99,
Stock: 5,
}
tmpl.Execute(w, product)
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
Product Template (product.html)
<!DOCTYPE html>
<html>
<body>
<h1>{{.Name}}</h1>
<p>Price: ${{.Price}}</p>
{{if gt .Stock 0}}
<p>In stock: {{.Stock}} units</p>
<button>Add to Cart</button>
{{else}}
<p>Out of stock</p>
{{end}}
</body>
</html>
Advanced Template Features
Template Functions
package main
import (
"log"
"net/http"
"strings"
"text/template"
)
func main() {
funcMap := template.FuncMap{
"upper": strings.ToUpper,
"lower": strings.ToLower,
"title": strings.Title,
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.New("test").Funcs(funcMap).ParseFiles("template.html")
if err != nil {
log.Fatal(err)
}
data := map[string]string{"Text": "hello world"}
tmpl.Execute(w, data)
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
Template with Custom Functions
<h1>{{upper .Text}}</h1>
<p>{{title .Text}}</p>
Nested Templates
package main
import (
"log"
"net/http"
"text/template"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Parse multiple templates
tmpl, err := template.ParseFiles(
"layout.html",
"header.html",
"footer.html",
)
if err != nil {
log.Fatal(err)
}
tmpl.ExecuteTemplate(w, "layout.html", nil)
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
Layout Template (layout.html)
<!DOCTYPE html>
<html>
<head>
<title>My Site</title>
</head>
<body>
{{template "header.html"}}
<main>
<h1>Welcome</h1>
</main>
{{template "footer.html"}}
</body>
</html>
Practical Examples
Blog Post Template
package main
import (
"log"
"net/http"
"text/template"
"time"
)
type Post struct {
Title string
Content string
Author string
Date time.Time
Tags []string
}
func main() {
http.HandleFunc("/post", func(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles("post.html")
if err != nil {
log.Fatal(err)
}
post := Post{
Title: "Getting Started with Go",
Content: "Go is a great language...",
Author: "John Doe",
Date: time.Now(),
Tags: []string{"go", "programming", "tutorial"},
}
tmpl.Execute(w, post)
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
Post Template (post.html)
<!DOCTYPE html>
<html>
<head>
<title>{{.Title}}</title>
</head>
<body>
<article>
<h1>{{.Title}}</h1>
<p>By {{.Author}} on {{.Date.Format "2006-01-02"}}</p>
<div class="tags">
{{range .Tags}}
<span class="tag">{{.}}</span>
{{end}}
</div>
<div class="content">
{{.Content}}
</div>
</article>
</body>
</html>
Form Template
package main
import (
"log"
"net/http"
"text/template"
)
type FormData struct {
Name string
Email string
Message string
}
func main() {
http.HandleFunc("/form", func(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles("form.html")
if err != nil {
log.Fatal(err)
}
tmpl.Execute(w, nil)
})
http.HandleFunc("/submit", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
data := FormData{
Name: r.FormValue("name"),
Email: r.FormValue("email"),
Message: r.FormValue("message"),
}
tmpl, _ := template.ParseFiles("success.html")
tmpl.Execute(w, data)
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
Form Template (form.html)
<!DOCTYPE html>
<html>
<body>
<form method="POST" action="/submit">
<input type="text" name="name" placeholder="Name" required>
<input type="email" name="email" placeholder="Email" required>
<textarea name="message" placeholder="Message" required></textarea>
<button type="submit">Submit</button>
</form>
</body>
</html>
Best Practices
✅ Good Practices
// Cache parsed templates
var templates = template.Must(template.ParseFiles("template.html"))
// Use template.Must for safety
tmpl := template.Must(template.ParseFiles("file.html"))
// Escape user input automatically
// html/template escapes by default
// Use named templates for organization
tmpl.ExecuteTemplate(w, "layout", data)
// Separate concerns
// Keep templates in separate files
❌ Anti-Patterns
// Don't parse templates on every request
tmpl, _ := template.ParseFiles("file.html") // Inefficient
// Don't ignore template errors
tmpl.Execute(w, data) // Should check error
// Don't use text/template for HTML
// Use html/template for automatic escaping
// Don't mix logic in templates
// Keep templates simple
Resources
- Go html/template Documentation
- Go text/template Documentation
- Template Actions
- HTML Template Security
Summary
Go’s template system provides powerful tools for web development:
- Use
html/templatefor HTML output (auto-escaping) - Cache parsed templates for performance
- Use template functions for custom logic
- Organize templates with nested templates
- Keep templates simple and focused
With these tools, you can build dynamic, secure web applications in Go.
Comments