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