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.
Enjoy this post? Don't be a stranger!
Follow me on Twitter at @_josephwoodward and say Hi! I love to learn in the open, meet others in the community and talk Go, software engineering and distributed systems related topics.