Building and Validating Web Endpoints with Fiber in Golang

Building and Validating Web Endpoints with Fiber in Golang

Learn creating endpoints, middleware and validate requests with Fiber

Featured on Hashnode

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

  • 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!