Skip to main content

Echo Host Matching

Introduction

Host matching is a powerful feature in the Echo framework that allows you to route incoming HTTP requests based on the domain or host name in the request. This enables you to create multi-tenant applications, serve different content for different domains, or organize your API endpoints by subdomain—all within the same Echo application.

In this tutorial, we'll explore how to implement host matching in Echo, understand its use cases, and learn best practices for managing domain-based routing in your web applications.

Understanding Host Matching Basics

Host matching routes HTTP requests based on the Host header value in the incoming HTTP request. This means you can handle requests differently depending on which domain or subdomain the request is coming from.

For example, you might want to:

  • Serve different content for api.example.com versus www.example.com
  • Create multi-tenant applications where each customer gets their own subdomain
  • Implement versioning through subdomains like v1.api.example.com and v2.api.example.com

Implementing Host Matching in Echo

Echo provides a simple API to match routes based on host patterns. Let's look at the basic syntax:

go
// Create a new echo instance
e := echo.New()

// Define routes with host matching
e.GET("/", handler).Host("example.com")
e.GET("/", anotherHandler).Host("api.example.com")

In the example above, the same path / will be handled by different handlers depending on the host header.

Using Host Parameters

Echo supports host parameters, similar to path parameters, which can be used to capture parts of the hostname:

go
// Match any subdomain
e.GET("/users", listUsers).Host("*.example.com")

// Named parameters in host
e.GET("/", tenantHandler).Host(":tenant.example.com")

To access the host parameter in your handler:

go
func tenantHandler(c echo.Context) error {
tenant := c.Param("tenant") // Extracted from the host
return c.String(http.StatusOK, "Welcome to tenant: "+tenant)
}

Complete Example: Multi-Domain Application

Here's a comprehensive example of an Echo application with host matching:

go
package main

import (
"github.com/labstack/echo/v4"
"net/http"
)

func main() {
e := echo.New()

// Main website routes
e.GET("/", mainWelcomeHandler).Host("example.com")
e.GET("/about", mainAboutHandler).Host("example.com")

// API routes on subdomain
e.GET("/users", listUsersHandler).Host("api.example.com")
e.POST("/users", createUserHandler).Host("api.example.com")

// Multi-tenant routes with tenant parameter
e.GET("/", tenantHandler).Host(":tenant.example.com")

// Admin routes
e.GET("/*", adminHandler).Host("admin.example.com")

e.Logger.Fatal(e.Start(":8080"))
}

// Handler functions
func mainWelcomeHandler(c echo.Context) error {
return c.HTML(http.StatusOK, "<h1>Welcome to Example.com</h1>")
}

func mainAboutHandler(c echo.Context) error {
return c.HTML(http.StatusOK, "<h1>About Example.com</h1>")
}

func listUsersHandler(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{"message": "List of users from API"})
}

func createUserHandler(c echo.Context) error {
return c.JSON(http.StatusCreated, map[string]string{"message": "User created via API"})
}

func tenantHandler(c echo.Context) error {
tenant := c.Param("tenant")
return c.HTML(http.StatusOK, "<h1>Welcome to tenant: "+tenant+"</h1>")
}

func adminHandler(c echo.Context) error {
return c.HTML(http.StatusOK, "<h1>Admin Portal</h1><p>Path: "+c.Request().URL.Path+"</p>")
}

Testing Host Matching

To test host matching locally, you'll need to modify your /etc/hosts file (or equivalent) to add entries for your test domains, or use tools like cURL with the -H flag to set the host header:

bash
curl -H "Host: api.example.com" http://localhost:8080/users

You should see output like:

json
{"message":"List of users from API"}

And for a tenant subdomain:

bash
curl -H "Host: acme.example.com" http://localhost:8080/

Output:

html
<h1>Welcome to tenant: acme</h1>

Practical Use Cases

1. API Versioning Through Subdomains

You can use host matching to implement API versioning:

go
// API v1
e.GET("/users", v1ListUsersHandler).Host("v1.api.example.com")

// API v2
e.GET("/users", v2ListUsersHandler).Host("v2.api.example.com")

2. Multi-Tenant SaaS Application

For SaaS applications where each customer gets their own subdomain:

go
// Match specific tenants
e.GET("/*", specificTenantHandler).Host("premium.example.com")

// Match any other tenant
e.GET("/*", standardTenantHandler).Host(":tenant.example.com")

3. Environment-Based Routing

Different environments might have different behaviors:

go
e.GET("/api/*", prodHandler).Host("api.example.com")
e.GET("/api/*", stagingHandler).Host("api.staging.example.com")
e.GET("/api/*", devHandler).Host("api.dev.example.com")

Best Practices

  1. Group Your Routes: Organize routes by host to improve readability:
go
// API Group
apiHost := e.Group("", func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if c.Request().Host != "api.example.com" {
return echo.ErrNotFound
}
return next(c)
}
})
apiHost.GET("/users", listUsersHandler)
apiHost.POST("/users", createUserHandler)
  1. Use Middleware for Host-Specific Logic: Create middleware that runs only for specific hosts:
go
func hostMiddleware(allowedHost string) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if strings.HasSuffix(c.Request().Host, allowedHost) {
return next(c)
}
return echo.ErrForbidden
}
}
}

// Apply middleware
e.GET("/admin", adminHandler, hostMiddleware("admin.example.com"))
  1. Combine with Context Properties: Store host information in the context for use in handlers:
go
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
host := c.Request().Host
parts := strings.Split(host, ".")
if len(parts) >= 3 {
c.Set("subdomain", parts[0])
}
return next(c)
}
})

Summary

Echo's host matching feature provides a powerful way to route requests based on domain names. This allows you to build versatile applications that can handle multiple domains, implement multi-tenant architectures, or organize APIs by subdomain.

Key points to remember:

  • Use the .Host() method to attach host matchers to your routes
  • Host patterns can include wildcards and named parameters
  • You can combine host matching with path parameters and query strings
  • Host matching works well for API versioning and multi-tenant applications
  • Local testing requires setting host headers or modifying your hosts file

Exercises

  1. Create an Echo application that serves different content for "mobile.example.com" and "desktop.example.com".
  2. Implement a multi-tenant blog platform where each user gets their own subdomain (e.g., "johndoe.myblog.com").
  3. Create a versioned API using host matching with routes for "v1.api.example.com" and "v2.api.example.com".
  4. Implement a white-label service that serves different content based on the domain name.
  5. Create middleware that restricts access to certain routes based on the subdomain.

Additional Resources



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