{"id":1238,"hash":"112630211d3213bae0c29710d1ae2ffa4e9d8449a9c70e572c8fc53fe77e019b","pattern":"How to handle errors in Gin middleware","full_message":"I want to grab all http errors on each route without rewrite each time if 400 then if 404 then if 500 then etc... so I have an ErrorHandler() function inside each route handler:\n\nfunc (h *Handler) List(c *gin.Context) {\n    movies, err := h.service.ListService()\n\n    if err != nil {\n        utils.ErrorHandler(c, err)\n        return\n    }\n\n    c.JSON(http.StatusOK, movies)\n}\n\nThis function look like this:\n\nfunc ErrorHandler(c *gin.Context, err error) {\n    if err == ErrNotFound {\n        // 404\n        c.JSON(http.StatusNotFound, gin.H{\"error\": ErrNotFound.Error()})\n    } else if err == ErrInternalServerError {\n        // 500\n        c.JSON(http.StatusInternalServerError, gin.H{\"error\": ErrInternalServerError.Error()})\n    } // etc...\n}\n\nErrNotFound or ErrInternalServerError are just global variables initialized like this :\n\nvar ErrNotFound = errors.New(http.StatusText(http.StatusNotFound))  // 404\n\nI'd like to know if I'm doing right or if there is a better way to do this like grab the error inside the middleware and return directly the response ?\n\nWith node.js I was able to send err in the middleware parameter and use it like this:\n\napp.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {\n    if (err instanceof HttpError) {\n        res.status(err.status).json({error: err.message});\n      } else if (err instanceof Error) {\n        res.status(500).json({error: err.message});\n      } else {\n        res.status(500).send(\"Internal Server Error\");\n      }\n});\n\nThere is something similar ?","ecosystem":"go","package_name":"error-handling","package_version":null,"solution":"More idiomatic than using a function (utils is also frowned upon as a package name) is using a middleware:\n\nfunc ErrorHandler(c *gin.Context) {\n        c.Next()\n\n        for _, err := range c.Errors {\n            // log, handle, etc.\n        }\n    \n        c.JSON(http.StatusInternalServerError, \"\")\n}\n\nfunc main() {\n    router := gin.New()\n    router.Use(middleware.ErrorHandler)\n    // ... routes\n}\n\nNotably, you call c.Next() inside the middleware func before your actual error handling code, so you make sure the error handling happens after the rest of the handler chain has been called.\n\nNext should be used only inside middleware. It executes the pending handlers in the chain inside the calling handler. [...]\n\nThe advantage of using a middleware is that you can also pass arguments to it, e.g. a logger, that you may want to use later as part of the error handling, once, instead of passing it every time you call utils.ErrorHandler directly. In this case it looks like this (I use Uber Zap loggers):\n\nfunc ErrorHandler(logger *zap.Logger) gin.HandlerFunc {\n    return func(c *gin.Context) {\n        c.Next()\n        \n        for _, ginErr := range c.Errors {\n            logger.Error(\"whoops\", ...)\n        }\n    }\n}\n\nfunc main() {\n    router := gin.New()\n    \n    logger, _ := zap.NewDevelopment()\n\n    router.Use(middleware.ErrorHandler(logger))\n    // ... routes\n}\n\nThe handlers then will just abort the chain, instead of calling a function, which looks cleaner and it's easier to maintain:\n\nfunc (h *Handler) List(c *gin.Context) {\n    movies, err := h.service.ListService()\n\n    if err != nil {\n        c.AbortWithError(http.StatusInternalServerError, err)\n        return\n    }\n\n    c.JSON(http.StatusOK, movies)\n}\n\nIt's important to note that if you set an HTTP status in c.AbortWithStatus or c.AbortWithError, you may want to not overwrite it in the error handler. In that case, you can call c.JSON() with -1 as status code:\n\nfunc ErrorHandler(logger *zap.Logger) gin.HandlerFunc {\n    return func(c *gin.Context) {\n        c.Next()\n        \n        for _, ginErr := range c.Errors {\n            logger.Error(\"whoops\", ...)\n        }\n\n        // status -1 doesn't overwrite existing status code\n        c.JSON(-1, /* error payload */)\n    }\n}\n\nLastly, using a middleware allows you to call c.Error in your handlers multiple times, e.g. when a series of non-fatal errors occur and you want to capture all of them before actually aborting the request.\n\nError attaches an error to the current context. The error is pushed to a list of errors. It's a good idea to call Error for each error that occurred during the resolution of a request. A middleware can be used to collect all the errors and [process them]\n\nfunc (h *Handler) List(c *gin.Context) {\n    err1 := /* non-fatal error */\n    if err1 != nil {\n        c.Error(err1)\n    }\n\n    err2 := /* another non-fatal error */\n    if err2 != nil {\n        c.Error(err2)\n    }\n\n    fatalErr := /* fatal error */\n    if fatalErr != nil {\n        c.AbortWithError(505, fatalErr)\n        return\n        // the error handler will have collected all 3 errors\n    }\n\n    c.JSON(http.StatusOK, movies)\n}\n\nAs for the actual error handling in the middleware, it's pretty straightforward. Just remember that all calls to c.Error, c.AbortWith... will wrap your error in a gin.Error. So to inspect the original value you have to check the err.Err field:\n\nfunc ErrorHandler(c *gin.Context) {\n        c.Next()\n\n        for _, err := range c.Errors {\n            switch err.Err {\n                case ErrNotFound:\n                  c.JSON(-1, gin.H{\"error\": ErrNotFound.Error()})  \n            }\n            // etc...\n        }\n\n        c.JSON(http.StatusInternalServerError, \"\")\n}\n\nIterating over c.Errors may seem unwieldy because now you have potentially N errors instead of one, but depending on how you intend to use the middleware, you can simply check len(c.Errors) > 0 and access only the first item c.Errors[0].","confidence":0.95,"source":"stackoverflow","source_url":"https://stackoverflow.com/questions/69948784/how-to-handle-errors-in-gin-middleware","votes":19,"created_at":"2026-04-19T04:52:41.404033+00:00","updated_at":"2026-04-19T04:52:41.404033+00:00"}