Template Integration
Integrate Go’s template engine with Goa to render dynamic HTML content, including template composition, data passing, and proper error handling.
Goa services can render dynamic HTML content using Go’s standard html/template
package. This guide shows you how to integrate template rendering into your Goa service.
Design
First, define the service endpoints that will render HTML templates:
package design import . "goa.design/goa/v3/dsl" var _ = Service("front", func() { Description("Front-end web service with template rendering") Method("home", func() { Description("Render the home page") Payload(func() { Field(1, "name", String, "Name to display on homepage") Required("name") }) Result(Bytes) HTTP(func() { GET("/") Response(StatusOK, func() { ContentType("text/html") }) }) }) })
Implementation
Service Structure
Create a service that manages template rendering:
package front import ( "context" "embed" "html/template" "bytes" "fmt" genfront "myapp/gen/front" // replace with your generated package name ) //go:embed templates/*.html var templateFS embed.FS type Service struct { tmpl *template.Template } func New() (*Service, error) { tmpl, err := template.ParseFS(templateFS, "templates/*.html") if err != nil { return nil, fmt.Errorf("failed to parse templates: %w", err) } return &Service{tmpl: tmpl}, nil }
Template Rendering
Implement the service methods to render templates:
func (svc *Service) Home(ctx context.Context, p *genfront.HomePayload) ([]byte, error) { // Prepare data for the template data := map[string]interface{}{ "Title": "Welcome", "Content": "Welcome to " + p.Name + "!", } // Create a buffer to store the rendered template var buf bytes.Buffer // Render the template to the buffer if err := svc.tmpl.ExecuteTemplate(&buf, "home.html", data); err != nil { return nil, fmt.Errorf("failed to render template: %w", err) } return buf.Bytes(), nil }
Template Structure
Create your HTML templates in the templates
directory:
<!-- templates/base.html --> <!DOCTYPE html> <html> <head> <title>{{.Title}}</title> </head> <body> {{block "content" .}}{{end}} </body> </html> <!-- templates/home.html --> {{template "base.html" .}} {{define "content"}} <h1>{{.Title}}</h1> <p>{{.Content}}</p> {{end}}
Server Setup
Set up your server with the template service:
func main() { // Create the service svc := front.New() // Initialize HTTP server endpoints := genfront.NewEndpoints(svc) mux := goahttp.NewMuxer() server := genserver.New(endpoints, mux, goahttp.RequestDecoder, goahttp.ResponseEncoder, nil, nil) genserver.Mount(server, mux) // Start the server if err := http.ListenAndServe(":8080", mux); err != nil { log.Fatalf("failed to start server: %v", err) } }
Optional: Combined Static and Dynamic Content
If you need to serve both static files and dynamic templates, you can combine them in your service design:
var _ = Service("front", func() { // Dynamic template endpoints Method("home", func() { HTTP(func() { GET("/") Response(StatusOK, func() { ContentType("text/html") }) }) }) // Static file serving Files("/static/{*filepath}", "public/static") })
This setup serves:
- Dynamic content from templates at the root path (/)
- Static files (CSS, JS, images) from the /static path
- All files under public/static will be available at /static/
Best Practices
Template Organization
- Use template composition with a base layout
- Keep templates in a dedicated directory
- Use the embed.FS to bundle templates with your binary
Error Handling
- Parse templates during service initialization
- Return meaningful errors when template rendering fails
- Set appropriate HTTP response codes and headers
Performance
- Parse templates once at startup
- Use template caching in production
- Consider implementing template reloading in development