This episode dissects sending responses in Echo: JSON, XML, and HTML, template engine integration with a custom renderer, response streaming, serving static files, file uploads, and the StaticDirectoryHandler security fix for CVE-2026-55677.

The response is the face of your API. So far we've only sent plain text and simple JSON; this episode expands the scope to XML, HTML with templates, streaming, plus serving static files and uploads — real needs for almost every web application.
Episode 7 dissects sending responses in Echo: JSON, XML, and HTML; template engine integration through e.Renderer; response streaming; e.Static and e.File; file uploads; and the StaticDirectoryHandler security step related to CVE-2026-55677.
Echo handlers can return almost any format. The three most common ones:
type Product struct {
ID int `json:"id" xml:"id"`
Name string `json:"name" xml:"name"`
Price int `json:"price" xml:"price"`
}
e.GET("/product/json", func(c echo.Context) error {
return c.JSON(http.StatusOK, Product{ID: 1, Name: "Keyboard", Price: 500000})
})
e.GET("/product/xml", func(c echo.Context) error {
return c.XML(http.StatusOK, Product{ID: 1, Name: "Keyboard", Price: 500000})
})
e.GET("/page", func(c echo.Context) error {
return c.HTML(http.StatusOK, "<h1>Halo dari Echo</h1>")
})c.JSON, c.XML, and c.HTML set the Content-Type and write the body at the same time. For finer control, c.Blob sends raw bytes with a specific type, and c.HTMLBlob sends HTML from bytes.
When a response is large or generated incrementally, use c.Stream so the data flows without holding the entire payload in memory:
e.GET("/download", func(c echo.Context) error {
content := []byte("data bervolume besar yang dikirim bertahap")
return c.Stream(http.StatusOK, echo.MIMEOctetStream, bytes.NewReader(content))
})c.Stream uses an io.Reader as its source, so large files can be streamed from disk without being fully read first.
Echo doesn't lock you into a specific template engine. Implement the echo.Renderer interface and use your favorite library — the standard html/template is the safest choice because it escapes HTML automatically:
type Template struct {
templates *template.Template
}
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.templates.ExecuteTemplate(w, name, data)
}
t := &Template{
templates: template.Must(template.ParseGlob("views/*.html")),
}
e.Renderer = tOnce e.Renderer is set, handlers can render templates with data:
e.GET("/welcome", func(c echo.Context) error {
return c.Render(http.StatusOK, "welcome.html", map[string]string{
"Name": "Arman",
})
})c.Render(http.StatusOK, name, data) accepts the status, template name, and data. Any template engine — from html/template, pongo2, to amber — works as long as it implements Render.
Serving static files takes a single line. e.Static maps a URL prefix to a directory, and e.File serves one specific file:
e.Static("/static", "public")
e.File("/favicon.ico", "public/favicon.ico")With e.Static("/static", "public"), the file public/logo.png can be accessed at /static/logo.png.
File uploads use c.FormFile to grab a file from multipart/form-data:
e.POST("/upload", func(c echo.Context) error {
file, err := c.FormFile("file")
if err != nil {
return err
}
src, err := file.Open()
if err != nil {
return err
}
defer src.Close()
dst, err := os.Create("/tmp/" + file.Filename)
if err != nil {
return err
}
defer dst.Close()
if _, err := io.Copy(dst, src); err != nil {
return err
}
return c.JSON(http.StatusOK, map[string]string{"file": file.Filename})
})Always remember to limit upload size with BodyLimit (episode 6) and validate the file type before saving it.
In 2026, CVE-2026-55677 was discovered, a flaw that allowed route bypass in static directory handling. The combination of route patterns and the handling of ../ in paths could direct requests to files outside the allowed directory.
The fix was released in Echo 5.2.0 and 4.15.3. Here's what you need to do:
go get github.com/labstack/echo/v5@v5.2.0
go mod tidyBesides updating, make sure the static directory never contains sensitive files like .env or private keys. Validate uploaded filenames and avoid passing path values from the user directly without normalization.
Episode 7 expands your response capabilities: JSON, XML, and HTML; template rendering with a custom renderer based on html/template; streaming for large data; serving static files with e.Static and e.File; safe file uploads; and mitigating CVE-2026-55677 by updating Echo.
Key takeaways:
c.JSON, c.XML, c.HTML, and c.Blob handle common formats.c.Stream streams data through an io.Reader without holding memory.html/template escapes output automatically.c.Render renders a template with data from the handler.e.Static and e.File serve static files.c.FormFile; limit the size with BodyLimit.In episode 8 next, we'll discuss project structure & clean architecture — organizing handlers, services, and repositories, the internal/ layout, simple dependency injection, and modularizing the router and middleware setup so the code is easy to test and scale.