Centralized Error Handling in Go (Like Laravel)



This content originally appeared on DEV Community and was authored by Harry Douglas

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
}

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"`
}

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
}

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}
}

Now, you can easily throw:

panic(exceptions.Unprocessable("Email is required"))

✅ 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)
    })
}

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))
}

✅ 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
}

New way (cleaner):

if email == "" {
    panic(exceptions.Unprocessable("Email is required"))
}

That’s it!

✅ Example Output

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

{
  "code": 422,
  "message": "Email is required"
}

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

Instead of repeating this everywhere:

w.WriteHeader(...)
json.NewEncoder(w).Encode(...)

You just call:

panic(exceptions.Unprocessable("message"))

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


This content originally appeared on DEV Community and was authored by Harry Douglas