Global Error Handling via Middleware with Go's Gin Framework

Posted on Wednesday, 1st June 2022

A common, repetitive theme when building HTTP APIs is the process of translating application level errors into the appropriate HTTP status code. For resources that can return a number of status codes this tends to involve checking the type of the err value then returning the necessary status code. Couple this will the number of resources presented by your application and you’ve got a lot of error handling that could, in my opinion, be better solved in a centralised manner as a cross-cutting concern via middleware. This is where Gin Error Handling Middleware comes in, a simple Gin middleware library I wrote.

Handling errors as part of your HTTP API’s middleware enables the following benefits:

  • Centralised location for handling errors
  • Reduce boilerplate ‘error to response’ mappings in your request handlers or controller actions
  • Helps protect yourself from inadvertently revealing errors to API consumers

A Trivial Example

Let’s take a look at how we can use the Gin Error Handling Middleware package to utilise a more centralised approach to error handling.

First add the package to your application:

go get github.com/JosephWoodward/gin-errorhandling

Let’s take a look at a bare-bones example where we register a custom NotFoundError, mapping it to a 404 (http.StatusNotFound) error.

...

var (
    NotFoundError = fmt.Errorf("resource could not be found")
)

func main() {
    r := gin.Default()

    // Register middleware and specify your errors to handle
    r.Use(
        ErrorHandler(
            Map(NotFoundError).ToStatusCode(http.StatusNotFound),
        ))

    r.GET("/test", func(c *gin.Context) {
        // Return our typed error via Gin's Error function
        c.Error(NotFoundError)
    })

    r.Run()
}

Now when you make an HTTP request to /test you should see the following response:

HTTP/1.1 404 Not Found
Date: Wed, 16 Feb 2022 04:08:50 GMT
Content-Length: 0
Connection: close

Going further

So far we’ve seen how we can use the Gin error handling middleware to translate a custom error into a specific status code. If we still want control over the response body returned to the consumer (we want to return a custom error to the caller for instance) then we can use the ToResponse function which allows us to pass a function literal which passes the gin.Context as an argument:

...

var (
    NotFoundError = fmt.Errorf("resource could not be found")
)

func main() {
    r := gin.Default()
    r.Use(
        ErrorHandler(
            Map(NotFoundError).ToResponse(func(c *gin.Context, err error) {
                c.Status(http.StatusNotFound)
                c.Writer.Write([]byte(err.Error())),
            }),
        ))

    r.GET("/ping", func(c *gin.Context) {
        c.Error(NotFoundError)
    })

    r.Run()
}

In this case the caller receives the following HTTP response to their request to /ping

HTTP/1.1 404 Not Found
Date: Wed, 16 Feb 2022 04:21:37 GMT
Content-Length: 27
Content-Type: text/plain; charset=utf-8
Connection: close

resource could not be found

On Closing

Hopefully this post has adequately demonstrated the advantages of error handling via Gin middleware with Gin Error Handling Middleware. Whilst still in its infancy hopefully the library will prove helpful.