Building and Validating Web Endpoints with Fiber in Golang
Fiber, a web framework inspired by Express and built atop Fasthttp, the quickest HTTP engine for Go, is designed to simplify rapid development while keeping zero memory allocation and performance as key considerations.
In this blog post, we will learn how to create endpoints and middleware and validate requests with Fiber.
If you would rather learn via watching a video, here is the YouTube version of this post.
To start with Golang, first, let’s create an empty project with the go.mod
file.
module fiber
go 1.21
Install the Fiber library:
go get github.com/gofiber/fiber/v2
Creating a Simple GET Endpoint
Start by creating a main.go
file.
package main
import "github.com/gofiber/fiber/v2"
func main() {
app := fiber.New()
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("mfk test!")
})
app.Listen(":3000")
}
It is like a minimal API in dotnet.
app
is an instance of FiberMethod
is an HTTP request method:GET
,PUT
,POST
, etc.path
is a virtual path on the serverfunc(*fiber.Ctx) error
is a callback function containing theContext
executed when the route is matched
Now, let's compile our project.
go build main.go
Add Basic GET Routing
Each route can have multiple handler functions that are executed when the route is matched.
app.Get("/orders/code/:orderCode", func(c *fiber.Ctx) error {
return c.SendString("order code that you sent is: " + c.Params("orderCode"))
})
app.Get("/users/name/:name?", func(c *fiber.Ctx) error {
if c.Params("name") != "" {
return c.SendString("Hello " + c.Params("name"))
}
return c.SendString("Where is mfk?")
})
Add POST Routing
Here's how to handle POST requests:
app.Post("/orders", func(ctx *fiber.Ctx) error {
var request CreateOrderRequest
err := ctx.BodyParser(&request)
if err != nil {
return err
}
return ctx.Status(nethttp.StatusCreated).JSON("Order created successfully")
})
type CreateOrderRequest struct {
ShipmentNumber string `json:"shipmentNumber"`
}
curl --header "Content-Type: application/json" \\
--request POST \\
--data '{"shipmentNumber": "55555"}' \\
<http://localhost:3000/orders>
Middleware
Middleware functions can perform actions like logging or authentication.
You need to define your middleware before registering endpoints, or it won’t work.
Creating the First Middleware
This middleware logs every request when called.
app.Use(func(c *fiber.Ctx) error {
fmt.Println("Called " + string(c.Request().RequestURI()))
return c.Next()
})
Adding CorrelationId Middleware
- Example: Correlation Id middleware. This middleware gets
correlation-id
from the header and puts into Locals key-value, so you can access it from Context, if you want to.
app.Use("/orders/code/:orderCode", func(c *fiber.Ctx) error {
// middleware logic
var correlationId = c.Get("x-correlationid")
if correlationId == "" {
return c.Status(nethttp.StatusBadRequest).JSON("x-correlationid is mandatory")
}
_, err := uuid.Parse(correlationId)
if err != nil {
return c.Status(nethttp.StatusBadRequest).JSON("x-correlationid must be guid")
}
c.Locals("correlationId", correlationId)
return c.Next()
})
app.Get("/orders/code/:orderCode", func(c *fiber.Ctx) error {
// Updated GET route to demonstrate middleware usage
fmt.Printf("Your correlationId is %v", ctx.Locals("correlationId")) // Added this line to the function
return c.SendString("order code that you sent is: " + c.Params("orderCode"))
})
curl -i -H "Accept: application/json" -H "Content-Type: application/json" -H "x-correlationid: a4ebf613-4bc2-4ceb-8046-4042e31f333b" -X GET <http://localhost:3000/orders/code/mfkcode>
Recover Middleware
Use Fiber's recover
middleware for error handling. With this middleware, when you panic inside of an endpoint, the app is not going to crash.
"github.com/gofiber/fiber/v2/middleware/recover"
app.Use(recover.New())
app.Get("/", func(ctx *fiber.Ctx) error {
panic("Panic at the disco!")
return ctx.SendString("mfk test")
})
Error Handler Middleware
Customize error responses with an error handler. You can return customized Base Error Responses.
app := fiber.New(fiber.Config{
ErrorHandler: func(c *fiber.Ctx, err error) error {
code := fiber.StatusInternalServerError
var e *fiber.Error
if errors.As(err, &e) {
code = e.Code
}
c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
return c.Status(code).SendString("something happened at the disco!")
},
})
- There are a lot of written middleware in fiber. Check out the docs.
Validation
The last section in fiber is about request validation. In the real world, we always need to validate requests and return some actionable and structured custom error messages.
Validator library is de facto for this job, and it works with fiber.
First, let’s install the library.
go get github.com/go-playground/validator/v10
Now, let's define validation structures and custom validators. Then, Implement validation in our routes.
// You can write this section above main function
type ValidationError struct {
HasError bool
Field string
Tag string
Value interface{}
}
type CustomValidator struct {
validator *validator.Validate
}
var validate = validator.New()
func (v CustomValidator) Validate(data interface{}) []ValidationError {
var validationErrors []ValidationError
errs := validate.Struct(data)
if errs != nil {
for _, err := range errs.(validator.ValidationErrors) {
var ve ValidationError
ve.Field = err.Field()
ve.Tag = err.Tag()
ve.Value = err.Value()
ve.HasError = true
validationErrors = append(validationErrors, ve)
}
}
return validationErrors
}
// You can write this section in the beginning of main function
customValidator := &CustomValidator{
validator: validate,
}
Now, let's add validation tags to CreateOrderRequest
.
required
means not null, len
means length.
type CreateOrderRequest struct {
ShipmentNumber string `json:"shipmentNumber" validate:"required"`
// New Property
CountryCode string `json:"countryCode" validate:"required,len=2"`
}
Now we can validate the request in the Create Order POST endpoint.
if errs := customValidator.Validate(request); len(errs) > 0 && errs[0].HasError {
errorMessages := make([]string, 0)
for _, err2 := range errs {
errorMessages = append(errorMessages, fmt.Sprintf("%s field failed. Validation: '%s'", err2.Field, err2.Tag))
}
return ctx.Status(nethttp.StatusBadRequest).JSON(strings.Join(errorMessages, " and that "))
}
There are a lot of validate functions, but if you have custom logic, you can create your own validation tag as well.
// You can write this section after creating customValidator object.
customValidator.validator.RegisterValidation("oldAge", func(fl validator.FieldLevel) bool {
return fl.Field().Int() < 40
})
Let's try our new oldAge
validation tag. First, add a property to the CreateOrderRequest struct.
type CreateOrderRequest struct {
ShipmentNumber string `json:"shipmentNumber" validate:"required"`
CountryCode string `json:"countryCode" validate:"required,len=2"`
// New Property
Age int `json:"age" validate:"required,oldAge"`
}
Now, we can call our endpoint. First, without the age property, so we can see the error message.
curl --header "Content-Type: application/json" \\
--request POST \\
--data '{"shipmentNumber": "55555", "countryCode": "TR"}' \\
<http://localhost:3000/orders>
Response is:
"Age field has failed. Validation is: required"
Now, with the age
property.
curl --header "Content-Type: application/json" \\
--request POST \\
--data '{"shipmentNumber": "55555", "countryCode": "TR", "age": 45}' \\
<http://localhost:3000/orders>
This is how you can create endpoints and middleware and validate requests with Fiber. If you want to get the full source code, here it is.
May the force be with you!
Subscribe to my newsletter
Read articles from Firat Komurcu directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by