Build a REST API with Golang and MongoDB - Fiber Version
Tags: go, fiber, mongodb, restapi
Published: 2022-01-03 07:20:12

---
title: Build a REST API with Golang and MongoDB - Fiber Version
published: true
description: 
tags: #go #fiber #mongodb #restapi
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/54mlchx3crpexwhbowqa.png
---


Representational state transfer (REST) is an architectural pattern that guides an Application programming interface(API) design and development. REST APIs have become the standard of communication between the server part of the product and its client to increase performance, scalability, simplicity, modifiability, visibility, portability, and reliability.

This post will discuss building a user management application with Golang using the [Fiber](https://github.com/gofiber/fiber) framework and [MongoDB](https://www.mongodb.com/). At the end of this tutorial, we will learn how to structure a Fiber application, build a REST API and persist our data using MongoDB.

Fiber is an [Express](https://github.com/expressjs/express) inspired HTTP web framework written in Golang with performance and zero memory allocation support. Fiber is built on top of [Fasthttp](https://github.com/valyala/fasthttp), an HTTP engine written in Golang.

MongoDB is a document-based database management program used as an alternative to relational databases. MongoDB supports working with large sets of distributed data with options to store or retrieve information seamlessly.

You can find the complete source code in this [repository](https://github.com/Mr-Malomz/fiber-mongo-api).

# Prerequisites

The following steps in this post require Golang experience. Experience with MongoDB isn’t a requirement, but it’s nice to have.

We will also be needing the following:

- A [MongoDB account](https://www.mongodb.com/) to host database. [**Signup**](https://www.mongodb.com/cloud/atlas/register) **is completely free**.
- [Postman](https://www.postman.com/downloads/) or any API testing application of your choice


# Let’s code
## Getting Started

To get started, we need to navigate to the desired directory and run the command below in our terminal 


    mkdir fiber-mongo-api && cd fiber-mongo-api

This command creates a  `fiber-mongo-api` folder and navigates into the project directory.

Next, we need to initialize a Go module to manage project dependencies by running the command below:


    go mod init fiber-mongo-api

This command will create a `go.mod` file for tracking project dependencies.

We proceed to install the required dependencies with:


    go get -u github.com/gofiber/fiber/v2 go.mongodb.org/mongo-driver/mongo github.com/joho/godotenv github.com/go-playground/validator/v10

`github.com/gofiber/fiber/v2` is a framework for building web application.

`go.mongodb.org/mongo-driver/mongo` is a driver for connecting to MongoDB.

`github.com/joho/godotenv` is a library for managing environment variable.

`github.com/go-playground/validator/v10` is a library for validating structs and fields.

After installing the required dependencies, we might get `github.com/klauspost/compress is not in your go.mod filego mod tidy` error in `go.mod` file. To fix this, we need to manually install the required package with

    go get github.com/klauspost/compress

## Application Entry Point

With the project dependencies installed, we need to create `main.go` file in the root directory and add the snippet below:

```go
package main

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

func main() {
    app := fiber.New()
  
    app.Get("/", func(c *fiber.Ctx) error {
        return c.JSON(&fiber.Map{"data": "Hello from Fiber & mongoDB"})
    })
  
    app.Listen(":6000")
}
```

The snippet above does the following:

- Import the required dependencies.
- Initialize a Fiber application using the `New` function.
- Use the `Get` function to route to `/` path and an handler function that returns a JSON of `Hello from Fiber & mongoDB`. `fiber.Map` is a shortcut for `map[string]interface{}`, useful for JSON returns.
- Set the application to listen on port `6000`.

Next, we can test our application by starting the development server by running the command below in our terminal.


    go run main.go


![Testing the app](https://paper-attachments.dropbox.com/s_97A8BAFA9C176AA31F7FC2C18065AA1FE2F74B0C6537A26A8D511A9956341968_1640616832121_Screenshot+2021-12-27+at+15.39.28.png)



## Modularization in Golang

It is essential to have a good folder structure for our project. Good project structure simplifies how we work with dependencies in our application and makes it easier for us and others to read our codebase.
To do this, we need to create  `configs`, `controllers`, `models`, `responses` and `routes` folder in our project directory.

![Updated project folder structure](https://paper-attachments.dropbox.com/s_97A8BAFA9C176AA31F7FC2C18065AA1FE2F74B0C6537A26A8D511A9956341968_1640618264604_Screenshot+2021-12-27+at+16.17.26.png)


**PS**: *The* `go.sum` *file contains all the dependency checksums, and is managed by the* `go` *tools. We don’t have to worry about it.*

`configs` is for modularizing project configuration files

`controllers` is for modularizing application logics.

`models` is for modularizing data and database logics.

`responses` is for modularizing files describing the response we want our API to give. This will become clearer later on.

`routes` is for modularizing URL pattern and handler information.


## Setting up MongoDB

With that done, we need to log in or sign up into our [MongoDB](http://) account. Click the project dropdown menu and click on the **New Project** button.

![New Project](https://paper-attachments.dropbox.com/s_B58C50BB61437E91E681B5825981F54733906EC9A6070DFB4A21109C66F0E3FA_1637365380025_Screenshot+2021-11-20+at+00.33.27.png)


Enter the `golang-api` as the project name, click **Next**, and click **Create Project..**

![enter project name](https://paper-attachments.dropbox.com/s_0E637C74C0D58C5FC3FBA1D21E2CF4F11E7592B7929DAE10790A9D12FE972D1D_1640511449262_Screenshot+2021-12-26+at+10.35.59.png)
![Create Project](https://paper-attachments.dropbox.com/s_0E637C74C0D58C5FC3FBA1D21E2CF4F11E7592B7929DAE10790A9D12FE972D1D_1640511449329_Screenshot+2021-12-26+at+10.36.27.png)


Click on **Build a Database**

![](https://paper-attachments.dropbox.com/s_0E637C74C0D58C5FC3FBA1D21E2CF4F11E7592B7929DAE10790A9D12FE972D1D_1640511574285_Screenshot+2021-12-26+at+10.38.59.png)


Select **Shared** as the type of database.

![Shared highlighted in red](https://paper-attachments.dropbox.com/s_B58C50BB61437E91E681B5825981F54733906EC9A6070DFB4A21109C66F0E3FA_1637334670330_Screenshot+2021-11-19+at+15.59.38.png)


Click on **Create** to setup a cluster. This might take sometime to setup.

![Creating a cluster](https://paper-attachments.dropbox.com/s_B58C50BB61437E91E681B5825981F54733906EC9A6070DFB4A21109C66F0E3FA_1637334961608_Screenshot+2021-11-19+at+16.13.28.png)


Next, we need to create a user to access the database externally by inputting the **Username**, **Password** and then clicking on **Create User**. We also need to add our IP address to safely connect to the database by clicking on the **Add My Current IP Address** button. Then click on **Finish and Close** to save changes.

![Create user](https://paper-attachments.dropbox.com/s_0E637C74C0D58C5FC3FBA1D21E2CF4F11E7592B7929DAE10790A9D12FE972D1D_1640512109914_Screenshot+2021-12-26+at+10.43.36.png)
![Add IP](https://paper-attachments.dropbox.com/s_0E637C74C0D58C5FC3FBA1D21E2CF4F11E7592B7929DAE10790A9D12FE972D1D_1640512118747_Screenshot+2021-12-26+at+10.43.52.png)


On saving the changes, we should see a Database Deployments screen, as shown below:

![Database Screen](https://paper-attachments.dropbox.com/s_0E637C74C0D58C5FC3FBA1D21E2CF4F11E7592B7929DAE10790A9D12FE972D1D_1640512328028_Screenshot+2021-12-26+at+10.51.34.png)

## Connecting our application to MongoDB

With the configuration done, we need to connect our application with the database created. To do this, click on the **Connect** button

![Connect to database](https://paper-attachments.dropbox.com/s_0E637C74C0D58C5FC3FBA1D21E2CF4F11E7592B7929DAE10790A9D12FE972D1D_1640512936968_Screenshot+2021-12-26+at+10.51.34.png)


Click on **Connect your application**, change the **Driver** to `Go` and the **Version** as shown below. Then click on the copy **icon** to copy the connection string.

![connect application](https://paper-attachments.dropbox.com/s_0E637C74C0D58C5FC3FBA1D21E2CF4F11E7592B7929DAE10790A9D12FE972D1D_1640513302462_Screenshot+2021-12-26+at+11.03.40.png)
![Copy connection string](https://paper-attachments.dropbox.com/s_0E637C74C0D58C5FC3FBA1D21E2CF4F11E7592B7929DAE10790A9D12FE972D1D_1640513302542_Screenshot+2021-12-26+at+11.04.29.png)


**Setup Environment Variable**
Next, we must modify the copied connection string with the user's password we created earlier and change the database name. To do this, first, we need to create a `.env` file in the root directory, and in this file, add the snippet below:


    MONGOURI=mongodb+srv://<YOUR USERNAME HERE>:<YOUR PASSWORD HERE>@cluster0.e5akf.mongodb.net/myFirstDatabese?retryWrites=true&w=majority

Sample of a properly filled connection string below:

    MONGOURI=mongodb+srv://malomz:malomzPassword@cluster0.e5akf.mongodb.net/golangDB?retryWrites=true&w=majority


![Updated folder structure with .env file](https://paper-attachments.dropbox.com/s_97A8BAFA9C176AA31F7FC2C18065AA1FE2F74B0C6537A26A8D511A9956341968_1640646363920_Screenshot+2021-12-28+at+00.05.44.png)


**Load Environment Variable**
With that done, we need to create a helper function to load the environment variable using the `github.com/joho/godotenv` library we installed earlier.  To do this, we need to navigate to the `configs` folder and in this folder, create an `env.go` file and add the snippet below:



```go
package configs

import (
    "log"
    "os"
    "github.com/joho/godotenv"
)

func EnvMongoURI() string {
    err := godotenv.Load()
    if err != nil {
        log.Fatal("Error loading .env file")
    }
  
    return os.Getenv("MONGOURI")
}
```

The snippet above does the following:

- Import the required dependencies.
- Create an `EnvMongoURI` function that checks if the environment variable is correctly loaded and returns the environment variable.

**Connecting to MongoDB** 
To connect to the MongoDB database from our application, first we need to navigate to the `configs` folder and in this folder, create a `setup.go` file and add the snippet below:



```go
package configs

import (
    "context"
    "fmt"
    "log"
    "time"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

func ConnectDB() *mongo.Client  {
    client, err := mongo.NewClient(options.Client().ApplyURI(EnvMongoURI()))
    if err != nil {
        log.Fatal(err)
    }
  
    ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
    err = client.Connect(ctx)
    if err != nil {
        log.Fatal(err)
    }

    //ping the database
    err = client.Ping(ctx, nil)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Connected to MongoDB")
    return client
}

//Client instance
var DB *mongo.Client = ConnectDB()

//getting database collections
func GetCollection(client *mongo.Client, collectionName string) *mongo.Collection {
    collection := client.Database("golangAPI").Collection(collectionName)
    return collection
}
```

The snippet above does the following:

- Import the required dependencies.
- Create a `ConnectDB` function that first configures the client to use the correct URI and check for errors. Secondly, we defined a timeout of 10 seconds we wanted to use when trying to connect. Thirdly, check if there is an error while connecting to the database and cancel the connection if the connecting period exceeds 10 seconds. Finally, we pinged the database to test our connection and returned the `client` instance.
- Create a `DB` variable instance of the `ConnectDB`. This will come in handy when creating collections.
- Create a `GetCollection` function to retrieve and create `collections` on the database.

Next, we need to connect to the database when our application startup. To do this, we need to modify `main.go` as shown below:


```go
package main

import (
    "fiber-mongo-api/configs" //add this
    "github.com/gofiber/fiber/v2" 
)

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

    //run database
    configs.ConnectDB()

    app.Listen(":6000")
}
```


## Setup API Route Handler and Response Type

**Route Handler**
With that done, we need to create a  `user_route.go` file inside the `routes` folder to manage all the user-related routes in our application, as shown below:


```go
package routes

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

func UserRoute(app *fiber.App) {
    //All routes related to users comes here
}
```

Next, we need to attach the newly created route to the **http.Server** in `main.go` by modifying it as shown below:


```go
package main

import (
    "fiber-mongo-api/configs"
    "fiber-mongo-api/routes" //add this
    "github.com/gofiber/fiber/v2"
)

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

    //run database
    configs.ConnectDB()

    //routes
    routes.UserRoute(app) //add this

    app.Listen(":6000")
}
```

**Response Type**
Next, we need to create a reusable `struct` to describe our API’s response. To do this, navigate to the `responses` folder and in this folder, create a `user_response.go` file and add the snippet below:


```go
package responses

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

type UserResponse struct {
    Status  int        `json:"status"`
    Message string     `json:"message"`
    Data    *fiber.Map `json:"data"`
}
```

The snippet above creates a `UserResponse` struct with `Status`, `Message`, and `Data` property to represent the API response type.

**PS**: `json:"status"`*,* `json:"message"`*, and* `json:"data"` *are known as* ***struct tags****. Struct tags allow us to attach meta-information to corresponding struct properties. In other words, we use them to reformat the JSON response returned by the API.*


## Finally, Creating REST API’s

Next, we need a model to represent our application data. To do this, we need to navigate to the `models` folder, and in this folder, create a `user_model.go` file and add the snippet below:


```go
package models

import "go.mongodb.org/mongo-driver/bson/primitive"

type User struct {
    Id       primitive.ObjectID `json:"id,omitempty"`
    Name     string             `json:"name,omitempty" validate:"required"`
    Location string             `json:"location,omitempty" validate:"required"`
    Title    string             `json:"title,omitempty" validate:"required"`
}
```

The snippet above does the following:

- Import the required dependencies.
- Create a `User` struct with required properties. We added `omitempty` and `validate:"required"` to the struct tag to tell Fiber to ignore empty fields and make the field required, respectively.

**Create a User Endpoint**
With the model setup, we can now create a function to create a user. To do this, we need to navigate to the `controllers` folder, and in this folder, create a `user_controller.go` file and add the snippet below:


```go
package controllers

import (
    "context"
    "fiber-mongo-api/configs"
    "fiber-mongo-api/models"
    "fiber-mongo-api/responses"
    "net/http"
    "time"
  
    "github.com/go-playground/validator/v10"
    "github.com/gofiber/fiber/v2"
    "go.mongodb.org/mongo-driver/bson/primitive"
    "go.mongodb.org/mongo-driver/mongo"
)

var userCollection *mongo.Collection = configs.GetCollection(configs.DB, "users")
var validate = validator.New()

func CreateUser(c *fiber.Ctx) error {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    var user models.User
    defer cancel()

    //validate the request body
    if err := c.BodyParser(&user); err != nil {
        return c.Status(http.StatusBadRequest).JSON(responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: &fiber.Map{"data": err.Error()}})
    }

    //use the validator library to validate required fields
    if validationErr := validate.Struct(&user); validationErr != nil {
        return c.Status(http.StatusBadRequest).JSON(responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: &fiber.Map{"data": validationErr.Error()}})
    }

    newUser := models.User{
        Id:       primitive.NewObjectID(),
        Name:     user.Name,
        Location: user.Location,
        Title:    user.Title,
    }

    result, err := userCollection.InsertOne(ctx, newUser)
    if err != nil {
        return c.Status(http.StatusInternalServerError).JSON(responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: &fiber.Map{"data": err.Error()}})
    }
  
    return c.Status(http.StatusCreated).JSON(responses.UserResponse{Status: http.StatusCreated, Message: "success", Data: &fiber.Map{"data": result}})
}
```


The snippet above does the following:

- Import the required dependencies.
- Create `userCollection` and `validate` variables to create a collection and validate models using the `github.com/go-playground/validator/v10` library we installed earlier on, respectively.
- Create a `CreateUser` function that returns an `error`. Inside the function, we first defined a timeout of 10 seconds when inserting user into the document, validating both the request body and required field using the validator library. We returned the appropriate message and status code using the `UserResponse` struct we created earlier. Secondly, we created a `newUser` variable, inserted it using the `userCollection.InsertOne` function and check for errors if there are any. Finally, we returned the correct response if the insert was successful.

Next, we need to update `user_routes.go` with the route API URL and corresponding controller as shown below:

```go
package routes

import (
    "fiber-mongo-api/controllers" //add this

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

func UserRoute(app *fiber.App) {
    app.Post("/user", controllers.CreateUser) //add this
}
```


**Get a User Endpoint**
To get the details of a user, we need to modify `user_controller.go` as shown below:

```go
package controllers

import (
    //other import goes here    
    "go.mongodb.org/mongo-driver/bson" //add this
)

var userCollection *mongo.Collection = configs.GetCollection(configs.DB, "users")
var validate = validator.New()

func CreateUser(c *fiber.Ctx) error {
    //create user code goes here
}

func GetAUser(c *fiber.Ctx) error {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    userId := c.Params("userId")
    var user models.User
    defer cancel()

    objId, _ := primitive.ObjectIDFromHex(userId)

    err := userCollection.FindOne(ctx, bson.M{"id": objId}).Decode(&user)
    if err != nil {
        return c.Status(http.StatusInternalServerError).JSON(responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: &fiber.Map{"data": err.Error()}})
    }

    return c.Status(http.StatusOK).JSON(responses.UserResponse{Status: http.StatusOK, Message: "success", Data: &fiber.Map{"data": user}})
}
```


The snippet above does the following:

- Import the required dependencies.
- Create a `GetAUser` function that returns an `error`. Inside the function, we first defined a timeout of 10 seconds when finding a user in the document, a `userId` variable to get the user’s **id** from the URL parameter and a `user`  variable. We converted the `userId` from a string to a `primitive.ObjectID` type,  a BSON type MongoDB uses. Secondly, we searched for the user using the `userCollection.FindOne`, pass the `objId` as a filter and use the `Decode` attribute method to get the corresponding object. Finally, we returned the decoded response.

Next, we need to update `user_routes.go` with the route API URL and corresponding controller as shown below:


```go
package routes

import (
    //import goes here
)

func UserRoute(app *fiber.App) {
    //other routes goes here
    app.Get("/user/:userId", controllers.GetAUser) //add this
}
```


**PS:** *We also passed a* `userId` *as a parameter to the URL path. The specified parameter must match the one we specified in the controller.*

**Edit a User Endpoint**
To edit a user, we need to modify `user_controller.go` as shown below:


```go
package controllers

import (
    //other import goes here
)

var userCollection *mongo.Collection = configs.GetCollection(configs.DB, "users")
var validate = validator.New()

func CreateUser(c *fiber.Ctx) error {
    //create user code goes here
}

func GetAUser(c *fiber.Ctx) error {
    //get a user code goes here
}

func EditAUser(c *fiber.Ctx) error {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    userId := c.Params("userId")
    var user models.User
    defer cancel()

    objId, _ := primitive.ObjectIDFromHex(userId)

    //validate the request body
    if err := c.BodyParser(&user); err != nil {
        return c.Status(http.StatusBadRequest).JSON(responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: &fiber.Map{"data": err.Error()}})
    }

    //use the validator library to validate required fields
    if validationErr := validate.Struct(&user); validationErr != nil {
        return c.Status(http.StatusBadRequest).JSON(responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: &fiber.Map{"data": validationErr.Error()}})
    }

    update := bson.M{"name": user.Name, "location": user.Location, "title": user.Title}

    result, err := userCollection.UpdateOne(ctx, bson.M{"id": objId}, bson.M{"$set": update})

    if err != nil {
        return c.Status(http.StatusInternalServerError).JSON(responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: &fiber.Map{"data": err.Error()}})
    }
    //get updated user details
    var updatedUser models.User
    if result.MatchedCount == 1 {
        err := userCollection.FindOne(ctx, bson.M{"id": objId}).Decode(&updatedUser)

        if err != nil {
            return c.Status(http.StatusInternalServerError).JSON(responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: &fiber.Map{"data": err.Error()}})
        }
    }

    return c.Status(http.StatusOK).JSON(responses.UserResponse{Status: http.StatusOK, Message: "success", Data: &fiber.Map{"data": updatedUser}})
}
```


The `EditAUser` function above does the same thing as the `CreateUser` function. However, we included an `update` variable to get updated fields and updated the collection using the `userCollection.UpdateOne`. Lastly, we searched for the updated user’s details and returned the decoded response.

Next, we need to update `user_routes.go` with the route API URL and corresponding controller as shown below:


```go
package routes

import (
    //import goes here
)

func UserRoute(app *fiber.App) {
    //other routes goes here 
    app.Put("/user/:userId", controllers.EditAUser) //add this
}
```


**Delete a User Endpoint**
To delete a user, we need to modify `user_controller.go` as shown below:

```go
package controllers

import (
    //other import goes here
)

var userCollection *mongo.Collection = configs.GetCollection(configs.DB, "users")
var validate = validator.New()

func CreateUser(c *fiber.Ctx) error {
    //create user code goes here
}

func GetAUser(c *fiber.Ctx) error {
    //get a user code goes here
}

func EditAUser(c *fiber.Ctx) error {
    //edit a user code goes here
}

func DeleteAUser(c *fiber.Ctx) error {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    userId := c.Params("userId")
    defer cancel()

    objId, _ := primitive.ObjectIDFromHex(userId)

    result, err := userCollection.DeleteOne(ctx, bson.M{"id": objId})
    if err != nil {
        return c.Status(http.StatusInternalServerError).JSON(responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: &fiber.Map{"data": err.Error()}})
    }

    if result.DeletedCount < 1 {
        return c.Status(http.StatusNotFound).JSON(
            responses.UserResponse{Status: http.StatusNotFound, Message: "error", Data: &fiber.Map{"data": "User with specified ID not found!"}},
        )
    }

    return c.Status(http.StatusOK).JSON(
        responses.UserResponse{Status: http.StatusOK, Message: "success", Data: &fiber.Map{"data": "User successfully deleted!"}},
    )
}
```

The `DeleteAUser` function follows the previous steps by deleting the matched record using the `userCollection.DeleteOne`. We also checked if an item was successfully deleted and returned the appropriate response.

Next, we need to update `user_routes.go` with the route API URL and corresponding controller as shown below:

```go
package routes

import (
    //import goes here
)

func UserRoute(app *fiber.App) {
    //other routes goes here
    app.Delete("/user/:userId", controllers.DeleteAUser) //add this
}
```


**Get List of Users Endpoint**
To get the list of users, we need to modify `user_controller.go` as shown below:

```go
package controllers

import (
    //other import goes here
)

var userCollection *mongo.Collection = configs.GetCollection(configs.DB, "users")
var validate = validator.New()

func CreateUser(c *fiber.Ctx) error {
    //create user code goes here
}

func GetAUser(c *fiber.Ctx) error {
    //get a user code goes here
}

func EditAUser(c *fiber.Ctx) error {
    //edit a user code goes here
}

func DeleteAUser(c *fiber.Ctx) error {
    //delete a user code goes here
}

func GetAllUsers(c *fiber.Ctx) error {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    var users []models.User
    defer cancel()

    results, err := userCollection.Find(ctx, bson.M{})

    if err != nil {
        return c.Status(http.StatusInternalServerError).JSON(responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: &fiber.Map{"data": err.Error()}})
    }

    //reading from the db in an optimal way
    defer results.Close(ctx)
    for results.Next(ctx) {
        var singleUser models.User
        if err = results.Decode(&singleUser); err != nil {
            return c.Status(http.StatusInternalServerError).JSON(responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: &fiber.Map{"data": err.Error()}})
        }

        users = append(users, singleUser)
    }

    return c.Status(http.StatusOK).JSON(
        responses.UserResponse{Status: http.StatusOK, Message: "success", Data: &fiber.Map{"data": users}},
    )
}
```

The `GetAllUsers` function follows the previous steps by getting the list of users using the `userCollection.Find`. We also read the retuned list optimally using the `Next` attribute method to loop through the returned list of users.

Next, we need to update `user_routes.go` with the route API URL and corresponding controller as shown below:

```go
package routes

import (
    //import goes here
)

func UserRoute(app *fiber.App) {
    //other routes goes here
    app.Get("/users", controllers.GetAllUsers) //add this
}
```


**Complete user_controller.go**

```go
package controllers
import (
    "context"
    "fiber-mongo-api/configs"
    "fiber-mongo-api/models"
    "fiber-mongo-api/responses"
    "net/http"
    "time"
  
    "github.com/go-playground/validator/v10"
    "github.com/gofiber/fiber/v2"
    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/bson/primitive"
    "go.mongodb.org/mongo-driver/mongo"
)

var userCollection *mongo.Collection = configs.GetCollection(configs.DB, "users")
var validate = validator.New()

func CreateUser(c *fiber.Ctx) error {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    var user models.User
    defer cancel()
  
    //validate the request body
    if err := c.BodyParser(&user); err != nil {
        return c.Status(http.StatusBadRequest).JSON(responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: &fiber.Map{"data": err.Error()}})
    }
  
    //use the validator library to validate required fields
    if validationErr := validate.Struct(&user); validationErr != nil {
        return c.Status(http.StatusBadRequest).JSON(responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: &fiber.Map{"data": validationErr.Error()}})
    }
  
    newUser := models.User{
        Id:       primitive.NewObjectID(),
        Name:     user.Name,
        Location: user.Location,
        Title:    user.Title,
    }
  
    result, err := userCollection.InsertOne(ctx, newUser)
    if err != nil {
        return c.Status(http.StatusInternalServerError).JSON(responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: &fiber.Map{"data": err.Error()}})
    }
  
    return c.Status(http.StatusCreated).JSON(responses.UserResponse{Status: http.StatusCreated, Message: "success", Data: &fiber.Map{"data": result}})
}

func GetAUser(c *fiber.Ctx) error {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    userId := c.Params("userId")
    var user models.User
    defer cancel()
  
    objId, _ := primitive.ObjectIDFromHex(userId)
  
    err := userCollection.FindOne(ctx, bson.M{"id": objId}).Decode(&user)
    if err != nil {
        return c.Status(http.StatusInternalServerError).JSON(responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: &fiber.Map{"data": err.Error()}})
    }
  
    return c.Status(http.StatusOK).JSON(responses.UserResponse{Status: http.StatusOK, Message: "success", Data: &fiber.Map{"data": user}})
}

func EditAUser(c *fiber.Ctx) error {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    userId := c.Params("userId")
    var user models.User
    defer cancel()
  
    objId, _ := primitive.ObjectIDFromHex(userId)
  
    //validate the request body
    if err := c.BodyParser(&user); err != nil {
        return c.Status(http.StatusBadRequest).JSON(responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: &fiber.Map{"data": err.Error()}})
    }
  
    //use the validator library to validate required fields
    if validationErr := validate.Struct(&user); validationErr != nil {
        return c.Status(http.StatusBadRequest).JSON(responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: &fiber.Map{"data": validationErr.Error()}})
    }
  
    update := bson.M{"name": user.Name, "location": user.Location, "title": user.Title}
  
    result, err := userCollection.UpdateOne(ctx, bson.M{"id": objId}, bson.M{"$set": update})
    if err != nil {
        return c.Status(http.StatusInternalServerError).JSON(responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: &fiber.Map{"data": err.Error()}})
    }
  
    //get updated user details
    var updatedUser models.User
    if result.MatchedCount == 1 {
        err := userCollection.FindOne(ctx, bson.M{"id": objId}).Decode(&updatedUser)
        if err != nil {
            return c.Status(http.StatusInternalServerError).JSON(responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: &fiber.Map{"data": err.Error()}})
        }
    }
  
    return c.Status(http.StatusOK).JSON(responses.UserResponse{Status: http.StatusOK, Message: "success", Data: &fiber.Map{"data": updatedUser}})
}

func DeleteAUser(c *fiber.Ctx) error {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    userId := c.Params("userId")
    defer cancel()
  
    objId, _ := primitive.ObjectIDFromHex(userId)
  
    result, err := userCollection.DeleteOne(ctx, bson.M{"id": objId})
    if err != nil {
        return c.Status(http.StatusInternalServerError).JSON(responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: &fiber.Map{"data": err.Error()}})
    }
  
    if result.DeletedCount < 1 {
        return c.Status(http.StatusNotFound).JSON(
            responses.UserResponse{Status: http.StatusNotFound, Message: "error", Data: &fiber.Map{"data": "User with specified ID not found!"}},
        )
    }
  
    return c.Status(http.StatusOK).JSON(
        responses.UserResponse{Status: http.StatusOK, Message: "success", Data: &fiber.Map{"data": "User successfully deleted!"}},
    )
}

func GetAllUsers(c *fiber.Ctx) error {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    var users []models.User
    defer cancel()
  
    results, err := userCollection.Find(ctx, bson.M{})
  
    if err != nil {
        return c.Status(http.StatusInternalServerError).JSON(responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: &fiber.Map{"data": err.Error()}})
    }
  
    //reading from the db in an optimal way
    defer results.Close(ctx)
    for results.Next(ctx) {
        var singleUser models.User
        if err = results.Decode(&singleUser); err != nil {
            return c.Status(http.StatusInternalServerError).JSON(responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: &fiber.Map{"data": err.Error()}})
        }
      
        users = append(users, singleUser)
    }
  
    return c.Status(http.StatusOK).JSON(
        responses.UserResponse{Status: http.StatusOK, Message: "success", Data: &fiber.Map{"data": users}},
    )
}
```


**Complete user_route.go**

```go
package routes

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

func UserRoute(app *fiber.App) {
    app.Post("/user", controllers.CreateUser)
    app.Get("/user/:userId", controllers.GetAUser)
    app.Put("/user/:userId", controllers.EditAUser)
    app.Delete("/user/:userId", controllers.DeleteAUser)
    app.Get("/users", controllers.GetAllUsers)
}
```


With that done, we can test our application by starting the development server by running the command below in our terminal.


    go run main.go


![terminal output](https://paper-attachments.dropbox.com/s_97A8BAFA9C176AA31F7FC2C18065AA1FE2F74B0C6537A26A8D511A9956341968_1640678631468_Screenshot+2021-12-28+at+00.18.21.png)



![Create a user endpoint](https://paper-attachments.dropbox.com/s_97A8BAFA9C176AA31F7FC2C18065AA1FE2F74B0C6537A26A8D511A9956341968_1640692844347_Screenshot+2021-12-28+at+12.59.51.png)



![Get a user endpoint](https://paper-attachments.dropbox.com/s_0E637C74C0D58C5FC3FBA1D21E2CF4F11E7592B7929DAE10790A9D12FE972D1D_1640566665581_Screenshot+2021-12-26+at+20.59.07.png)

![Edit a user endpoint](https://paper-attachments.dropbox.com/s_0E637C74C0D58C5FC3FBA1D21E2CF4F11E7592B7929DAE10790A9D12FE972D1D_1640566698763_Screenshot+2021-12-27+at+00.28.38.png)

![Delete a user endpoint](https://paper-attachments.dropbox.com/s_0E637C74C0D58C5FC3FBA1D21E2CF4F11E7592B7929DAE10790A9D12FE972D1D_1640566729703_Screenshot+2021-12-27+at+00.49.33.png)

![Get list of users endpoint](https://paper-attachments.dropbox.com/s_0E637C74C0D58C5FC3FBA1D21E2CF4F11E7592B7929DAE10790A9D12FE972D1D_1640566748806_Screenshot+2021-12-27+at+01.16.15.png)



![Database with users document](https://paper-attachments.dropbox.com/s_0E637C74C0D58C5FC3FBA1D21E2CF4F11E7592B7929DAE10790A9D12FE972D1D_1640609703212_Screenshot+2021-12-26+at+19.39.22.png)

# Conclusion

This post discussed how to structure a Fiber application, build a REST API and persist our data using MongoDB.

You may find these resources helpful:

- [Fiber](https://github.com/gofiber/fiber)
- [MongoDB Go Driver](https://docs.mongodb.com/drivers/go/current/)
- [Go Validator](https://github.com/go-playground/validator)
- [Go Environment Loader](https://github.com/joho/godotenv)