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.
    
Top comments (0)