Eternal Dev
Posted on September 4, 2021
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.
Originally published at https://eternaldev.com/blog/building-basic-crud-operations-in-go-with-fiber
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.
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
- /api/bookmark - GET endpoint for list of bookmarks
- /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
}
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.
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
Posted on September 4, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.