If you are familiar with Express, you may recognize that Fiber is inspired by the awesome Node.js framework — except it is written in Go. Why?
Well, because Go is very fast, low on memory footprint, and highly performant for building scalable web servers and applications.
Fiber leverages these performance benefits and features. Firstly, it is based on the fasthttp package, which is the fastest HTTP client library in the Go ecosystem. From benchmark results, fasthttp is 10 times as fast as the net/http
native Go client package.
In this post, we are going to explore Fiber by looking at its features and components, such as routing, middleware support, and context. At the end of the day, we should then be able to apply these features and build a demo application that interacts with a database of our choice.
To easily follow along with this tutorial, we should have at least a basic knowledge of the Go programming language. It might also be beneficial to know a little bit of Express, as this could help in quickly understanding Fiber from an architecture point of view.
Also, make sure you have the Postgres.app for your OS of choice — you can download it here. Also, you can install any GUI client for Postgres. In this article, we will be using Postico, which you can download here.
Finally, make sure you have the latest version of Go installed on your machine. Instructions to do so can be found in the documentation.
In the coming section, we will talk briefly about the motivation behind Fiber. Let’s go.
As we mentioned earlier, Fiber was inspired by Express and takes on almost the same design and thinking. For example, this is a simple Fiber app:
package main import "github.com/gofiber/fiber" func main() { // Fiber instance app := fiber.New() // Routes app.Get("/", hello) // start server app.Listen(3000) } // Handler func hello(c *fiber.Ctx){ c.send("Hello, world!") } // Note: we can pass any other native listerner using the Serve method.
And this is a simple Express app:
const express = require('express') // Express instance const app = express() // Routes app.get('/', hello) // Start server app.listen(3000) // Handler function hello(req, res) { res.send('hello world!') })
Just like Express, this simple Fiber app above mirrors just the bare minimum needed to start a simple server. A really interesting feature is the use of the fasthttp RequestCtx
package, which basically helps with handling regular HTTP requests and responses, with all the methods we already know: req.query
, req.params
, req.body
, and so on.
Note that to run the above application in our development machines, all we need to do is make sure we have Go installed. After that, we can go ahead and create a new Go module:
go init github.com/firebase007/go-rest-api
Now we can go ahead and create a file in the root directly — let’s call it sample.go
. Then, we can paste the code above into the file we just created and run the go run sample.go
command to start our program. The output is shown below:
retina@alex go-rest-api % go mod init github.com/firebase007/go-rest-api-with-fiber go: creating new go.mod: module github.com/firebase007/go-rest-api-with-fiber retina@alex go-rest-api % go get -u github.com/gofiber/fiber go: finding github.com/gofiber/fiber v1.9.6 go: downloading github.com/gofiber/fiber v1.9.6 go: extracting github.com/gofiber/fiber v1.9.6 go: updates to go.mod needed, but contents have changed retina@alex go-rest-api % go run sample.go _______ __ ____ / ____(_) /_ ___ _____ _____ / /_ / / __ \/ _ \/ ___/ __ / __/ / / /_/ / __/ / /_/ /_/_.___/\___/_/ v1.9.6 Started listening on 0.0.0.0:3000
Note: Let’s not forget to import the Fiber package into our workspace. To do so, we can run:
go get -u github.com/gofiber/fiber
After these steps above, we can visit our browser on port 3000
to see that our app works. The browser renders the output shown below:
Hello, World!
Remember that after importing the Fiber package, app := fiber.New()
basically calls the New
function located in the app.go
file. This function accepts a pointer of optional settings
we can pass as arguments to our app on initialization. We can also look at how the newServer
method initializes the fasthttp
server on this line.
It is great to point out that Fiber is quickly becoming very popular as a framework for building web servers and applications in Go. It is gradually gaining huge momentum and traction from the Go community and developers alike for their APIs, and also for Node.js developers moving to Go.
As can be seen from the above example, it is quite easy and fast to create a simple Fiber app, just like Express. Let’s now learn more about Fiber by exploring its major component features and how they are implemented in Go.
Just like Express, Fiber comes with a highly performant router, which, like the Express router, has a callback function that runs for every request that matches a specific path on our server. Let’s see the signature:
// Function signature app.Method(path string, ...func(*fiber.Ctx))
Note that Method
represents regular HTTP methods — GET
, POST
, HEAD
, PUT
and so on. Path
represents the route we intend to match, and ...func(*fiber.Ctx)
represents a handler or callback that runs for that particular route. It is also important to note that we can have multiple handlers for a particular route, useful mainly when we intend to pass middleware functions for any purpose we intend.
As always, app
is an instance of a Fiber app. To serve static files, we can use the app.Static()
method. More details about routing in Fiber can be found in the docs. The implementation can be found in the Layer.go
, router.go
, and app.go
files in the GitHub repo.
Note: We can think of a route as one big ordered slice. When a request comes in, the first handler that matches the method name, path, and pattern would be executed. Also, based on the route matched at any particular time, we tend to know which middleware would be executed next.
Fiber already comes with some prebuilt middleware. Just as a recap, a middleware helps intercept and manipulate requests just before they get to a main handler or controller. Middleware functions are basically part of the request cycle/context, usually for performing certain actions.
Let’s see a very simple middleware example of a 404-handler
from the Go Fiber Recipes repo on GitHub:
package main import "github.com/gofiber/fiber" // handler function func handler() func(*fiber.Ctx) { return func(c *fiber.Ctx) {c.Send("This is a dummy route") }} func main() { // Create new Fiber instance app := fiber.New() // Create new sample GET routes app.Get("/demo", handler()) app.Get("/list", handler()) // Last middleware to match anything app.Use(func(c *fiber.Ctx) { c.SendStatus(404) // => 404 "Not Found" }) // Start server on http://localhost:3000 app.Listen(3000) }
This is a very simple usage of a middleware. In the above example, the middleware checks for routes that don’t match the ones registered. Just like Express, we can see that it is the last thing registered for our app with the app.Use()
method. Note that if we navigate to a route that isn’t /demo
or list
on the browser, we will get the error Not Found
.
The signature of the Use
method, which registers a middleware route, is shown below:
func (*fiber.App).Use(args ...interface{}) *fiber.App
This signifies an instance of a Fiber app, with the Use
method that accepts an empty interface as an argument. Again, a middleware would match a request beginning with the provided prefix, and if none is provided, it defaults to "/"
. Finally, there are a bunch of other middleware functions available in this section of the documentation. You can check them out to learn more.
As we mentioned earlier, context holds the HTTP request and response, with methods for request query, parameters, body, and so on. The most basic example we can quickly relate with is using the Body
method — just like when we do req.body
in Express.
In Fiber, the signature for the context Body
method is shown below:
c.Body() string // type string
Here’s a simple use case:
// curl -X POST http://localhost:8080 -d user=john app.Post("/", func(c *fiber.Ctx) { // Get raw body from POST request c.Body() // user=john })
More details about other methods available in the context package can be found here in the docs.
By now, we have explored how routing
works in Fiber, and we have also looked at the middleware support and context. Now let’s use all these features and work our way through building a Fiber application that interacts with a database.
In this section, we will explore our own way of structuring a scalable Fiber application and, in the process, learn about implementing Fiber’s core features. In this demo, we will be making use of the pq
package, which is a pure Go Postgres driver for the database/sql
package. We can check it our here on Go’s package repository.
Also, we will be making use of two middleware packages, basicauth
and logger
, which are part of Fiber’s supported inbuilt middleware. To begin, we need to initialize a new Go module with the following command:
go init github.com/firebase007/go-rest-api-with-fiber
Then we can go ahead and install the following packages using the go get
command. At the end of the day, our go.mod
file should look like this:
module github.com/firebase007/go-rest-api-with-fiber go 1.13 require ( github.com/gofiber/basicauth v0.0.3 github.com/gofiber/fiber v1.9.6 github.com/gofiber/logger v0.0.8 github.com/joho/godotenv v1.3.0 github.com/klauspost/compress v1.10.5 // indirect github.com/lib/pq v1.5.2 )
Now we are ready to start a new Fiber project. After navigating into our module directory, we can go ahead and create a main.go
file in the root path. Here’s how it should look:
package main import ( "github.com/gofiber/fiber" // import the fiber package "log" "github.com/gofiber/fiber/middleware" "github.com/firebase007/go-rest-api-with-fiber/database" "github.com/firebase007/go-rest-api-with-fiber/router" _ "github.com/lib/pq" ) // entry point to our program func main() { // Connect to database if err := database.Connect(); err != nil { log.Fatal(err) } // call the New() method - used to instantiate a new Fiber App app := fiber.New() // Middleware app.Use(middleware.Logger()) router.SetupRoutes(app) // listen on port 3000 app.Listen(3000) }
Here, we are importing the Fiber package and two other packages we have created inside our project directory: router
and database
. Before we proceed, here is a screenshot of our project directory:
In the main
function, we have instantiated the Connect
function from the database
package. The contents of our database package are shown below:
package database import ( "database/sql" "fmt" "strconv" "github.com/firebase007/go-rest-api-with-fiber/config" ) // Database instance var DB *sql.DB // Connect function func Connect() error { var err error p := config.Config("DB_PORT") // because our config function returns a string, we are parsing our str to int here port,err := strconv.ParseUint(p, 10, 32) if err != nil { fmt.Println("Error parsing str to int") } DB, err = sql.Open("postgres", fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", config.Config("DB_HOST"), port, config.Config("DB_USER"), config.Config("DB_PASSWORD"), config.Config("DB_NAME"))) if err != nil { return err } if err = DB.Ping(); err != nil { return err } CreateProductTable() fmt.Println("Connection Opened to Database") return nil }
It exports a single method that connects to our SQL database using the pg
driver package. Note that after we are successfully connected to our database, we are calling a CreateProductTable()
function, which, as the name implies, creates a new database table for us. The content of the file schema.go
, still in our database package, is shown below:
package database // CreateProductTable ... func CreateProductTable() { DB.Query(`CREATE TABLE IF NOT EXISTS products ( id SERIAL PRIMARY KEY, amount integer, name text UNIQUE, description text, category text NOT NULL ) `) }
Note that this function helps to create a new table in our database (if it doesn’t already exist). Earlier in our database file, we imported the config
package, which takes care of returning env values based on their respective keys. The content of that file is shown below:
package config import ( "github.com/joho/godotenv" "os" "fmt" ) // Config func to get env value from key --- func Config(key string) string{ // load .env file err := godotenv.Load(".env") if err != nil { fmt.Print("Error loading .env file") } return os.Getenv(key) }
The sample.env
file contains our secrets required for our database connection, as well as the username
and password
secret keys required for our basic-auth middleware package (for authenticating our routes). You can see its contents below:
DB_HOST=localhost DB_PORT=5432 DB_USER=postgres DB_PASSWORD= DB_NAME= USERNAME= PASSWORD=
After we are done with the setup and connecting to our database, we can see that we are also importing and initializing the SetupRoutes
function in our main
package. This function helps with setting up our routes. The content of the router
package is shown below:
package router import ( "github.com/firebase007/go-rest-api-with-fiber/handler" "github.com/firebase007/go-rest-api-with-fiber/middleware" "github.com/gofiber/fiber" ) // SetupRoutes func func SetupRoutes (app *fiber.App) { // Middleware api := app.Group("/api", logger.New(), middleware.AuthReq()) // routes api.Get("/", handler.GetAllProducts) api.Get("/:id", handler.GetSingleProduct) api.Post("/", handler.CreateProduct) api.Delete("/:id", handler.DeleteProduct) }
As we can see from the package file above, we are importing two packages: the handler
and middleware
packages. The middleware
package contains an AuthReq
function that returns a basic-auth config. The content of the package is shown below:
package middleware import ( "github.com/gofiber/fiber" "github.com/gofiber/basicauth" "github.com/firebase007/go-rest-api-with-fiber/config" ) // AuthReq middleware func AuthReq() func(*fiber.Ctx) { cfg := basicauth.Config{ Users: map[string]string{ config.Config("USERNAME"): config.Config("PASSWORD"), }, } err := basicauth.New(cfg); return err }
Note that the app.Group()
method is used for grouping routes by creating a *Group
struct. The signature is shown below:
app.Group(prefix string, handlers ...func(*Ctx)) *Group
From the routes file above, we are also calling our handler
package, which contains functions that will be called when a route matches an appropriate path. The content of the handler
package is shown below:
package handler import ( "log" "database/sql" "github.com/gofiber/fiber" "github.com/firebase007/go-rest-api-with-fiber/model" "github.com/firebase007/go-rest-api-with-fiber/database" ) // GetAllProducts from db func GetAllProducts(c *fiber.Ctx) { // query product table in the database rows, err := database.DB.Query("SELECT name, description, category, amount FROM products order by name") if err != nil { c.Status(500).JSON(&fiber.Map{ "success": false, "error": err, }) return } defer rows.Close() result := model.Products{} for rows.Next() { product := model.Product{} err := rows.Scan(&product.Name, &product.Description, &product.Category, &product.Amount) // Exit if we get an error if err != nil { c.Status(500).JSON(&fiber.Map{ "success": false, "error": err, }) return } // Append Product to Products result.Products = append(result.Products, product) } // Return Products in JSON format if err := c.JSON(&fiber.Map{ "success": true, "product": result, "message": "All product returned successfully", }); err != nil { c.Status(500).JSON(&fiber.Map{ "success": false, "message": err, }) return } } // GetSingleProduct from db func GetSingleProduct(c *fiber.Ctx) { id := c.Params("id") product := model.Product{} // query product database row, err := database.DB.Query("SELECT * FROM products WHERE id = $1", id) if err != nil { c.Status(500).JSON(&fiber.Map{ "success": false, "message": err, }) return } defer row.Close() // iterate through the values of the row for row.Next() { switch err := row.Scan(&id, &product.Amount, &product.Name, &product.Description, &product.Category ); err { case sql.ErrNoRows: log.Println("No rows were returned!") c.Status(500).JSON(&fiber.Map{ "success": false, "message": err, }) case nil: log.Println(product.Name, product.Description, product.Category, product.Amount) default: // panic(err) c.Status(500).JSON(&fiber.Map{ "success": false, "message": err, }) } } // return product in JSON format if err := c.JSON(&fiber.Map{ "success": false, "message": "Successfully fetched product", "product": product, }); err != nil { c.Status(500).JSON(&fiber.Map{ "success": false, "message": err, }) return } } // CreateProduct handler func CreateProduct(c *fiber.Ctx) { // Instantiate new Product struct p := new(model.Product) // Parse body into product struct if err := c.BodyParser(p); err != nil { log.Println(err) c.Status(400).JSON(&fiber.Map{ "success": false, "message": err, }) return } // Insert Product into database res, err := database.DB.Query("INSERT INTO products (name, description, category, amount) VALUES ($1, $2, $3, $4)" , p.Name, p.Description, p.Category, p.Amount ) if err != nil { c.Status(500).JSON(&fiber.Map{ "success": false, "message": err, }) return } // Print result log.Println(res) // Return Product in JSON format if err := c.JSON(&fiber.Map{ "success": true, "message": "Product successfully created", "product": p, }); err != nil { c.Status(500).JSON(&fiber.Map{ "success": false, "message": "Error creating product", }) return } } // DeleteProduct from db func DeleteProduct(c *fiber.Ctx) { id := c.Params("id") // query product table in database res, err := database.DB.Query("DELETE FROM products WHERE id = $1", id) if err != nil { c.Status(500).JSON(&fiber.Map{ "success": false, "error": err, }) return } // Print result log.Println(res) // return product in JSON format if err := c.JSON(&fiber.Map{ "success": true, "message": "product deleted successfully", }); err != nil { c.Status(500).JSON(&fiber.Map{ "success": false, "error": err, }) return } }
We are also importing our database
and model
packages from the handler
package above. One thing to note is that Fiber comes with the fiber.Map()
method, which is basically a shortcut for map[string]interface{}
. More details about the project can be found on the GitHub repo.
To start the API, run go run main.go
on the project root directory. Also, a POSTMAN collection is available if you intend to try out the endpoints for our API.
As an example, using POSTMAN to create a new product is shown below:
We can also visualize our database records with the newly created products using Postico, as below:
Fiber is gaining some solid momentum and finding traction with both Go developers and Node.js developers moving to Go as a programming language.
As we have seen, Fiber is extremely easy to use — just like Express. It also comes with fasthttp methods under the hood, which gives it an edge in terms of performance. We have also explored some of Fiber’s most important features, which include support for middlewares (including third-party), just like Express.
Finally, Fiber is optimized for high-speed backend API development with Go. It offers support for static files, a prefork feature settings, templating engines, WebSockets, testing, and many more. The documentation is the best place to check out these awesome features.
Thanks again and please reach out to me on Twitter if you have any questions, or use the comment box below. Would be glad to answer them. 🙂
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowUser expectations for web applications have evolved. Users expect seamless and smooth experiences when navigating between pages; they want the […]
Let’s discuss Svelte’s history and key features, why you might choose it for your next project, and what sets it apart from other frameworks.
The latest release of the monorepo tool Turborepo includes a new terminal UI, Watch Mode, and completely new docs.
If you’ve been active on Twitter or Reddit lately, you’ve likely seen discussions surrounding Vercel’s pricing model, which has turned […]
5 Replies to "Building an Express-style API in Go with Fiber"
hi, how do you hot reload the go fiber server while developing the same way we use nodemon in nodejs ? thanks
you can use air, which hot compiles and reloads. go is so fast to compile it is a viable option.
fiber is compiler language, can’t hot reload
Nope, because Golang is compiler language, must be restart service
Hopefully not many people will take this as good practice: aka a global pointer to a db connection that’s going to be directly shared (and potentially changed) across goroutines.