Routing & Handlers
The handler signature
Section titled “The handler signature”In Express a handler receives (req, res, next). In Echo every handler has a single, uniform signature:
func(c *echo.Context) error*echo.Context bundles the request, response writer, path parameters, query parameters, and helper methods. You return nil on success or an error to let Echo handle the failure response.
// Express handlerapp.get('/books/:id', (req, res, next) => { const id = req.params.id; const book = findBook(id); if (!book) return res.status(404).json({ message: 'not found' }); res.json(book);});// Echo handlerfunc getBook(c *echo.Context) error { id := c.Param("id") book, err := findBook(id) if err != nil { return echo.NewHTTPError(http.StatusNotFound, "not found") } return c.JSON(http.StatusOK, book)}Registering routes
Section titled “Registering routes”Echo’s routing API mirrors Express closely. Methods map directly to HTTP verbs.
// Expressapp.get('/books', listBooks);app.post('/books', createBook);app.get('/books/:id', getBook);app.put('/books/:id', updateBook);app.delete('/books/:id', deleteBook);// Echoe.GET("/books", listBooks)e.POST("/books", createBook)e.GET("/books/:id", getBook)e.PUT("/books/:id", updateBook)e.DELETE("/books/:id", deleteBook)Path parameters
Section titled “Path parameters”c.Param("name") returns the string value of a named segment. The colon syntax :name is identical to Express.
// Expressapp.get('/users/:id/posts/:postId', (req, res) => { const { id, postId } = req.params; res.json({ userId: id, postId });});// Echoe.GET("/users/:id/posts/:postId", func(c *echo.Context) error { userID := c.Param("id") postID := c.Param("postId") return c.JSON(http.StatusOK, map[string]string{ "userId": userID, "postId": postID, })})Query parameters
Section titled “Query parameters”c.QueryParam("name") reads a single query value. c.QueryParams() returns all of them as url.Values (a map[string][]string). c.QueryParamOr("page", "1") supplies a default.
// Expressapp.get('/books', (req, res) => { const { page = '1', limit = '10' } = req.query; res.json({ page, limit });});// Echoe.GET("/books", func(c *echo.Context) error { page := c.QueryParamOr("page", "1") limit := c.QueryParamOr("limit", "10") return c.JSON(http.StatusOK, map[string]string{ "page": page, "limit": limit, })})Typed parameters — no manual strconv
Section titled “Typed parameters — no manual strconv”In Express you convert req.params.id yourself. Echo v5 ships generic helpers that parse and validate in one call, returning an error you can turn into a 400:
e.GET("/items/:id", func(c *echo.Context) error { id, err := echo.PathParam[int](c, "id") if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "id must be an integer") } limit, err := echo.QueryParamOr[int](c, "limit", 10) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "limit must be an integer") } return c.JSON(http.StatusOK, map[string]int{"id": id, "limit": limit})})The same family exists for every source: echo.QueryParam[T], echo.QueryParams[T] (repeated keys), echo.FormValue[T], and echo.PathParamOr[T].
Attaching handlers to a struct (NestJS controller style)
Section titled “Attaching handlers to a struct (NestJS controller style)”NestJS controllers are classes. In Go the idiomatic equivalent is a struct whose methods are the handlers. Dependency injection happens through the struct’s fields.
// NestJS controller@Controller('books')export class BooksController { constructor(private readonly booksService: BooksService) {}
@Get(':id') findOne(@Param('id') id: string) { return this.booksService.findOne(id); }}// Echo — handler struct (manual DI)type BookHandler struct { store BookStore}
func NewBookHandler(s BookStore) *BookHandler { return &BookHandler{store: s}}
func (h *BookHandler) Get(c *echo.Context) error { id := c.Param("id") book, err := h.store.FindByID(c.Request().Context(), id) if err != nil { return echo.NewHTTPError(http.StatusNotFound, err.Error()) } return c.JSON(http.StatusOK, book)}
// Registration in main.go:// bh := NewBookHandler(store)// e.GET("/books/:id", bh.Get)Run this locally — it needs the Echo module and a network port.