Skip to main content

Routing

Echo's router is based on radix tree, making route lookup really fast. It leverages sync pool to reuse memory and achieve zero dynamic memory allocation with no GC overhead.

Routes can be registered by specifying HTTP method, path and a matching handler. For example, code below registers a route for method GET, path /hello and a handler which sends Hello, World! HTTP response.

// Handler
funchello(c *echo.Context)error{
return c.String(http.StatusOK,"Hello, World!")
}

// Route
e.GET("/hello", hello)

HTTP Methods

Echo supports all standard HTTP methods. Each method follows the signature: e.METHOD(path string, h HandlerFunc, middleware ...Middleware) echo.RouteInfo

Available HTTP Methods

MethodSignatureDescription
GETe.GET(path, handler, ...middleware)Retrieve data
POSTe.POST(path, handler, ...middleware)Create new resource
PUTe.PUT(path, handler, ...middleware)Update entire resource
PATCHe.PATCH(path, handler, ...middleware)Partial update of resource
DELETEe.DELETE(path, handler, ...middleware)Remove resource
HEADe.HEAD(path, handler, ...middleware)Get headers only
OPTIONSe.OPTIONS(path, handler, ...middleware)Get allowed methods
CONNECTe.CONNECT(path, handler, ...middleware)Connect method
TRACEe.TRACE(path, handler, ...middleware)Trace method
RouteNotFounde.TRACE(path, handler, ...middleware)In case router did not found matching route (404)
Anye.TRACE(path, handler, ...middleware)Matches any request method. Lower priority than other handlers

Method Parameters

  • path (string): The route pattern (e.g., /users/:id, /api/*)
  • handler (HandlerFunc): Function that handles the request
  • middleware (...Middleware): Optional middleware functions

Handler Function Signature

Echo defines handler functions as func(c *echo.Context) error where:

  • *echo.Context: Provides access to:
    • Request and response objects
    • Path parameters (c.Param("id"))
    • Query parameters (c.QueryParam("name"))
    • Form data (c.FormValue("field"))
    • JSON binding (c.Bind(&struct{}))
    • Response helpers (c.JSON(), c.String(), etc.)
    • etc

Example Usage

// GET request
e.GET("/users/:id", getUser)

// POST request with JSON body
e.POST("/users", createUser)

// PUT request for updates
e.PUT("/users/:id", updateUser)

// DELETE request
e.DELETE("/users/:id", deleteUser)

// PATCH request for partial updates
e.PATCH("/users/:id", patchUser)

// HEAD request (same as GET but without body)
e.HEAD("/users/:id", getUser)

// OPTIONS request for CORS
e.OPTIONS("/users", handleOptions)

Route Registration Methods

You can use Echo.Any(path string, h Handler) to register a handler for all HTTP methods. If you want to register it for some methods use Echo.Match(methods []string, path string, h Handler).

Echo.Any()

Registers a handler for all HTTP methods on a given path:

e.Any("/api/*",func(c *echo.Context)error{
return c.String(http.StatusOK,"Handles all methods")
})

Echo.Match()

Registers a handler for specific HTTP methods:

// Handle both GET and POST
e.Match([]string{"GET","POST"},"/users", userHandler)

// Handle multiple methods
e.Match([]string{"PUT","PATCH"},"/users/:id", updateHandler)

Route Parameters

Echo supports various types of route parameters for flexible URL patterns.

Path Parameters

Path parameters are defined using :paramName syntax and can be accessed in handlers using c.Param("paramName").

// Single parameter
e.GET("/users/:id",func(c *echo.Context)error{
id := c.Param("id")
return c.String(http.StatusOK,"User ID: "+ id)
})

// genetic type function to convert string to int
e.GET("/users/:id",func(c *echo.Context)error{
ID, err := echo.PathParam[int](c,"id")
//ID, err := echo.PathParamOr[int](c, "id", -1) // default value -1
if err !=nil{
return err
}
return c.String(http.StatusOK, fmt.Sprintf("User ID: %d", ID))
})

// Multiple parameters
e.GET("/users/:id/posts/:postId",func(c *echo.Context)error{
userId := c.Param("id")
postId := c.Param("postId")
return c.String(http.StatusOK,"User: "+ userId +", Post: "+ postId)
})

// Optional parameters (using query parameters instead)
e.GET("/users",func(c *echo.Context)error{
id := c.QueryParam("id")// Optional
return c.String(http.StatusOK,"User ID: "+ id)
})

Query Parameters

Query parameters are accessed using c.QueryParam("name") and are optional by nature:

e.GET("/search",func(c *echo.Context)error{
query := c.QueryParam("q")
// typed generic function to get value other type than string
//limit, err := echo.QueryParam[int](c, "limit")
limit, err := echo.QueryParamOr[int](c,"limit",100)// default value -1
if err !=nil{
return err
}
return c.String(http.StatusOK, fmt.Sprintf("Search query: %s, Limit: %d", query, limit))
})

Example URLs:

  • /search?q=golang&limit=10
  • /users/123?include=posts&limit=5

Form Parameters

For POST/PUT requests with form data, use c.FormValue("field"):

e.POST("/users",func(c *echo.Context)error{
name := c.FormValue("name")
email := c.FormValue("email")
age, err := echo.FormValueOr[int](c,"age",-1)// default value -1
if err !=nil{
return err
}
return c.String(http.StatusOK, fmt.Sprintf("Name: %s, Email: %s, Age: %d", name, email, age))
})

Match-any / wildcard

Matches zero or more characters in the path. For example, pattern /users/* will match:

  • /users/
  • /users/1
  • /users/1/files/1
  • /users/anything...
caution

There can be only one effective match-any parameter in route. When route is added with multiple match-any /v1/*/images/*. The router matches always the first * till the end of request URL i.e. it works as /v1/*.

Path Matching Order

  • Static
  • Param
  • Match any

Example

e.GET("/users/:id",func(c *echo.Context)error{
return c.String(http.StatusOK,"/users/:id")
})

e.GET("/users/new",func(c *echo.Context)error{
return c.String(http.StatusOK,"/users/new")
})

e.GET("/users/1/files/*",func(c *echo.Context)error{
return c.String(http.StatusOK,"/users/1/files/*")
})

Above routes would resolve in the following order:

  • /users/new
  • /users/:id
  • /users/1/files/*
tip

Routes can be written in any order.

Group

Echo#Group(prefix string, m ...Middleware) *Group

Routes with common prefix can be grouped to define a new sub-router with optional middleware. In addition to specified middleware group also inherits parent middleware. To add middleware later in the group you can use Group.Use(m ...Middleware). Groups can also be nested.

Basic Group Usage

// Create a group with prefix
api := e.Group("/api")

// Add routes to the group
api.GET("/users", getUsers)
api.POST("/users", createUser)
api.GET("/users/:id", getUser)

Group with Middleware

// Admin group with authentication
admin := e.Group("/admin")
admin.Use(middleware.BasicAuth(func(c *echo.Context, username, password string)(bool,error){
// Use constant-time comparison to prevent timing attacks
userMatch := subtle.ConstantTimeCompare([]byte(username),[]byte("joe"))==1
passMatch := subtle.ConstantTimeCompare([]byte(password),[]byte("secret"))==1

return userMatch && passMatch,nil
}))

// All routes under /admin/* will require authentication
admin.GET("/dashboard", dashboard)
admin.GET("/users", adminUsers)
admin.POST("/users", createUser)

Advanced Group Examples

// API v1 group with logging and CORS
apiV1 := e.Group("/api/v1")
apiV1.Use(middleware.RequestLogger())
apiV1.Use(middleware.CORS("https://api.example.com"))

apiV1.GET("/users", getUsers)
apiV1.POST("/users", createUser)

// API v2 group with different middleware
apiV2 := e.Group("/api/v2")
apiV2.Use(middleware.RequestLogger())
apiV2.Use(middleware.CORS("https://api.example.com"))
apiV2.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(20)))

apiV2.GET("/users", getUsersV2)
apiV2.POST("/users", createUserV2)

// Nested groups
admin := e.Group("/admin")
admin.Use(middleware.BasicAuth(...))

// Admin users sub-group
adminUsers := admin.Group("/users")
adminUsers.Use(middleware.RoleBasedAuth("admin"))
adminUsers.GET("/", listUsers)
adminUsers.POST("/", createUser)

Group Middleware Management

// Create group without initial middleware
api := e.Group("/api")

// Add middleware later
api.Use(middleware.RequestLogger())
api.Use(middleware.Recover())

// Add routes
api.GET("/health", healthCheck)
api.GET("/users", getUsers)

// Add more middleware to specific routes
api.GET("/sensitive", sensitiveHandler, middleware.AuthRequired())

Practical Example: RESTful API Structure

funcsetupRoutes(e *echo.Echo){
// Public routes
e.GET("/", homeHandler)
e.GET("/login", loginPage)
e.POST("/login", loginHandler)

// API group with common middleware
api := e.Group("/api")
api.Use(middleware.RequestLogger())
api.Use(middleware.Recover())
api.Use(middleware.CORS("https://api.example.com"))

// Public API routes
api.GET("/health", healthCheck)
api.POST("/register", registerUser)

// Protected API routes
protected := api.Group("")
protected.Use(middleware.JWT([]byte("secret")))

protected.GET("/users", getUsers)
protected.POST("/users", createUser)
protected.GET("/users/:id", getUser)
protected.PUT("/users/:id", updateUser)
protected.DELETE("/users/:id", deleteUser)

// Admin API routes
admin := protected.Group("/admin")
admin.Use(middleware.RoleBasedAuth("admin"))

admin.GET("/stats", getStats)
admin.POST("/users/:id/ban", banUser)
}

Group Benefits

  1. Middleware Inheritance: Groups inherit parent middleware
  2. URL Organization: Logical grouping of related routes
  3. Middleware Reuse: Apply common middleware to multiple routes
  4. Nested Structure: Create hierarchical route structures
  5. Easy Maintenance: Modify group middleware affects all routes

Route Naming

Each of the registration methods returns a Route object, which can be used to name a route after the registration. Route names are useful for generating URIs from templates, avoiding hardcoded URLs, and when you have multiple routes with the same handler.

Basic Route Naming

_, err := e.AddRoute(echo.Route{
Method: http.MethodGet,
Path:"/users/:id",
Name:"user_details",
Handler:func(c *echo.Context)error{
return c.String(http.StatusOK, fmt.Sprintf("User ID: %s", c.Param("id")))
},
Middlewares:nil,
})

URI Building

Echo provides two methods for generating URIs: Echo.URI() and Echo.Reverse().

Echo.URI() - Handler-based URI Generation

Echo.URI(handler HandlerFunc, params ...any) generates URIs based on handler functions:

// Define handlers
funcgetUser(c *echo.Context)error{
return c.String(http.StatusOK,"User")
}

funcgetUserPost(c *echo.Context)error{
return c.String(http.StatusOK,"User Post")
}

// Register routes
e.GET("/users/:id", getUser)
e.GET("/users/:id/posts/:postId", getUserPost)

// Generate URIs
userURI := e.URI(getUser,123)// "/users/123"
postURI := e.URI(getUserPost,123,456)// "/users/123/posts/456"

Router.Reverse() - Name-based URI Generation

echo.Router.Reverse(name string, params ...any) generates URIs based on route names:

route := echo.Route{
Method: http.MethodGet,
Path:"/users/:id",
Name:"user_details",
Handler:func(c *echo.Context)error{
return c.String(http.StatusOK, fmt.Sprintf("User ID: %s", c.Param("id")))
},
Middlewares:nil,
}
_, err := e.AddRoute(route)

userURI, err := e.Router().Routes().Reverse("user_details",123)// "/users/123"

Benefits of Route Naming

  1. URL Centralization: Change URLs in one place
  2. Template Integration: Generate links in HTML templates
  3. Refactoring Safety: Rename handlers without breaking links
  4. API Documentation: Generate API documentation with correct URLs

List Routes

Echo#Router#Routes() echo.Routes can be used to list all registered routes in the order they are defined. Each route contains HTTP method, path and an associated handler.

Example: Listing Routes

// Handlers
funccreateUser(c *echo.Context)error{
}

funcfindUser(c *echo.Context)error{
}

funcupdateUser(c *echo.Context)error{
}

funcdeleteUser(c *echo.Context)error{
}

// Routes
e.POST("/users", createUser)
e.GET("/users", findUser)
e.PUT("/users", updateUser)
e.DELETE("/users", deleteUser)

Using the following code you can output all the routes to a JSON file:

data, err := json.MarshalIndent(e.Router().Routes(),""," ")
if err !=nil{
return err
}
os.WriteFile("routes.json", data,0644)

routes.json

[
{
"Method":"POST",
"Path":"/users",
"Name":"main.createUser"
},
{
"Method":"GET",
"Path":"/users",
"Name":"main.findUser"
},
{
"Method":"PUT",
"Path":"/users",
"Name":"main.updateUser"
},
{
"Method":"DELETE",
"Path":"/users",
"Name":"main.deleteUser"
}
]

AltStyle によって変換されたページ (->オリジナル) /