Back

Building a RESTful API with Go and Fiber

Nikita Khabya

Nikita Khabya

8 min read

|

2 months ago

Go ecosystem has various frameworks like Gin, Echo, Chi, Fiber and more. Among these the most popular one in terms of Simplicity and Performance is Fiber.

Fiber is inspired by Express.js (Node.js framework) due to which Node.js developers have an "Ah-ha!" moment when they get started with Fiber. Let's explore Fiber in more detail and get our hands dirty by building a RESTful API with it.

In the last blog, we explored building a RESTful API using the Gin framework. If you haven't checked it out yet, you can find it here.

About Fiber ⚡️

Specific to Fiber~

Building Your Server 🚀

We'll build a RESTful API to manage a simple library of books stored in memory. The API will support the following endpoints:

Let's dive in!

Before writing code, ensure you have Go installed on your system. Create a new directory for your project, initialize a Go module, and install the Fiber package.

bash
~ $ mkdir books-api

~ $ cd books-api

~/books-api $ go mod init books-api

~/books-api $ go get github.com/gofiber/fiber/v2

1. Start simple

Let's start with a minimal Fiber server to test the setup. Create a file named main.go:

go
package main

import (
    "log"
    "github.com/gofiber/fiber/v2"
)

func main() {
    app := fiber.New() // Create a new Fiber app

    // Define a simple route
    app.Get("/", func(c *fiber.Ctx) error {
        return c.JSON(fiber.Map{
            "message": "Welcome to our Books API! 📚",
        })
    })

    log.Fatal(app.Listen(":8080")) // Start server on port 8080
}

Run the server using the following command:

bash
~/books-api $ go run main.go

You can setup air for live reloading during development. Check out air

Open your browser and navigate to http://localhost:8080. You should see the following output:

json
{
    "message": "Welcome to our Books API! 📚"
}

2. Define the Book Model and In-Memory Store

💡 This example uses an in-memory store for simplicity. In production, you’d likely use a database (e.g. PostgreSQL, MySQL, MongoDB) with an ORM like GORM.

go
package main

import (
    "errors"
    "log"
    "strconv"
    "strings"
    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/fiber/v2/middleware/cors"
    "github.com/gofiber/fiber/v2/middleware/logger"
)

// Book represents a book in our library
type Book struct {
    ID            int    `json:"id"`
    Title         string `json:"title"`
    Author        string `json:"author"`
    Genre         string `json:"genre"`
    Pages         int    `json:"pages"`
    Available     bool   `json:"available"`
    PublishedYear int    `json:"published_year"`
}

// In-memory store for books
var library = []Book{
    {ID: 1, Title: "The Go Programming Language", Author: "Alan Donovan", Genre: "Technology", Pages: 380, Available: true, PublishedYear: 2015},
    {ID: 2, Title: "Clean Code", Author: "Robert Martin", Genre: "Technology", Pages: 464, Available: false, PublishedYear: 2008},
    {ID: 3, Title: "The Pragmatic Programmer", Author: "Dave Thomas", Genre: "Technology", Pages: 352, Available: true, PublishedYear: 1999},
}

var nextBookID = 4 // Next ID for new books

3. Writing the API Endpoints

3.1. GET /books

This endpoint retrieves all books, with optional search and filtering by genre and availability.

go
func getAllBooks(c *fiber.Ctx) error {
    result := library
    
    // for search and filter logics refer to the complete code

    return c.Status(fiber.StatusOK).JSON(fiber.Map{
        "books": result,
        "total": len(result),
    })
}

3.2. GET /books/:id

This endpoint retrieves a specific book by its ID.

go
func findBookByID(id int) (*Book, int, error) {
    for i, book := range library {
        if book.ID == id {
            return &book, i, nil
        }
    }
    return nil, -1, errors.New("book not found")
}

func getBook(c *fiber.Ctx) error {
    idStr := c.Params("id")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
            "error": "Invalid book ID",
        })
    }
    
    book, _, err := findBookByID(id)
    if err != nil {
        return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
            "error": "Book not found",
        })
    }
    
    return c.Status(fiber.StatusOK).JSON(book)
}

3.3. Adding New Books

go
func createBook(c *fiber.Ctx) error {
    var newBook Book
    
    // Parse the JSON body into our Book struct
    if err := c.BodyParser(&newBook); err != nil {
        return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
            "error": "Cannot parse JSON",
            "details": err.Error(),
        })
    }
    
    // Basic validation
    if newBook.Title == "" || newBook.Author == "" {
        return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
            "error": "Title and Author are required",
        })
    }
    
    // Assign ID and add to library
    newBook.ID = nextBookID
    nextBookID++
    library = append(library, newBook)
    
    return c.Status(fiber.StatusCreated).JSON(fiber.Map{
        "message": "Book added successfully",
        "book": newBook,
    })
}

To check the Update and Delete endpoints, you can refer to the complete code.

4. Wiring Everything Together 🔌

Now let's put it all together with a global error handler, middleware, and route definitions in the main.go file:

go
func main() {
    // Create Fiber app with custom error handler
    app := fiber.New(fiber.Config{
        ErrorHandler: func(c *fiber.Ctx, err error) error {
            code := fiber.StatusInternalServerError
            if e, ok := err.(*fiber.Error); ok {
                code = e.Code
            }
            return c.Status(code).JSON(fiber.Map{
                "error": err.Error(),
            })
        },
    })

    // Middleware
    app.Use(logger.New())
    app.Use(cors.New())

    // Routes
    app.Get("/books", getAllBooks)
    app.Post("/books", createBook)
    app.Get("/books/:id", getBook)
    app.Put("/books/:id", updateBook)
    app.Delete("/books/:id", deleteBook)

    // Start server
    log.Fatal(app.Listen(":8080"))
}

For in-depth testing, consider using tools like Postman or Thunder client.

TL;DR