Yuen Ying Kit
Posted on November 20, 2018
Originally posted on Boatswain Blog.
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.
If you already have an idea on how Echo works, jump to the Using nested template section.
A basic Echo project setup
Create the project folder under proper $GOPATH
The complete project code is hosted on GitLab so we first create the project folder $GOPATH/src/gitlab.com/ykyuen/golang-echo-template-example.
Create the main.go
Inside the newly created folder, let's just copy the hello world example from the Echo official site and create the main.go.
main.go
package main
import (
"net/http"
"github.com/labstack/echo"
)
func main() {
e := echo.New()
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})
e.Logger.Fatal(e.Start(":1323"))
}
Download the Echo package using dep
Simply run dep init if dep is installed. You can refer to this post for more information about dep.
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.
main.go
package main
import (
"net/http"
"github.com/labstack/echo"
)
func main() {
e := echo.New()
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})
e.GET("/json", func(c echo.Context) error {
return c.JSONBlob(
http.StatusOK,
[]byte(`{ "id": "1", "msg": "Hello, Boatswain!" }`),
)
})
e.Logger.Fatal(e.Start(":1323"))
}
Return an HTML
Similar to returning a JSON object, we just need to call another method in the return statement.
main.go
package main
import (
"net/http"
"github.com/labstack/echo"
)
func main() {
e := echo.New()
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})
e.GET("/json", func(c echo.Context) error {
return c.JSONBlob(
http.StatusOK,
[]byte(`{ "id": "1", "msg": "Hello, Boatswain!" }`),
)
})
e.GET("/html", func(c echo.Context) error {
return c.HTML(
http.StatusOK,
"<h1>Hello, Boatswain!</h1>",
)
})
e.Logger.Fatal(e.Start(":1323"))
}
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.
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
main.go
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)
}
func main() {
// 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
package handler
import (
"net/http"
"github.com/labstack/echo"
)
func HomeHandler(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
view/home.html
{{define "home.html"}}
<!DOCTYPE html>
<html>
<head>
<title>Boatswain Blog | {{index . "name"}}</title>
</head>
<body>
<h1>{{index . "msg"}}</h1>
</body>
</html>
{{end}}
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.
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.
main.go
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)
}
func main() {
// 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.
view/base.html
{{define "base.html"}}
<!DOCTYPE html>
<html>
<head>
<title>{{template "title" .}}</title>
</head>
<body>
{{template "body" .}}
</body>
</html>
{{end}}
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
{{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
package handler
import (
"net/http"
"github.com/labstack/echo"
)
func AboutHandler(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 engine.
The complete example could be found on gitlab.com.
Posted on November 20, 2018
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 27, 2024