Echo Template Data Passing
Introduction
When building web applications with Echo, one of the most important aspects is the ability to render dynamic content. Echo templates allow you to pass data from your Go backend to your HTML frontend, making your web pages interactive and personalized. This guide will walk you through the fundamentals of passing data to Echo templates and displaying that data in your views.
Data passing is the process of transferring information from your Go handler functions to your HTML templates. This is what makes web applications dynamic – showing personalized user information, displaying database query results, or customizing the user interface based on specific conditions.
Understanding Data Context
In Echo, data is passed to templates using a data context. This context is typically a map or a struct that contains all the values you want to access in your template. Let's explore how this works:
The Basics of Data Passing
To render a template with data in Echo, you use the Context.Render()
method. Here's the basic pattern:
func HandleHome(c echo.Context) error {
// Create data to pass to the template
data := map[string]interface{}{
"title": "Welcome to my website",
"username": "JohnDoe",
}
// Render template with data
return c.Render(http.StatusOK, "home.html", data)
}
In the template, you can then access these variables:
<h1>{{.title}}</h1>
<p>Hello, {{.username}}!</p>
When rendered, this will output:
<h1>Welcome to my website</h1>
<p>Hello, JohnDoe!</p>
Using Structs vs Maps
You can pass data using either maps or structs. Each approach has its advantages:
Using Maps
Maps offer flexibility but no compile-time type checking:
data := map[string]interface{}{
"title": "Dashboard",
"items": []string{"Item 1", "Item 2", "Item 3"},
"count": 42,
}
Using Structs
Structs provide type safety and better organization:
type PageData struct {
Title string
User UserInfo
Stats Statistics
}
type UserInfo struct {
Username string
Email string
IsAdmin bool
}
type Statistics struct {
VisitCount int
LastVisit time.Time
}
data := PageData{
Title: "User Profile",
User: UserInfo{
Username: "johndoe",
Email: "[email protected]",
IsAdmin: false,
},
Stats: Statistics{
VisitCount: 42,
LastVisit: time.Now(),
},
}
Accessing Nested Data
When your data structure is nested (like with structs), you can access nested fields in your templates:
<h1>{{.Title}}</h1>
<div class="profile">
<p>Username: {{.User.Username}}</p>
<p>Email: {{.User.Email}}</p>
{{if .User.IsAdmin}}
<p class="admin-badge">Admin</p>
{{end}}
</div>
<div class="stats">
<p>Visit count: {{.Stats.VisitCount}}</p>
<p>Last visit: {{.Stats.LastVisit.Format "Jan 2, 2006 at 3:04pm"}}</p>
</div>
Complete Example
Let's put together a complete example of setting up Echo templates with data passing:
File Structure
myapp/
├── main.go
├── templates/
│ ├── base.html
│ ├── home.html
│ └── dashboard.html
1. Setting Up Templates in main.go
package main
import (
"html/template"
"io"
"net/http"
"time"
"github.com/labstack/echo/v4"
)
// Template renderer
type TemplateRenderer struct {
templates *template.Template
}
// Implement the Renderer interface
func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.templates.ExecuteTemplate(w, name, data)
}
func main() {
e := echo.New()
// Initialize templates
renderer := &TemplateRenderer{
templates: template.Must(template.ParseGlob("templates/*.html")),
}
e.Renderer = renderer
// Routes
e.GET("/", handleHome)
e.GET("/dashboard", handleDashboard)
// Start server
e.Start(":8080")
}
// Home page handler
func handleHome(c echo.Context) error {
data := map[string]interface{}{
"title": "Welcome to Echo Templates",
"currentTime": time.Now().Format(time.RFC1123),
}
return c.Render(http.StatusOK, "home.html", data)
}
// Dashboard page handler
type DashboardData struct {
Title string
User struct {
Name string
Email string
Role string
}
Stats struct {
VisitCount int
LastLogin time.Time
Activities []string
}
}
func handleDashboard(c echo.Context) error {
data := DashboardData{
Title: "User Dashboard",
User: struct {
Name string
Email string
Role string
}{
Name: "John Doe",
Email: "[email protected]",
Role: "Administrator",
},
Stats: struct {
VisitCount int
LastLogin time.Time
Activities []string
}{
VisitCount: 27,
LastLogin: time.Now().Add(-24 * time.Hour),
Activities: []string{
"Updated profile picture",
"Changed password",
"Created new project",
},
},
}
return c.Render(http.StatusOK, "dashboard.html", data)
}
2. base.html Template
{{define "base"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.title}}</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
.container { padding: 20px; border: 1px solid #ddd; border-radius: 5px; }
.footer { margin-top: 30px; border-top: 1px solid #ddd; padding-top: 10px; font-size: 0.8em; }
</style>
</head>
<body>
<div class="container">
{{block "content" .}}{{end}}
</div>
<div class="footer">
<p>© 2023 Echo Templates Example</p>
</div>
</body>
</html>
{{end}}
3. home.html Template
{{define "home.html"}}
{{template "base" .}}
{{define "content"}}
<h1>{{.title}}</h1>
<p>This is a simple example of passing data to Echo templates.</p>
<p>Current server time: {{.currentTime}}</p>
<div>
<a href="/dashboard">Go to Dashboard</a>
</div>
{{end}}
{{end}}
4. dashboard.html Template
{{define "dashboard.html"}}
{{template "base" .}}
{{define "content"}}
<h1>{{.Title}}</h1>
<div class="user-info">
<h2>User Information</h2>
<p><strong>Name:</strong> {{.User.Name}}</p>
<p><strong>Email:</strong> {{.User.Email}}</p>
<p><strong>Role:</strong> {{.User.Role}}</p>
</div>
<div class="stats">
<h2>Account Statistics</h2>
<p><strong>Visit Count:</strong> {{.Stats.VisitCount}}</p>
<p><strong>Last Login:</strong> {{.Stats.LastLogin.Format "Jan 02, 2006 15:04:05"}}</p>
<h3>Recent Activities</h3>
<ul>
{{range .Stats.Activities}}
<li>{{.}}</li>
{{end}}
</ul>
</div>
<div>
<a href="/">Back to Home</a>
</div>
{{end}}
{{end}}
Common Patterns for Data Passing
1. Using a Base View Model
It's often helpful to have a consistent structure for your templates:
type BaseViewModel struct {
Title string
Description string
User *UserInfo
CurrentYear int
// Common data for all pages
}
func NewBaseViewModel(title string) BaseViewModel {
return BaseViewModel{
Title: title,
CurrentYear: time.Now().Year(),
// Set default values
}
}
// Then in your handler:
func handleSomePage(c echo.Context) error {
viewModel := NewBaseViewModel("Page Title")
// Add page-specific data...
return c.Render(http.StatusOK, "page.html", viewModel)
}
2. Flash Messages
For temporary messages (like form submission results):
func handlePostForm(c echo.Context) error {
// Process form...
// Set flash message for next page load
session.Values["flashMessage"] = "Your form was submitted successfully!"
session.Save(c.Request(), c.Response())
return c.Redirect(http.StatusSeeOther, "/success")
}
func handleSuccess(c echo.Context) error {
data := map[string]interface{}{
"title": "Success",
}
// Get flash message if any
if flashMsg, ok := session.Values["flashMessage"]; ok {
data["flashMessage"] = flashMsg
delete(session.Values, "flashMessage")
session.Save(c.Request(), c.Response())
}
return c.Render(http.StatusOK, "success.html", data)
}
In your template:
{{if .flashMessage}}
<div class="alert alert-success">{{.flashMessage}}</div>
{{end}}
3. Pagination Data
When displaying lists of items:
type PaginatedResponse struct {
Items []Item
CurrentPage int
TotalPages int
HasNext bool
HasPrev bool
}
func handleListItems(c echo.Context) error {
page, _ := strconv.Atoi(c.QueryParam("page"))
if page < 1 {
page = 1
}
itemsPerPage := 10
offset := (page - 1) * itemsPerPage
// Fetch items from database with limit and offset
items, totalItems := getItemsFromDB(itemsPerPage, offset)
totalPages := (totalItems + itemsPerPage - 1) / itemsPerPage
data := map[string]interface{}{
"title": "Item List",
"pagination": PaginatedResponse{
Items: items,
CurrentPage: page,
TotalPages: totalPages,
HasNext: page < totalPages,
HasPrev: page > 1,
},
}
return c.Render(http.StatusOK, "items.html", data)
}
Best Practices
- Type Safety: Use structs when possible for compile-time checks
- Separate Logic: Keep complex logic in your Go code, not in templates
- Consistent Naming: Use consistent naming conventions for your template data
- Default Values: Always provide default values to avoid nil pointer errors
- Context Awareness: Include relevant contextual data (user info, permissions, etc.)
- Error Handling: Validate data before passing it to templates
Common Pitfalls
- Unexported Fields: Remember that template engines can only access exported fields (starting with an uppercase letter)
- Nil Values: Check for nil before accessing nested properties
- Type Assertions: Be careful when using type assertions with interface values
- Template Caching: Be aware that templates are usually parsed once at startup
- Large Data Sets: Avoid passing very large datasets to templates
Summary
Data passing in Echo templates is the foundation for creating dynamic web applications. By understanding how to structure your data, pass it to templates, and access it within your HTML, you can create rich, interactive web experiences while maintaining clean, maintainable code.
The key points to remember are:
- Use
Context.Render()
to pass data to templates - Choose between maps and structs based on your needs
- Structure your data logically for templates
- Use nested data for complex views
- Follow best practices for maintainable template code
Further Exercises
- Create a template that displays a list of products with different formatting based on whether they're in stock or not
- Build a user profile page that shows different information based on user permissions
- Implement a dashboard that displays various statistics with appropriate formatting
- Create a multi-page form that preserves data between submissions
- Build a blog system with pagination, categories, and author information
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)