Echo Framework: Building Web Applications
Introduction
Echo is a high-performance, extensible web framework for Go. It provides a clean API for building web applications with excellent routing, middleware support, and built-in features like validation and error handling. This guide covers building production-ready web applications with Echo.
Core Concepts
Echo Features
- Fast HTTP routing with path parameters
- Middleware support for request processing
- Built-in validation and binding
- Automatic error handling
- WebSocket support
- Template rendering
Good: Basic Echo Setup
Creating an Echo Application
package main
import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"net/http"
)
// โ
GOOD: Basic Echo application
func main() {
e := echo.New()
// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// Routes
e.GET("/", handleHome)
e.GET("/users/:id", handleGetUser)
e.POST("/users", handleCreateUser)
// Start server
e.Logger.Fatal(e.Start(":8080"))
}
func handleHome(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"message": "Welcome to Echo",
})
}
func handleGetUser(c echo.Context) error {
id := c.Param("id")
return c.JSON(http.StatusOK, map[string]string{
"id": id,
})
}
func handleCreateUser(c echo.Context) error {
user := struct {
Name string `json:"name"`
Email string `json:"email"`
}{}
if err := c.Bind(&user); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{
"error": err.Error(),
})
}
return c.JSON(http.StatusCreated, user)
}
Route Groups
package main
import (
"github.com/labstack/echo/v4"
"net/http"
)
// โ
GOOD: Route grouping in Echo
func setupRoutes(e *echo.Echo) {
// API v1
v1 := e.Group("/api/v1")
{
v1.GET("/health", handleHealth)
v1.GET("/users", handleListUsers)
v1.POST("/users", handleCreateUser)
}
// API v2
v2 := e.Group("/api/v2")
{
v2.GET("/health", handleHealthV2)
v2.GET("/users", handleListUsersV2)
}
// Admin routes
admin := e.Group("/admin")
admin.Use(authMiddleware())
{
admin.GET("/stats", handleStats)
admin.DELETE("/users/:id", handleDeleteUser)
}
}
func handleHealth(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{"status": "ok"})
}
func handleListUsers(c echo.Context) error {
return c.JSON(http.StatusOK, []string{})
}
func handleCreateUser(c echo.Context) error {
return c.JSON(http.StatusCreated, map[string]bool{"created": true})
}
func handleHealthV2(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{"status": "ok", "version": "2"})
}
func handleListUsersV2(c echo.Context) error {
return c.JSON(http.StatusOK, []string{})
}
func handleStats(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]interface{}{"stats": "data"})
}
func handleDeleteUser(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]bool{"deleted": true})
}
func authMiddleware() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Check auth
return next(c)
}
}
}
Good: Middleware
Creating Echo Middleware
package main
import (
"github.com/labstack/echo/v4"
"log"
"time"
)
// โ
GOOD: Logging middleware
func loggingMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
start := time.Now()
err := next(c)
duration := time.Since(start)
log.Printf("%s %s %d %v",
c.Request().Method,
c.Request().URL.Path,
c.Response().Status,
duration,
)
return err
}
}
// โ
GOOD: Authentication middleware
func authMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
token := c.Request().Header.Get("Authorization")
if token == "" {
return c.JSON(401, map[string]string{"error": "Unauthorized"})
}
// Validate token
c.Set("user_id", 123)
return next(c)
}
}
// โ
GOOD: CORS middleware
func corsMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
c.Response().Header().Set("Access-Control-Allow-Origin", "*")
c.Response().Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
return next(c)
}
}
// โ
GOOD: Error handling middleware
func errorHandlingMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
defer func() {
if err := recover(); err != nil {
log.Printf("Panic: %v", err)
c.JSON(500, map[string]string{"error": "Internal server error"})
}
}()
return next(c)
}
}
Good: Request Handling
Binding and Validation
package main
import (
"github.com/labstack/echo/v4"
"net/http"
)
// โ
GOOD: Struct with validation tags
type User struct {
Name string `json:"name" validate:"required,min=3,max=50"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"min=0,max=150"`
}
// โ
GOOD: Bind and validate JSON
func handleCreateUserWithValidation(c echo.Context) error {
user := new(User)
if err := c.Bind(user); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{
"error": "Invalid request",
})
}
// Validate
if err := c.Validate(user); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{
"error": err.Error(),
})
}
return c.JSON(http.StatusCreated, user)
}
// โ
GOOD: Extract query parameters
func handleSearch(c echo.Context) error {
query := c.QueryParam("q")
limit := c.QueryParam("limit")
return c.JSON(http.StatusOK, map[string]string{
"query": query,
"limit": limit,
})
}
// โ
GOOD: Extract path parameters
func handleGetUserByID(c echo.Context) error {
id := c.Param("id")
return c.JSON(http.StatusOK, map[string]string{
"id": id,
})
}
// โ
GOOD: Set response headers
func handleWithHeaders(c echo.Context) error {
c.Response().Header().Set("X-Custom-Header", "value")
return c.JSON(http.StatusOK, map[string]string{"message": "ok"})
}
Advanced Patterns
Custom Error Handler
package main
import (
"github.com/labstack/echo/v4"
"net/http"
)
// โ
GOOD: Custom error handler
type ErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
}
func customErrorHandler(err error, c echo.Context) {
code := http.StatusInternalServerError
message := "Internal server error"
if he, ok := err.(*echo.HTTPError); ok {
code = he.Code
message = he.Message.(string)
}
c.JSON(code, ErrorResponse{
Code: code,
Message: message,
})
}
// โ
GOOD: Using custom error handler
func setupErrorHandling(e *echo.Echo) {
e.HTTPErrorHandler = customErrorHandler
}
File Upload
package main
import (
"github.com/labstack/echo/v4"
"net/http"
)
// โ
GOOD: Handle file upload
func handleFileUpload(c echo.Context) error {
file, err := c.FormFile("file")
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{
"error": "No file provided",
})
}
// Save file
src, _ := file.Open()
defer src.Close()
dst, _ := c.Response().Writer.(http.ResponseWriter)
if _, err := dst.Write([]byte("File saved")); err != nil {
return err
}
return c.JSON(http.StatusOK, map[string]interface{}{
"filename": file.Filename,
"size": file.Size,
})
}
Best Practices
1. Use Middleware for Cross-Cutting Concerns
// โ
GOOD: Middleware for logging, auth, etc.
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(authMiddleware())
// โ BAD: Repeating logic in handlers
func handler(c echo.Context) error {
// Log
// Check auth
// Do work
}
2. Validate Input
// โ
GOOD: Use validation tags
type Request struct {
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"min=0,max=150"`
}
// โ BAD: Manual validation
type BadRequest struct {
Email string
Age int
}
3. Handle Errors Consistently
// โ
GOOD: Consistent error responses
return c.JSON(http.StatusBadRequest, ErrorResponse{
Code: http.StatusBadRequest,
Message: "Invalid input",
})
// โ BAD: Inconsistent error handling
return c.String(http.StatusBadRequest, "Error")
Resources
- Echo Documentation: https://echo.labstack.com/
- Echo GitHub: https://github.com/labstack/echo
- Echo Middleware: https://echo.labstack.com/middleware/
Summary
Echo is a powerful framework for building web applications in Go. Use route groups for organization, middleware for cross-cutting concerns, and proper validation for robust applications. Echo’s simplicity and extensibility make it ideal for building scalable web services.
Comments