Building basic CRUD operations in Go with Fiber

Building basic CRUD operations in Go with Fiber

Building simple CRUD operations using Fiber. This article goes into detail about how to set up the API with multiple endpoints and working with a database to building a simple Bookmarking API.

Building basic CRUD operations in Go with Fiber

Fiber Basics

Fiber is a Go package that helps in building API for Web Development. It is inspired by Express framework from Node.js and built on top of https://github.com/valyala/fasthttp. If you have prior experience with building API with node.js, it will be easier to start with Fiber in Go.

Don’t worry if this is your first time building an API server, We will go into detail about how to set up the API with multiple endpoints and working with a database to building a simple Bookmarking API.

What are we building?

We are browsing the depths of the internet all day and more often than not, we will come up with links that are interesting. You might be familiar with the browser bookmarking feature which will add the link to the browser and we will replicate that feature in our application.

FiberAPI_building.jpg

We will have a simple data model for this app.

Name - User-defined name for the bookmark

URL - HTTP link of the website

type Bookmark struct {
	Name string
	Url  string
}

Creating the first endpoint

Let’s get started with setting up our Go application with go mod command. Create a new folder and initialize the go project.

mkdir bookmark-api-fiber
cd bookmark-api-fiber
go mod init <repo_name>

Adding the Fiber package

Install the Fiber package using the go get command

go get -u github.com/gofiber/fiber/v2

We can set up our server which will wait and listen for requests.

package main

import "github.com/gofiber/fiber/v2"

func main() {
    app := fiber.New()

    app.Get("/", func(c *fiber.Ctx) error {
        return c.SendString("Hello, World πŸ‘‹!")
    })

    app.Listen(":3000")
}

Let’s breakdown the above syntax to create a new API server.

app := fiber.New() // Create a new instance of Fiber
app.Get("/", func(c *fiber.Ctx) error {
    return c.SendString("Hello, World πŸ‘‹!")
})

Creating a GET endpoint for your application. This is a pattern from REST that will allow us to get some information from the server. If you are a beginner with REST, you can follow this link

https://www.smashingmagazine.com/2018/01/understanding-using-rest-api/

GET endpoints usually return some information needed by the client. In our example, we are returning the string β€œHello, World πŸ‘‹!”

 app.Listen(":3000")

Fiber server will need to run on a particular port when you start the application. You can define the port number and so this app will run in localhost:3000

Run the Fiber app

go run main.go

Output:
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” 
 β”‚                   Fiber v2.18.0                   β”‚ 
 β”‚               http://127.0.0.1:3000               β”‚ 
 β”‚       (bound on host 0.0.0.0 and port 3000)       β”‚ 
 β”‚                                                   β”‚ 
 β”‚ Handlers ............. 2  Processes ........... 1 β”‚ 
 β”‚ Prefork ....... Disabled  PID .............. 2019 β”‚ 
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Yay! we have started our server and this process will wait for any request which comes into the url

Creating a new package for bookmark logic

Create a new folder called bookmark and add all the logic of the bookmark inside that folder. This helps in separating the API routing logic and business logic and keeping the app nice and tidy.

	package bookmark
	
	import "github.com/gofiber/fiber/v2"
	
	func GetAllBookmarks(c *fiber.Ctx) error {
		return c.SendString("All Bookmarks")
	}
	
	func SaveBookmark(c *fiber.Ctx) error {
		return c.SendString("Bookmark Saved!")
	}

Update the main go file to import the bookmark package and use them in the routes

package main

import (
	"bookmark-api-fiber/bookmark"
	"github.com/gofiber/fiber/v2"
)

func status(c *fiber.Ctx) error {
	return c.SendString("Server is running! Send your request")
}

func setupRoutes(app *fiber.App) {

	app.Get("/", status)

	app.Get("/api/bookmark", bookmark.GetAllBookmarks)
	app.Post("/api/bookmark", bookmark.SaveBookmark)
}

func main() {
	app := fiber.New()

	setupRoutes(app)
	app.Listen(":3000")
}

Main function is split into multiple methods as well. Routing logic is made into a separate function called setupRoutes which takes the pointer to the Fiber app.

Default / route is set to return a status string. This can help to validate if your app is running properly.

Explanation of other routes

It is a convention when building API to prefix all the routes with /api which will clearly mark them as API. You can define all the REST endpoints for a particular resource using the resource name after that.

Our resource name is bookmark

We can define our REST verbs like below

  1. /api/bookmark - GET endpoint for list of bookmarks
  2. /api/bookmark - POST endpoint to save a new bookmark

Database setup

Let’s follow a similar pattern like bookmark package and create a new folder called database and create a file called database.go

Sqlite3

We will make use of sqlite3 for the database. It has a simple interface for saving and loading data and it gets stored in .db file in the project. This is not the best solution for production-ready apps but this will do for our demo to store and retrieve data.

Note from sqlite3 package: this is a CGO enabled package you are required to set the environment variable CGO_ENABLED=1 and have a gcc compile present within your path.

GORM - ORM for Go

We will use an ORM (Object-Relational-Mapping) to make our interaction with the database easier. If you opt for directly working with the database, you need to write database queries to perform operations on the database. ORM makes it easier by taking care of converting our requests into queries and giving a simple API for us to read, write data to the database.

More info on what is ORM - https://blog.bitsrc.io/what-is-an-orm-and-why-you-should-use-it-b2b6f75f5e2a

Installing sqlite3 and Gorm

go get -u github.com/mattn/go-sqlite3
go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite

Initialize the database with the model

We will use a table to store our data. In Sqlite3, we will need to define columns with types in the database. This can be made simple using ORM when we define the model we want to store. Gorm takes care of the heavy lifting of creating a table with the right columns.

We also get a few extra bookkeeping columns when creating the model with Gorm such as β€œID, CreatedAt, UpdatedAt, DeletedAt”

type Bookmark struct {
	gorm.Model
	Name string `json:"name"`
	Url  string `json:"url"`
}

Model is defined as a struct with JSON names that will be used when converting the model into JSON and sending it back to the client.

func InitDatabase() error {
	db, err := gorm.Open(sqlite.Open("bookmark.db"), &gorm.Config{})
	if err != nil {
		return err
	}

	db.AutoMigrate(&Bookmark{})

	return nil
}

InitDatabase function is defined which will try to establish a connection with the database. If the file is not present, it will create a new file with the name β€œbookmark.db”

AutoMigrate call helps in creating the table if it is not already present. Database migration are usually things that change the structure of the database over time and this helps in making sure that the database structure is properly migrated to the latest version.

Update the main.go file to initialize database

func main() {
	app := fiber.New()
	dbErr := database.InitDatabase()

	if dbErr != nil {
		panic(dbErr)
	}

	setupRoutes(app)
	app.Listen(":3000")
}

Post Request to add a bookmark

Let’s start by sending a POST request to the server to create a new bookmark to store in the database. Using Gorm we can simplify the process by calling the Create method on the database and passing in the necessary data needed.

Update the code in the bookmark.go file to send the request to the database

func SaveBookmark(c *fiber.Ctx) error {
	newBookmark := new(database.Bookmark)

	err := c.BodyParser(newBookmark)
	if err != nil {
		c.Status(400).JSON(&fiber.Map{
			"success": false,
			"message": err,
			"data":    nil,
		})
		return err
	}

	result, err := database.CreateBookmark(newBookmark.Name, newBookmark.Url)
	if err != nil {
		c.Status(400).JSON(&fiber.Map{
			"success": false,
			"message": err,
			"data":    nil,
		})
		return err
	}

	c.Status(200).JSON(&fiber.Map{
		"success": true,
		"message": "",
		"data":    result,
	})
	return nil
}

It is a good practice to return a predefined structure as a response to the API request along with Status code and message.

We can use the BodyParser function to convert the POST request data into our model format. We are sending that data to the database to create a new record

func CreateBookmark(name string, url string) (Bookmark, error) {
	var newBookmark = Bookmark{Name: name, Url: url}

	db, err := gorm.Open(sqlite.Open("bookmark.db"), &gorm.Config{})
	if err != nil {
		return newBookmark, err
	}
	db.Create(&Bookmark{Name: name, Url: url})

	return newBookmark, nil
}

POST Request to send bookmark data

I am using a VS Code extension β€œThunder Client” to send the POST request but you can use any tool which you are comfortable with and send the JSON to the POST request

{
    "name": "Go blog website",
    "url": "https://eternaldev.com"
}

A little bit of self-promotion there to save this site as your bookmark. This is the gist of thing to send a POST request and save the data to the database.

Getting the bookmark

Update the bookmark.go file for the GetAllBookmarks function as well.

func GetAllBookmarks(c *fiber.Ctx) error {
	result, err := database.GetAllBookmarks()
	if err != nil {
		return c.Status(500).JSON(&fiber.Map{
			"success": false,
			"message": err,
			"data":    nil,
		})
	}

	return c.Status(200).JSON(&fiber.Map{
		"success": true,
		"message": "",
		"data":    result,
	})
}

In our use case, the GET request does not need any data to be sent. We just want the list of all the bookmarks which is saved. So we can call the database to return all the bookmark data. Check for any error and then send the Status Code 500 if there is an error from the database call.

If there is no error we can send the data in a similar format and setting the success to true.

func GetAllBookmarks() ([]Bookmark, error) {
	var bookmarks []Bookmark

	db, err := gorm.Open(sqlite.Open("bookmark.db"), &gorm.Config{})
	if err != nil {
		return bookmarks, err
	}

	db.Find(&bookmarks)

	return bookmarks, nil
}

Update the database file to return a list of bookmarks from the database. Find function can be used to get the list of items. You just have to pass the variable to store the data.

GET Request to get bookmark data

Output of GET Request

{
  "data": [
    {
      "ID": 1,
      "CreatedAt": "2021-09-03T19:29:56.5726629+05:30",
      "UpdatedAt": "2021-09-03T19:29:56.5726629+05:30",
      "DeletedAt": null,
      "name": "Go blog website",
      "url": "https://eternaldev.com"
    },
    {
      "ID": 2,
      "CreatedAt": "2021-09-03T19:30:27.4942284+05:30",
      "UpdatedAt": "2021-09-03T19:30:27.4942284+05:30",
      "DeletedAt": null,
      "name": "Go official website",
      "url": "https://golang.org/"
    }
  ],
  "message": "",
  "success": true
}

Source code

https://github.com/eternaldevgames/bookmark-api-fiber

Summary

Fiber can help you in building API using golang and there are multiple ways to improve the above code. You can add updating and deleting the bookmark functionality. If you like this article, let us know on the Discord server and we can make it into a detailed series covering Fiber in Go

Stay tuned by subscribing to our mailing list and joining our Discord community

Discord