Static Files and Templates in Go
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.
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