Skip to main content
โšก Calmops

Static Files and Templates in Go

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

Summary

Go’s template system provides powerful tools for web development:

  • Use html/template for 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