Skip to main content

Echo Response Body

Introduction

When building web applications or APIs with the Echo framework, one of the most fundamental aspects is sending back data to the client. The response body is the actual content that gets delivered to the client after processing a request. It could be HTML for web pages, JSON for APIs, or various other formats like XML, text, or binary data.

In this guide, we'll explore how Echo handles response bodies and the various ways you can send different types of content back to your users. Understanding response bodies is crucial for creating functional and efficient web applications.

Basic Response Body Concepts

In Echo, the response body is managed through the echo.Context object that's passed to your handler functions. This context provides several methods to send different types of response data.

Simple Text Response

The most basic way to send a response is with plain text:

go
e.GET("/hello", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})

In this example:

  • c.String() sets the response body to the provided string
  • http.StatusOK (which is 200) sets the HTTP status code
  • The Content-Type header is automatically set to "text/plain"

Output:

Hello, World!

JSON Responses

For web APIs, JSON is the most common format for response bodies:

go
e.GET("/user", func(c echo.Context) error {
user := struct {
Name string `json:"name"`
Email string `json:"email"`
}{
Name: "John Doe",
Email: "[email protected]",
}

return c.JSON(http.StatusOK, user)
})

This handler will return:

Output:

json
{
"name": "John Doe",
"email": "[email protected]"
}

Echo automatically handles the JSON serialization and sets the Content-Type header to "application/json".

HTML Responses

For web applications that serve HTML pages:

go
e.GET("/page", func(c echo.Context) error {
html := `
<!DOCTYPE html>
<html>
<head>
<title>Echo HTML Response</title>
</head>
<body>
<h1>Hello from Echo!</h1>
<p>This is an HTML response.</p>
</body>
</html>
`
return c.HTML(http.StatusOK, html)
})

This sets the Content-Type to "text/html" and sends the HTML content to the browser.

Working with Different Response Formats

Echo provides several methods for different response formats:

XML Response

go
e.GET("/data", func(c echo.Context) error {
type Person struct {
XMLName xml.Name `xml:"person"`
Name string `xml:"name"`
Age int `xml:"age"`
IsStudent bool `xml:"is_student"`
}

person := Person{
Name: "Alice",
Age: 28,
IsStudent: false,
}

return c.XML(http.StatusOK, person)
})

Output:

xml
<person>
<name>Alice</name>
<age>28</age>
<is_student>false</is_student>
</person>

Binary Data

For serving files or binary data:

go
e.GET("/image", func(c echo.Context) error {
// Read image file
data, err := ioutil.ReadFile("logo.png")
if err != nil {
return err
}

return c.Blob(http.StatusOK, "image/png", data)
})

This sends binary data with the appropriate MIME type.

File Download

Echo can also send files directly from the server:

go
e.GET("/download", func(c echo.Context) error {
return c.File("files/document.pdf")
})

For creating a downloadable response with a specific filename:

go
e.GET("/download-report", func(c echo.Context) error {
return c.Attachment("reports/annual.pdf", "company-report-2023.pdf")
})

Customizing Response Content

Setting Custom Headers

Sometimes you need to set additional headers along with your response body:

go
e.GET("/api/data", func(c echo.Context) error {
// Set custom headers
c.Response().Header().Set("X-Custom-Header", "CustomValue")
c.Response().Header().Set("Cache-Control", "no-cache")

data := map[string]interface{}{
"message": "Success",
"data": []int{1, 2, 3, 4, 5},
}

return c.JSON(http.StatusOK, data)
})

Streaming Response

For large responses or real-time data, you might want to stream the response:

go
e.GET("/stream", func(c echo.Context) error {
c.Response().Header().Set("Content-Type", "text/event-stream")
c.Response().Header().Set("Cache-Control", "no-cache")
c.Response().WriteHeader(http.StatusOK)

// Send data in chunks
for i := 0; i < 10; i++ {
c.Response().Write([]byte(fmt.Sprintf("data: Message %d\n\n", i)))
c.Response().Flush()
time.Sleep(1 * time.Second)
}

return nil
})

This example creates a simple server-sent events (SSE) endpoint that sends 10 messages with 1-second intervals.

Real-World Application Examples

RESTful API Response

Here's a more complete example of a RESTful API endpoint that returns a structured response:

go
type ApiResponse struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
Data interface{} `json:"data,omitempty"`
Error string `json:"error,omitempty"`
}

e.GET("/api/products/:id", func(c echo.Context) error {
id := c.Param("id")

// Simulate database lookup
product, err := findProductById(id)
if err != nil {
response := ApiResponse{
Success: false,
Error: "Product not found",
}
return c.JSON(http.StatusNotFound, response)
}

response := ApiResponse{
Success: true,
Data: product,
}

return c.JSON(http.StatusOK, response)
})

Dynamic Content Negotiation

You can implement content negotiation to serve different formats based on the client's Accept header:

go
e.GET("/api/user/:id", func(c echo.Context) error {
id := c.Param("id")
user, err := findUserById(id)

if err != nil {
return c.String(http.StatusNotFound, "User not found")
}

// Check Accept header
switch c.Request().Header.Get("Accept") {
case "application/xml":
return c.XML(http.StatusOK, user)
case "text/plain":
return c.String(http.StatusOK, fmt.Sprintf("User: %s, Email: %s", user.Name, user.Email))
default:
// Default to JSON
return c.JSON(http.StatusOK, user)
}
})

Performance Considerations

When working with response bodies, keep these performance tips in mind:

  1. Avoid unnecessary serialization: Only convert data to formats like JSON if needed.
  2. Use streaming for large responses: For large datasets, consider streaming the response.
  3. Set appropriate Content-Length: For fixed-size responses, setting the Content-Length header helps clients process the response efficiently.
  4. Compression: Consider enabling gzip compression for text-based responses.

Example with compression middleware:

go
// Use the built-in middleware for Gzip compression
e.Use(middleware.Gzip())

e.GET("/large-data", func(c echo.Context) error {
data := generateLargeJSONData()
return c.JSON(http.StatusOK, data)
// Response will be automatically compressed if the client supports it
})

Error Handling in Response Bodies

When an error occurs, it's good practice to send a structured error response:

go
e.GET("/api/resource", func(c echo.Context) error {
result, err := someOperation()

if err != nil {
errorResponse := map[string]string{
"error": err.Error(),
"code": "RESOURCE_ERROR",
}
return c.JSON(http.StatusInternalServerError, errorResponse)
}

return c.JSON(http.StatusOK, result)
})

Summary

In this guide, we've explored how to work with response bodies in the Echo framework:

  • Sending basic text, JSON, HTML, XML, and binary responses
  • Customizing response headers and content
  • Implementing streaming responses
  • Creating real-world API responses with proper structure
  • Handling content negotiation
  • Optimizing performance with compression and other techniques

Understanding how to properly format and send response bodies is essential for building robust web applications and APIs. Echo provides a clean and intuitive interface for handling various response types, making it easier to create applications that communicate effectively with clients.

Additional Resources

Exercises

  1. Create an Echo handler that returns a list of items in both JSON and XML format based on a query parameter format.
  2. Implement a file upload endpoint that responds with information about the uploaded file.
  3. Build a simple API that streams weather updates every few seconds to the client.
  4. Create an endpoint that serves different image formats (PNG, JPEG, WebP) based on client capabilities.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)