Keep Your GoFiber Handlers Clean with Middleware Validation
If you’ve been building APIs with GoFiber, you’ve probably had that moment where your route handler starts to get a little... messy. Especially when you’re doing request validation right inside the handler.
Let’s talk about a simple tip to clean that up using middleware — it’s easy, reusable, and your future self will thank you.
😬 The Messy Way (We've All Done It)
Here’s what a typical handler might look like at first:
app.Post("/users", func(c *fiber.Ctx) error { type UserRequest struct { Name string `json:"name"` Email string `json:"email"` } var body UserRequest if err := c.BodyParser(&body); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Invalid request body", }) } if body.Name == "" || body.Email == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Name and Email are required", }) } // Do the thing return c.JSON(fiber.Map{"message": "User created"}) })
This works fine at first, but as your API grows, stuffing all the parsing and validation into your handlers gets old real quick.
✨ The Cleaner Way: Middleware
Let’s move the validation logic out of the handler and into a middleware. That way, your handler can focus on doing what it’s actually meant to do.
🧱 Step 1: Define Your Request Struct
type UserRequest struct { Name string `json:"name"` Email string `json:"email"` }
🧼 Step 2: Write a Middleware to Validate It
func ValidateUserRequest(c *fiber.Ctx) error { var body UserRequest if err := c.BodyParser(&body); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Invalid JSON body", }) } if body.Name == "" || body.Email == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Name and Email are required", }) } // Pass the validated body to the next handler c.Locals("userBody", body) return c.Next() }
🧑🍳 Step 3: Use It in Your Route
app.Post("/users", ValidateUserRequest, func(c *fiber.Ctx) error { body := c.Locals("userBody").(UserRequest) // Clean and easy return c.JSON(fiber.Map{ "message": "User created", "user": body, }) })
Nice and tidy. You’re only dealing with the stuff you care about inside the handler.
🏆 Why This Rocks
- Separation of concerns – Validation and logic are in their own lanes.
- Reusability – You can reuse the same middleware for other routes.
- Testability – Easier to test your logic without worrying about request parsing every time.
💪 Bonus: Use a Validation Library
Want to step it up a notch? Add go-playground/validator
for fancy rules like email format, string length, etc.
go get github.com/go-playground/validator/v10
Then tweak your middleware:
import "github.com/go-playground/validator/v10" var validate = validator.New() type UserRequest struct { Name string `json:"name" validate:"required"` Email string `json:"email" validate:"required,email"` } func ValidateUserRequest(c *fiber.Ctx) error { var body UserRequest if err := c.BodyParser(&body); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid body"}) } if err := validate.Struct(body); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()}) } c.Locals("userBody", body) return c.Next() }
Now your validation is way more powerful, but still just as clean.
👋 Final Thoughts
Keeping your GoFiber handlers clean doesn’t have to be complicated. With a little middleware magic, you can offload the grunt work like validation and keep your handlers laser-focused.
Try it out on your next API project. Your code (and teammates) will be happier for it.