- read

Building a Custom Middleware in Go Echo

Ruangyot Nanchiang 79

***In this article, I was wrong in some detail. Echo can implement a middleware as similiar as Fiber. You can skip to Edit section at the bottom of this article to see the solution of this.

Hi, Gopher Developers. I just moved from GOFiber to Echo for the reason I loved the logger feature of Echo.

The logger in Echo is in JSON format by default. This allows me to easily insert log data into the database without any implementation.

I have been stuck for a day with how to do the custom middleware in Echo, It’s not the same methodology as GOFiber.

Problem

In the GOFiber is easier to implement a middleware. You can put much middleware into the router function if you need such as app.GET("/", middleware1, middleware2, yourhandler). For each middleware, you can return an error response (If not just return c.Next() while c is *fiber.Context )

But in the Echo, we can’t do this, you need to return only next(c) while c is the echo.Context. If you still try to response an error into the middleware, the Echo is going to panic.

Okay, from now, I’m going to show you how to return an error in the middleware in Echo.

🔥Coding Time

First, just do a simple handler in Echo by the following

package main

import (
"net/http"

"github.com/labstack/echo/v4"
)

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

e.GET("/", SomeHandler)

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

func SomeHandler(c echo.Context) error {
return c.JSON(
http.StatusOK,
map[string]any{"message": "Hello, 世界 !"},
)
}

This handler is going to response as JSON as below.

{"message": "Hello, 世界 !"}

Now, so far so good, let’s implement the middleware to random an error for the example.

If the error occurs, This middleware is going to response an error, but if not it’s going to move next to the handler.

But you need to implement the handler to response an error from the middleware too.

SomeErrorHandler

func SomeErrorHandler(c echo.Context) error {
return c.JSON(
http.StatusInternalServerError,
map[string]any{"message": "Something went wrong !"},
)
}

SomeMiddleware

func SomeMiddleware(next, stop echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
n := rand.Intn(100)
if n%2 == 0 {
return stop(c)
}
return next(c)
}
}

You can include a handler as an argument like the code section above. If this middleware has an error occur it’s going to move to the stop(c) handler, but if not it’s going to move to the next(c) handler.

Now, Let’s change the code in func main() {} by the following.

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

e.GET("/", SomeMiddleware(SomeHandler, SomeErrorHandler))

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

Let’s look at the result.

Non-Error Response

$ curl http://localhost:1323
{"message":"Hello, 世界 !"}

Error Response

$ curl http://localhost:1323
{"message":"Something went wrong !"}

In case you need to custom a status code or error message, You implement do the following.

type (
MiddlewareHandler interface {
SomeMiddleware(next, stop echo.HandlerFunc) echo.HandlerFunc
SomeErrorHandler(c echo.Context) error
}

middleware struct {
code int
message string
}
)

func NewMiddleware() MiddlewareHandler {
return &middleware{}
}

func (m *middleware) SetError(code int, message string) {
m.code = code
m.message = message
}

func (m *middleware) SomeMiddleware(next, stop echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
n := rand.Intn(100)
if n%2 == 0 {
m.SetError(http.StatusBadRequest, "Custom Error")
return stop(c)
}
return next(c)
}
}

func (m *middleware) SomeErrorHandler(c echo.Context) error {
return c.JSON(
m.code,
map[string]any{"message": m.message},
)
}

For the usage.

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

m := NewMiddleware()
e.GET("/", m.SomeMiddleware(SomeHandler, m.SomeErrorHandler))

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

Let’s see the result again in case of error occurs.

$ curl http://localhost:1323
{"message":"Custom Error"}

The end. Thanks for your attention.

***Edit

Maybe I was wrong, but I still don’t know why when I write a middleware like Fiber style it will panic.

Someone code like this and it’s worked.

package main

import (
"fmt"
"math/rand"
"net/http"

"github.com/labstack/echo/v4"
)

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

e.GET("/", SomeHandler, SomeMiddleware)

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

func SomeHandler(c echo.Context) error {
fmt.Println("SomeHandler")

return c.JSON(
http.StatusOK,
map[string]any{"message": "Hello, 世界 !"},
)
}

func SomeMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
fmt.Println("SomeMiddleware")

if rand.Intn(100)%2 == 0 {
return c.JSON(
http.StatusBadRequest,
map[string]any{"message": "error"},
)
}

return next(c)
}
}