DEV Community

Harry Douglas
Harry Douglas

Posted on

Centralized Error Handling in Go (Like Laravel)

When building an API in Go, you may notice you keep writing this kind of code everywhere:

if email == "" {
    validationError := responses.Response{
        Code:    422,
        Message: "Invalid email",
    }
    w.WriteHeader(http.StatusUnprocessableEntity)
    json.NewEncoder(w).Encode(validationError)
    return
}
Enter fullscreen mode Exit fullscreen mode

This gets repetitive, messy, and hard to manage as your project grows.

If you're coming from Laravel, you're used to something like throw new Exception(...), and having Laravel automatically return a proper JSON response.

In Go, you can achieve the same thing using:

  • A custom error type
  • Middleware
  • panic() + recover()

Let’s build it together 👇


✅ Goal

  • Stop repeating the same error-handling code
  • Throw errors in one line
  • Always return a clean JSON response
  • Centralize the logic in one place

✅ Step 1: Define a Common Error Response Format

Create a file like responses/Response.go:

package responses

type Response struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
}
Enter fullscreen mode Exit fullscreen mode

This is what your API will return to the client.


✅ Step 2: Create Custom Error Types

In exceptions/ApiError.go, define your custom error structure:

package exceptions

import "net/http"

type APIError struct {
    Code    int
    Message string
}

func (e APIError) Error() string {
    return e.Message
}
Enter fullscreen mode Exit fullscreen mode

Also add helper functions for common error types:

func NotFound(message string) APIError {
    return APIError{Code: http.StatusNotFound, Message: message}
}

func Unprocessable(message string) APIError {
    return APIError{Code: http.StatusUnprocessableEntity, Message: message}
}
Enter fullscreen mode Exit fullscreen mode

Now, you can easily throw:

panic(exceptions.Unprocessable("Email is required"))
Enter fullscreen mode Exit fullscreen mode

✅ Step 3: Global Middleware to Catch Panics and Return JSON

Create a recovery middleware:

package middleware

import (
    "encoding/json"
    "net/http"

    "yourapp/exceptions"
    "yourapp/responses"
)

func RecoveryMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if rec := recover(); rec != nil {
                code := http.StatusInternalServerError
                message := "Internal Server Error"

                if apiErr, ok := rec.(exceptions.APIError); ok {
                    code = apiErr.Code
                    message = apiErr.Message
                }

                w.Header().Set("Content-Type", "application/json")
                w.WriteHeader(code)
                json.NewEncoder(w).Encode(responses.ErrorResponse{
                    Code:    code,
                    Message: message,
                })
            }
        }()

        next.ServeHTTP(w, r)
    })
}
Enter fullscreen mode Exit fullscreen mode

Now, all panics will be caught and turned into clean JSON.


✅ Step 4: Use the Middleware in main.go

package main

import (
    "net/http"
    "yourapp/handlers/auth"
    "yourapp/middleware"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/auth/email", auth.EmailHandler)

    http.ListenAndServe(":8080", middleware.RecoveryMiddleware(mux))
}
Enter fullscreen mode Exit fullscreen mode

✅ Step 5: Throw Errors in Your Handler Instead of Writing Response Code

Old way (repetitive):

if email == "" {
    w.WriteHeader(http.StatusUnprocessableEntity)
    json.NewEncoder(w).Encode(responses.ErrorResponse{
        Code: 422,
        Message: "Email is required",
    })
    return
}
Enter fullscreen mode Exit fullscreen mode

New way (cleaner):

if email == "" {
    panic(exceptions.Unprocessable("Email is required"))
}
Enter fullscreen mode Exit fullscreen mode

That’s it!


✅ Example Output

If the user submits an empty email, they will get:

{
  "code": 422,
  "message": "Email is required"
}
Enter fullscreen mode Exit fullscreen mode

This approach gives you Laravel-style centralized error handling in Go.

Instead of repeating this everywhere:

w.WriteHeader(...)
json.NewEncoder(w).Encode(...)
Enter fullscreen mode Exit fullscreen mode

You just call:

panic(exceptions.Unprocessable("message"))
Enter fullscreen mode Exit fullscreen mode

And let your middleware handle the rest. Clean, consistent, and scalable.

Top comments (0)