Update @ 2019-12-13: Starting from Go 1.13, please use the built-in Go Module to manage dependencies.
Echo is a lightweight but complete web framework in Go for building RESTful API. It is fast and includes a bunch of middleware for handling the whole HTTP request-response cycle. For the rendering part, it works with any template engine but i pick the standard html/template package for the purpose of simplicity. And at the end of this article, a nested template Echo project setup is demonstrated.
Or run go get github.com/labstack/echo to download the Echo package in $GOPATH.
Run the hello world
Start the application by go run main.go and then visit http://localhost:1323 thru browser or the curl command.
Return a JSON response
When building a RESTful API, it is more likely that the client wants to receive and JSON response instead of a string. Let’s write some Go code in main.go.
The above are just two simple examples, Echo has a few more convenient ways to return JSON and HTML. For details please refer to the documentation.
Render HTML using template engine
As mentioned at the very beginning, we could implement template engine when returning the HTTP response but before that, let’s restructure the project as follow.
1
2
3
4
5
6
7
8
9
10
11
golang-echo-template-example/
├── handler/ # folder of request handlers
│ └── home_handler.go
├── vendor/ # dependencies managed by dep
│ ├── github.com/*
│ └── golang.org/*
├── view/ # folder of html templates
│ └── home.html
├── Gopkg.lock # dep config file
├── Gopkg.toml # dep config file
└── main.go # programme entrypoint
package main
import (
"html/template""io""github.com/labstack/echo""gitlab.com/ykyuen/golang-echo-template-example/handler"
)
// Define the template registry struct
type TemplateRegistry struct {
templates *template.Template
}
// Implement e.Renderer interface
func (t *TemplateRegistry) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.templates.ExecuteTemplate(w, name, data)
}
funcmain() {
// Echo instance
e := echo.New()
// Instantiate a template registry and register all html files inside the view folder
e.Renderer = &TemplateRegistry{
templates: template.Must(template.ParseGlob("view/*.html")),
}
// Route => handler
e.GET("/", handler.HomeHandler)
// Start the Echo server
e.Logger.Fatal(e.Start(":1323"))
}
In this main.go, we define a type called TemplateRegistry and implement the Renderer interface. A Renderer is a simple interface which wraps the Render() function. Inside a TemplateRegistry instance, it has a templates field containing all the templates needed for the Echo server to render html response and this is configured in the main() flow.
On the other hand, we define the HomeHandler in order to keep the logic in separate file.
handler/home_handler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package handler
import (
"net/http""github.com/labstack/echo"
)
funcHomeHandler(c echo.Context) error {
// Please note the the second parameter "home.html" is the template name and should
// be equal to the value stated in the {{ define }} statement in "view/home.html"
return c.Render(http.StatusOK, "home.html", map[string]interface{}{
"name": "HOME",
"msg": "Hello, Boatswain!",
})
}
When the c.Render() is invoked, it executes the template which is already set in our TemplateRegistry instance as stated in main.go. The three paramaters are
HTTP response code
The template name
The data object which could be used in the template
This above template is named as home.html as stated in the define statement and it could read the name and msg strings from c.Render() for the <title> and <h1> tags.
Using nested template
In the above setup, every HTML template has a complete set of HTML code and many of them are duplicated. Using nested template makes it easier to maintain the project. Originally the templates field in the TemplateRegistry contains all the templates files. In the new setup, we make it into an map field and each item is a single set of template files for a particular HTML page.
We add a few files to the project and it should look like this.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
golang-echo-template-example/
├── handler/ # folder of request handlers
│ ├── home_handler.go # handler for home page │ └── about_handler.go # handler for about page ├── vendor/ # dependencies managed by dep
│ ├── github.com/*
│ └── golang.org/*
├── view/ # folder of html templates │ ├── base.html # base layout template │ ├── home.html # home page template │ └── about.html # about page template ├── Gopkg.lock # dep config file
├── Gopkg.toml # dep config file
└── main.go # programme entrypoint
The codes below are based on this gist created by rand99.
package main
import (
"errors""html/template""io""github.com/labstack/echo""gitlab.com/ykyuen/golang-echo-template-example/handler"
)
// Define the template registry struct
type TemplateRegistry struct {
templates map[string]*template.Template
}
// Implement e.Renderer interface
func (t *TemplateRegistry) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
tmpl, ok := t.templates[name]
if !ok {
err := errors.New("Template not found -> " + name)
return err
}
return tmpl.ExecuteTemplate(w, "base.html", data)
}
funcmain() {
// Echo instance
e := echo.New()
// Instantiate a template registry with an array of template set
// Ref: https://gist.github.com/rand99/808e6e9702c00ce64803d94abff65678
templates := make(map[string]*template.Template)
templates["home.html"] = template.Must(template.ParseFiles("view/home.html", "view/base.html"))
templates["about.html"] = template.Must(template.ParseFiles("view/about.html", "view/base.html"))
e.Renderer = &TemplateRegistry{
templates: templates,
}
// Route => handler
e.GET("/", handler.HomeHandler)
e.GET("/about", handler.AboutHandler)
// Start the Echo server
e.Logger.Fatal(e.Start(":1323"))
}
We add a new route /about which is handled by a AboutHandler and as you can see from the above highlighted lines, the templates map contains different set of template files for different HTML pages and the Render() takes the name parameter as the templates map key so it could execute the correct template set.
The template statement tells the template engine that it should look for the {{title}} and {{body}} definitions in the template set and they are defined in the home.html and about.html.
view/about.html
1
2
3
4
5
6
7
8
{{define "title"}}
Boatswain Blog | {{index . "name"}}
{{end}}
{{define "body"}}
<h1>{{index . "msg"}}</h1>
<h2>This is the about page.</h2>
{{end}}
And here is the AboutHanlder which has no big difference from the HomeHandler.
handler/about_handler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package handler
import (
"net/http""github.com/labstack/echo"
)
funcAboutHandler(c echo.Context) error {
// Please note the the second parameter "about.html" is the template name and should
// be equal to one of the keys in the TemplateRegistry array defined in main.go
return c.Render(http.StatusOK, "about.html", map[string]interface{}{
"name": "About",
"msg": "All about Boatswain!",
})
}
Summary
This is just a basic example implementing nested template using the Go standard html/template library in Echo. With proper setup, we could develop a more customized and convenient pattern for Echo or even make it works with any other template engines.
The complete example could be found on gitlab.com.
About
Boatswain is a SAAS platform where you could VIEW your Docker application Status, ANALYSE the Logs from all the Containers and GET real time Notification on Events.