Echo Template Basics
Introduction
Echo is a high-performance, extensible, minimalist web framework for Go that makes it easy to build RESTful APIs and web applications. One of its powerful features is template rendering, which allows you to separate your application's logic from its presentation.
In this tutorial, we'll explore the basics of using templates with Echo, which will help you create dynamic web pages that can display data from your Go applications in a structured and visually appealing way.
What are Echo Templates?
Echo templates are a way to generate HTML dynamically based on data from your Go application. Rather than hard-coding HTML in your Go code (which would be messy and difficult to maintain), templates allow you to:
- Keep your HTML separate from your Go code
- Insert dynamic data into your HTML at specified points
- Reuse common elements across multiple pages
- Apply logic within your templates (conditionals, loops, etc.)
Echo itself doesn't include a template engine but provides an interface that works with Go's standard html/template
and text/template
packages, as well as third-party template engines.
Setting Up Echo Templates
Let's start by setting up a basic Echo application with templates:
package main
import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"html/template"
"io"
"net/http"
)
// Define a template renderer
type TemplateRenderer struct {
templates *template.Template
}
// Implement the Render method required by Echo's 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() {
// Create a new Echo instance
e := echo.New()
// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// Initialize the renderer
renderer := &TemplateRenderer{
templates: template.Must(template.ParseGlob("templates/*.html")),
}
e.Renderer = renderer
// Routes
e.GET("/", func(c echo.Context) error {
return c.Render(http.StatusOK, "index.html", map[string]interface{}{
"title": "Echo Template Demo",
"message": "Welcome to Echo Templates!",
})
})
// Start server
e.Logger.Fatal(e.Start(":8080"))
}
Let's create a simple template file named index.html
in a templates
directory:
<!DOCTYPE html>
<html>
<head>
<title>{{.title}}</title>
</head>
<body>
<h1>{{.message}}</h1>
<p>This is a basic Echo template example.</p>
</body>
</html>
When you run your application and visit http://localhost:8080
, you'll see a page with the title "Echo Template Demo" and the message "Welcome to Echo Templates!".
Template Data Binding
In the example above, we passed data to the template using a map. The data can be accessed in the template using the dot notation (.key
). Let's expand on this with more sophisticated data:
// Define a struct for our data
type User struct {
Name string
Email string
IsAdmin bool
}
// In your route handler
e.GET("/user", func(c echo.Context) error {
user := User{
Name: "John Doe",
Email: "[email protected]",
IsAdmin: true,
}
return c.Render(http.StatusOK, "user.html", map[string]interface{}{
"title": "User Profile",
"user": user,
})
})
And in your user.html
template:
<!DOCTYPE html>
<html>
<head>
<title>{{.title}}</title>
</head>
<body>
<h1>User Profile</h1>
<div class="user-info">
<p><strong>Name:</strong> {{.user.Name}}</p>
<p><strong>Email:</strong> {{.user.Email}}</p>
{{if .user.IsAdmin}}
<p class="admin-badge">Administrator</p>
{{end}}
</div>
</body>
</html>
Template Logic
Go templates support basic logic operations like conditionals and loops. Here's how to use them:
Conditionals
{{if .condition}}
<!-- Content to show when condition is true -->
{{else}}
<!-- Content to show when condition is false -->
{{end}}
Loops
<ul>
{{range .items}}
<li>{{.}}</li>
{{end}}
</ul>
Let's create an example with a list of items:
e.GET("/items", func(c echo.Context) error {
items := []string{"Apple", "Banana", "Cherry", "Date"}
return c.Render(http.StatusOK, "items.html", map[string]interface{}{
"title": "Fruit List",
"items": items,
"hasItems": len(items) > 0,
})
})
And in your items.html
template:
<!DOCTYPE html>
<html>
<head>
<title>{{.title}}</title>
</head>
<body>
<h1>Fruit List</h1>
{{if .hasItems}}
<ul>
{{range .items}}
<li>{{.}}</li>
{{end}}
</ul>
{{else}}
<p>No items to display.</p>
{{end}}
</body>
</html>
Template Includes and Layouts
For a more maintainable application, you'll want to reuse components across multiple pages. Go templates support including other templates:
Let's create a layout system with these files:
templates/layout.html:
<!DOCTYPE html>
<html>
<head>
<title>{{.title}}</title>
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<header>
<nav>
<a href="/">Home</a>
<a href="/items">Items</a>
<a href="/user">User</a>
</nav>
</header>
<main>
{{template "content" .}}
</main>
<footer>
<p>© 2023 Echo Templates Tutorial</p>
</footer>
</body>
</html>
templates/index.html:
{{define "content"}}
<h1>{{.message}}</h1>
<p>This is the homepage content.</p>
{{end}}
To use this layout system, modify your template renderer to parse all templates together:
renderer := &TemplateRenderer{
templates: template.Must(template.ParseGlob("templates/*.html")),
}
Then in your route handler:
e.GET("/", func(c echo.Context) error {
return c.Render(http.StatusOK, "layout.html", map[string]interface{}{
"title": "Home Page",
"message": "Welcome to our website!",
})
})
Serving Static Files
Templates often need static files like CSS, JavaScript, and images. Echo makes it easy to serve these files:
// Serve static files from the "static" directory
e.Static("/static", "static")
Now you can reference these files in your templates:
<link rel="stylesheet" href="/static/css/style.css">
<script src="/static/js/script.js"></script>
<img src="/static/images/logo.png" alt="Logo">
Real-world Example: A Simple Blog
Let's build a simple blog application to demonstrate Echo templates in action:
package main
import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"html/template"
"io"
"net/http"
"time"
)
// Template renderer
type TemplateRenderer struct {
templates *template.Template
}
func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.templates.ExecuteTemplate(w, name, data)
}
// Blog post struct
type BlogPost struct {
ID int
Title string
Content string
Author string
CreatedAt time.Time
}
func main() {
e := echo.New()
// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// Static files
e.Static("/static", "static")
// Templates
renderer := &TemplateRenderer{
templates: template.Must(template.ParseGlob("templates/*.html")),
}
e.Renderer = renderer
// Mock database of blog posts
posts := []BlogPost{
{
ID: 1,
Title: "Getting Started with Echo",
Content: "Echo is a high-performance web framework for Go...",
Author: "John Doe",
CreatedAt: time.Now().AddDate(0, 0, -3),
},
{
ID: 2,
Title: "Echo Template Basics",
Content: "Templates in Echo allow you to separate your HTML from Go code...",
Author: "Jane Smith",
CreatedAt: time.Now().AddDate(0, 0, -1),
},
}
// Routes
e.GET("/", func(c echo.Context) error {
return c.Render(http.StatusOK, "blog-list.html", map[string]interface{}{
"title": "My Blog",
"posts": posts,
})
})
e.GET("/post/:id", func(c echo.Context) error {
id := c.Param("id")
var post BlogPost
// In a real app, you'd fetch from a database
// This is just a simple example
for _, p := range posts {
if p.ID == 1 && id == "1" || p.ID == 2 && id == "2" {
post = p
break
}
}
return c.Render(http.StatusOK, "blog-post.html", map[string]interface{}{
"title": post.Title,
"post": post,
})
})
e.Logger.Fatal(e.Start(":8080"))
}
Now, create the templates:
templates/blog-list.html:
<!DOCTYPE html>
<html>
<head>
<title>{{.title}}</title>
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<header>
<h1>My Blog</h1>
</header>
<main>
<section class="posts">
{{range .posts}}
<article class="post-summary">
<h2><a href="/post/{{.ID}}">{{.Title}}</a></h2>
<p class="meta">By {{.Author}} on {{.CreatedAt.Format "Jan 02, 2006"}}</p>
<p class="excerpt">{{.Content}}</p>
<a href="/post/{{.ID}}" class="read-more">Read More</a>
</article>
{{end}}
</section>
</main>
<footer>
<p>© 2023 My Blog</p>
</footer>
</body>
</html>
templates/blog-post.html:
<!DOCTYPE html>
<html>
<head>
<title>{{.title}}</title>
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<header>
<h1>My Blog</h1>
<nav>
<a href="/">← Back to Posts</a>
</nav>
</header>
<main>
<article class="full-post">
<h1>{{.post.Title}}</h1>
<p class="meta">By {{.post.Author}} on {{.post.CreatedAt.Format "Jan 02, 2006"}}</p>
<div class="content">
{{.post.Content}}
</div>
</article>
</main>
<footer>
<p>© 2023 My Blog</p>
</footer>
</body>
</html>
And some simple CSS in static/css/style.css
:
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 0;
padding: 0;
color: #333;
}
header, main, footer {
max-width: 800px;
margin: 0 auto;
padding: 1rem;
}
header {
border-bottom: 1px solid #eee;
}
footer {
border-top: 1px solid #eee;
text-align: center;
font-size: 0.8rem;
}
.post-summary {
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid #eee;
}
.meta {
color: #666;
font-style: italic;
}
Summary
In this tutorial, we've covered the basics of using templates with the Echo framework:
- Setting up a template renderer in Echo
- Creating basic templates with dynamic data
- Using template logic with conditionals and loops
- Implementing layouts and includes for code reuse
- Serving static files alongside your templates
- Building a simple blog application as a practical example
Echo templates provide a powerful way to build dynamic web applications by separating your HTML from your Go code. This separation makes your code more maintainable and allows designers and developers to work more independently.
Additional Resources
Exercises
- Modify the blog example to include a search function that filters posts by title or content.
- Create a contact form template with form validation that displays error messages.
- Implement a template for a navigation menu that highlights the current page.
- Extend the blog to include categories and tags for posts, and add pages to browse by category/tag.
- Add pagination to the blog post list when there are more than 5 posts per page.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)