Echo Configuration
Introduction
When building web applications with Echo, understanding how to properly configure your server is crucial. Echo configuration allows you to customize your web server's behavior, manage middleware, set up routes, and define how your application responds to requests.
In this guide, we'll explore the various configuration options available in Echo, from basic server setup to advanced configurations that help you build robust and performant web applications.
Basic Echo Server Configuration
Let's start by creating a simple Echo server with some basic configuration options:
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
func main() {
// Create a new Echo instance
e := echo.New()
// Basic configuration
e.Debug = true // Enable debug mode
e.HideBanner = false // Display Echo banner on startup
e.HidePort = false // Display port number on startup
e.DisableHTTP2 = false // Enable HTTP/2 support
e.HTTPErrorHandler = customErrorHandler // Custom error handler
// Define a simple route
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, Echo!")
})
// Start the server
e.Logger.Fatal(e.Start(":8080"))
}
// Custom error handler
func customErrorHandler(err error, c echo.Context) {
code := http.StatusInternalServerError
if he, ok := err.(*echo.HTTPError); ok {
code = he.Code
}
c.JSON(code, map[string]string{
"error": err.Error(),
})
}
This basic setup demonstrates several configuration options:
Debug
mode for detailed logging during development- Banner and port display settings for server startup
- HTTP/2 support configuration
- Custom error handler for consistent error responses
Configuring Middleware
Middleware plays a crucial role in Echo applications. Let's see how to configure various built-in and custom middleware:
package main
import (
"net/http"
"time"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
// Configure logging middleware
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Format: "time=${time_rfc3339}, method=${method}, uri=${uri}, status=${status}\n",
}))
// Configure recovery middleware
e.Use(middleware.RecoverWithConfig(middleware.RecoverConfig{
StackSize: 1 << 10, // 1 KB
LogLevel: 1, // Error level logging
PrintStack: true, // Print stack trace on panic
}))
// Configure CORS middleware
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://example.com", "https://api.example.com"},
AllowMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType},
MaxAge: 86400, // 24 hours
}))
// Configure rate limiter middleware
e.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{
Skipper: middleware.DefaultSkipper,
Store: middleware.NewRateLimiterMemoryStore(20), // 20 requests per second
IdentifierExtractor: func(c echo.Context) (string, error) {
return c.RealIP(), nil // Use client IP as identifier
},
}))
// Define routes
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, Echo with middleware!")
})
// Start the server
e.Logger.Fatal(e.Start(":8080"))
}
This example shows how to configure several important middleware components:
- Logger: Customized to display specific request information
- Recovery: Configured to handle panics gracefully
- CORS: Set up with allowed origins, methods, and headers
- Rate Limiter: Configured to prevent API abuse
Configuring Server Parameters
Echo allows you to configure server-level parameters like timeouts, TLS, and request limits:
package main
import (
"crypto/tls"
"net/http"
"time"
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
// Create custom server with configuration
s := &http.Server{
Addr: ":8080",
ReadTimeout: 20 * time.Second,
WriteTimeout: 20 * time.Second,
IdleTimeout: 120 * time.Second,
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
},
},
MaxHeaderBytes: 1 << 20, // 1 MB
}
// Define routes
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Secure Echo server!")
})
// Start the custom server
e.Logger.Fatal(e.StartServer(s))
}
In this example, we configure:
- Read and write timeouts to prevent slow client attacks
- Idle timeout for connection management
- TLS settings for secure communication
- Maximum header size to prevent certain types of DOS attacks
Configuring the Echo Context
You can extend Echo's context to include custom data and behaviors:
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
// Custom context
type CustomContext struct {
echo.Context
UserID string
IsAdmin bool
}
// Middleware to create custom context
func customContextMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
cc := &CustomContext{
Context: c,
UserID: "anonymous", // Default value
IsAdmin: false, // Default value
}
// Example: Extract user info from JWT or session
// This would typically use real authentication logic
if auth := c.Request().Header.Get("Authorization"); auth == "admin-token" {
cc.UserID = "admin-user"
cc.IsAdmin = true
}
return next(cc)
}
}
func main() {
e := echo.New()
// Apply custom context middleware
e.Use(customContextMiddleware)
// Route that uses custom context
e.GET("/profile", func(c echo.Context) error {
cc := c.(*CustomContext) // Type assertion to access custom properties
data := map[string]interface{}{
"user_id": cc.UserID,
"is_admin": cc.IsAdmin,
}
return c.JSON(http.StatusOK, data)
})
e.Logger.Fatal(e.Start(":8080"))
}
This example demonstrates:
- Creating a custom context that extends Echo's context
- Adding custom properties to the context
- Using middleware to initialize the custom context
- Accessing the custom context in route handlers
Environment-Based Configuration
In production applications, you'll want to configure Echo differently based on your environment:
package main
import (
"net/http"
"os"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
// Get environment from ENV variable, default to development
env := os.Getenv("APP_ENV")
if env == "" {
env = "development"
}
// Base configuration
switch env {
case "production":
e.Debug = false
e.HideBanner = true
// Production middleware setup
e.Use(middleware.Recover())
e.Use(middleware.SecureWithConfig(middleware.SecureConfig{
XSSProtection: "1; mode=block",
ContentTypeNosniff: "nosniff",
XFrameOptions: "SAMEORIGIN",
HSTSMaxAge: 31536000, // 1 year
HSTSExcludeSubdomains: false,
ContentSecurityPolicy: "default-src 'self'",
}))
case "development":
e.Debug = true
// Development middleware setup - more verbose
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Format: "method=${method}, uri=${uri}, status=${status}, latency=${latency_human}\n",
}))
e.Use(middleware.Recover())
case "testing":
e.Debug = true
e.HideBanner = true
e.HidePort = true
// Minimal middleware for testing
e.Use(middleware.Recover())
}
// Common routes
e.GET("/", func(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"environment": env,
"status": "running",
})
})
// Start server
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
e.Logger.Fatal(e.Start(":" + port))
}
This example demonstrates:
- Loading configuration from environment variables
- Applying different configurations based on the environment
- Configuring middleware appropriately for each environment
- Using environment-specific security settings
Real-World Application: REST API Configuration
Let's look at a more comprehensive example of configuring Echo for a REST API:
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"time"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
type Config struct {
Port string
ShutdownTimeout time.Duration
CORSOrigins []string
RateLimit int
JWTSecret string
}
func loadConfig() Config {
// In a real application, this might load from environment variables,
// configuration files, or other sources
return Config{
Port: "8080",
ShutdownTimeout: 10 * time.Second,
CORSOrigins: []string{"https://example.com"},
RateLimit: 100, // requests per second
JWTSecret: "your-secret-key",
}
}
func main() {
// Load configuration
config := loadConfig()
// Create and configure Echo
e := echo.New()
e.Debug = true
// Configure middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: config.CORSOrigins,
AllowMethods: []string{http.MethodGet, http.MethodPut, http.MethodPost, http.MethodDelete},
}))
e.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{
Store: middleware.NewRateLimiterMemoryStore(config.RateLimit),
}))
e.Use(middleware.JWTWithConfig(middleware.JWTConfig{
SigningKey: []byte(config.JWTSecret),
Skipper: func(c echo.Context) bool {
// Skip authentication for login and public endpoints
return c.Path() == "/login" || c.Path() == "/public"
},
}))
// Configure custom error handler
e.HTTPErrorHandler = func(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)
}
// Don't log 404s as errors
if code != http.StatusNotFound {
e.Logger.Error(err)
}
c.JSON(code, map[string]string{
"error": message,
})
}
// Routes
e.GET("/public", func(c echo.Context) error {
return c.String(http.StatusOK, "Public endpoint")
})
e.POST("/login", func(c echo.Context) error {
// Authentication logic would go here
return c.String(http.StatusOK, "Login endpoint")
})
e.GET("/protected", func(c echo.Context) error {
return c.String(http.StatusOK, "Protected endpoint")
})
// Start server in a goroutine
go func() {
if err := e.Start(":" + config.Port); err != nil && err != http.ErrServerClosed {
e.Logger.Fatal("shutting down the server")
}
}()
// Wait for interrupt signal
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
<-quit
// Graceful shutdown
ctx, cancel := context.WithTimeout(context.Background(), config.ShutdownTimeout)
defer cancel()
if err := e.Shutdown(ctx); err != nil {
e.Logger.Fatal(err)
}
}
This comprehensive example demonstrates:
- Structured configuration management
- Multiple middleware configurations working together
- Route protection using JWT
- Custom error handling
- Graceful shutdown mechanism
Summary
Echo's configuration capabilities provide powerful tools to customize your web application's behavior:
- Basic server configuration: Debug mode, banners, HTTP/2 support
- Middleware configuration: Logging, recovery, security, rate limiting
- Server parameters: Timeouts, TLS settings, request limits
- Custom contexts: Extending Echo's context for application-specific data
- Environment-based configuration: Adapting settings based on development, testing, or production needs
- Comprehensive API configuration: Putting it all together for robust applications
By mastering Echo configuration, you can build web applications that are secure, performant, and tailored to your specific requirements.
Additional Resources
Exercises
- Create an Echo server with custom logging middleware that logs request duration and status code.
- Implement a configuration system that loads settings from a YAML file.
- Build an Echo application with different middleware configurations for API routes versus static file routes.
- Create a custom context that includes user authentication information and role-based permissions.
- Implement graceful shutdown handling with a timeout and proper cleanup of resources.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)