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) errorecho.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).
// 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.QueryParam("page") limit := c.QueryParam("limit") if page == "" { page = "1" } if limit == "" { limit = "10" } return c.JSON(http.StatusOK, map[string]string{ "page": page, "limit": limit, })})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.