Skip to main content

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:

  1. Keep your HTML separate from your Go code
  2. Insert dynamic data into your HTML at specified points
  3. Reuse common elements across multiple pages
  4. 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:

go
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:

html
<!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:

go
// 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:

html
<!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

html
{{if .condition}}
<!-- Content to show when condition is true -->
{{else}}
<!-- Content to show when condition is false -->
{{end}}

Loops

html
<ul>
{{range .items}}
<li>{{.}}</li>
{{end}}
</ul>

Let's create an example with a list of items:

go
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:

html
<!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:

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>&copy; 2023 Echo Templates Tutorial</p>
</footer>
</body>
</html>

templates/index.html:

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:

go
renderer := &TemplateRenderer{
templates: template.Must(template.ParseGlob("templates/*.html")),
}

Then in your route handler:

go
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:

go
// Serve static files from the "static" directory
e.Static("/static", "static")

Now you can reference these files in your templates:

html
<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:

go
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:

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>&copy; 2023 My Blog</p>
</footer>
</body>
</html>

templates/blog-post.html:

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>&copy; 2023 My Blog</p>
</footer>
</body>
</html>

And some simple CSS in static/css/style.css:

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:

  1. Setting up a template renderer in Echo
  2. Creating basic templates with dynamic data
  3. Using template logic with conditionals and loops
  4. Implementing layouts and includes for code reuse
  5. Serving static files alongside your templates
  6. 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

  1. Modify the blog example to include a search function that filters posts by title or content.
  2. Create a contact form template with form validation that displays error messages.
  3. Implement a template for a navigation menu that highlights the current page.
  4. Extend the blog to include categories and tags for posts, and add pages to browse by category/tag.
  5. 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! :)