Building and Validating Web Endpoints with Fiber in Golang

Mehmet Firat Komurcu
4 min readJan 25, 2024

--

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 Fiber
  • Method is an HTTP request method: GET, PUT, POST, etc.
  • path is a virtual path on the server
  • func(*fiber.Ctx) error is a callback function containing the Context 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

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!")
},
})

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!

Originally published at https://firatkomurcu.com on January 25, 2024.

--

--