Go Fiber: Start Building RESTful APIs on Golang (Feat. GORM)
Tags: go, rest, fiber, express
Published: 2021-09-14 10:15:07

## :books: Contents <a name="contents" />
* [What are we building](#what-are-we-building)
* [Go](#go)
* [Express and Fiber](#express-and-fiber)
* [Let's Get Started With APIs](#get-started-apis)
      * [Let's create an endpoint](#create-an-endpoint)
      * [Go modules](#go-modules)
* [Setup Essentials](#setup-essentials)
      * [Makefile](#makefile)
      * [Database](#database)
      * [Adding Config](#adding-config)
      * [Connecting to Database](#connecting-database)
      * [Add Models](#adding-models)
* [Add the Routes](#add-routes)
      * [Routes](#routes)
      * [Handlers](#handlers)
      * [Connecting Handlers to routes](#connecting-handlers-routes)
      * [Setup Note Routes](#setup-note-routes)
      * [Setup Router](#setup-router)

> *Let's learn tech in plain english*

#### If you are better with code than with text, feel free to directly jump to completed project, fork it and start adding your code - 

{% github percoguru/tutorial-notes-api-go-fiber no-readme %}

## :construction_worker: What are we building  <a name="what-are-we-building"></a>     [&uarr;](#contents)
We will be building a simple Note Taking API in Go to setup a basic repo which can be used to scale and create a complete backend service. The structure we create will help you understand and get started with APIs in Go. 
If you have wondered how to start developing APIs in Go or you just completed understanding basics of Go and want to get started with real development this will be a great starting point.

## :bullettrain_front: Go <a name="go"></a>    [&uarr;](#contents)
Go has been around for more than a decade now. It is super loved and super fun to learn and work with. 
Fully fledged full stack web applications can be built using Go, but for the purpose of this article which is to learn to build a basic web API in GoLang, we will stick to APIs.

## :fast_forward: Express and Fiber <a name="express-and-fiber"></a>     [&uarr;](#contents)
NodeJs has enjoyed a lot of love from people building backend services for the past decade. 
Developing APIs the Express way is super developer friendly for both polished NodeJs developers and new Go Developers. Thus [Fiber](https://gofiber.io/) was built with Express in mind.
Fiber is a web framework for Go with APIs readiness from scratch, middleware support and super fast performance. We will be making use of Fiber to create our web API.

### :file_folder: Final Project Structure

Let's first look at what our final structure will look like - 
```bash
notes-api-fiber
|
- config
|   - config.go
|
- database
|   - connect.go
|   - database.go
|
- internal
|   |     
|   - handler
|   |     - note
|   |         - noteHandler.go
|   |     
|   - model 
|   |     - model.go
|   |
|   - routes
|         - note
|             - noteRoutes.go
|
- router
|   - router.go
|
- main.go
- go.mod
- go.sum

```

No need to worry, we will start building with just a single file and reach this state with sufficient logic and explanations on the way.

### :school_satchel: Basics of packages -
Go code is distributed in packages and we will be making a lot of them. Go packages are used for distribution of code and logic based on their usage. It can also be observed in the directory structure we draw above.
- We declare the package name for a file by writing `package <package_name>` at the top.
- In simple words packages are group of code in a go project that share the variables and functions.
- Any number of files part of the same package share variables, functions, types, interfaces, basically any definition. 
- Inorder to reach any code for a package correctly all files for a package should be present in a single directory. 

## :bulb: Let's Get Started With APIs <a name="get-started-apis"></a>    [&uarr;](#contents)
We will be beginning with a single file, the starting point of our code - `main.go`. Create this file in the root directory of our project.
### main.go <a name="main"></a>    [&uarr;](#contents)
Let's start with writing the root file, the `main.go` file. This will be the starting point of the application. Right now, we will just be initializing a fiber app inside here. We will add more things later and this will become the place where the setup happens.

##### main.go
```go
package main

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

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

	// Listen on PORT 300
	app.Listen(":3000")
}

```
#### Let's create an endpoint <a name="create-an-endpoint"></a>   [&uarr;](#contents)
To have a basic understanding of how an API endpoint is created, let's first create a dummy endpoint to get started. 
> *If you have worked with Express you might notice the resemblance, if you have not worked with Express the resemblance  will come other way around for you*

##### main.go
```go
package main

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

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

	// Send a string back for GET calls to the endpoint "/"
	app.Get("/", func(c *fiber.Ctx) error {
		err := c.SendString("And the API is UP!")
		return err
	})

	// Listen on PORT 3000
	app.Listen(":3000")
}
```
Run the server by running 
```bash 
go run main.go
``` 
in the root directory. Then go to `localhost:3000`. You will see a page like this - 
![Basic API with Go Fiber](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ih7033nzgfiy6g6bl8qi.png)

Now, that we have seen that how we can startup an API from a single file, containing a few lines of code. 
**Note that you can keep on adding more and more Endpoints in here and scale. Such scenario will come up many times during this article, but instead of scaling vertically we will try to distribute our code horizontally wherever possible**

Now our project directory looks like - 

```
notes-api-fiber
|
- main.go

```

### :briefcase: Go modules <a name="go-modules"></a>    [&uarr;](#contents)
Our project will become a Go Module. To know more about Go Modules visit https://golang.org/ref/mod. To start a Go module within our project directory run the command 
```bash
go mod init <your_module_url>
```
Normally `<your_module_url>` is represented by where your module will be published. For now you can use your Github profile. Eg - If your github username is `percoguru`, you will run 
```
go mod init github.com/percoguru/notes-api-fiber
``` 
The last part of your path is the name of your project. 
Now any package that you create will be a subdirectory within this module. Eg a package `foo` would be imported by another package inside your module as `github.com/percoguru/notes-api-fiber/foo`.

Once you run the command, a file `go.mod` will be created that contains the basic information about our module and the dependencies that we would be adding. This can be considered as the `package.json` equivalent for Go.  

Your `go.mod` will look like this - 
##### go.mod
```go
module github.com/percoguru/notes-api-fiber

go 1.17
```

## :triangular_ruler: Setup Essentials <a name="setup-essentials"></a>    [&uarr;](#contents)
Now we will set up some basic stuff to support our APIs -
- Makefile 
- Database (PostgreSQL)
- Models
- Environment Variables

### Makefile <a name="makefile" />    [&uarr;](#contents)
As we introduce more and more changes, we will need to run `go run main.go` every time we want changes to reflect on our running server. To enable hot reload of our server create a `Makefile` in the root of your directory. 
- Install reflex 
```
go install github.com/cespare/reflex@latest
```
- Add commands to your `Makefile`
```make
build:
	go build -o server main.go

run: build
	./server

watch:
	reflex -s -r '\.go$$' make run
```
- Run 
```bash
make watch
```
- Make any changes to your code and see the server reloading in the terminal.


### :floppy_disk: Database <a name="database"></a>    [&uarr;](#contents)
We will be using Postgres for the database implementation in this article.

> Although we are making just a Note Taking Application here, the purpose of this article is to allow you to scale from here. I would even encourage to go crazy with the database schema now only and add new entities or use something other than Notes. SQL is great for such scaling and you would not be exploring other options if you decide to scale further from here.

**Get Postgres running on your machine, and create a database fiber-app for our implementation - follow instructions at - https://www.postgresql.org/download/** 

### :gear: Adding Config <a name="adding-config"></a>    [&uarr;](#contents)
We will be adding an environment variable file `.env` in the root of our project directory. When we connect to a database we will require some variables, we will store those in this file. 
#####.env
```bash
DB_HOST= localhost
DB_NAME= fiber-app
DB_USER= postgres
DB_PASSWORD= postgres
DB_PORT= 5432
```
The above values will most probably remain the same for you too except the password, which will be the password you choose for the postgres user. Remember to create a database fiber-app before going ahead.
Now we have to pick up these variables from the `.env` file. For this purpose we will create a `package config` that will provide us the configurations for the project.
Create a folder `config` in the root of your directory and create a file `config.go` inside this. 
First run - 
```bash
go get github.com/joho/godotenv
```
#####config.go
```go
package config

import (
	"fmt"
	"os"

	"github.com/joho/godotenv"
)

// Config func to get env value
func Config(key string) string {
	// load .env file
	err := godotenv.Load(".env")
	if err != nil {
		fmt.Print("Error loading .env file")
	}
        // Return the value of the variable
	return os.Getenv(key)
}

```

### :arrows_counterclockwise: Connecting to the Database <a name="connecting-database"></a>    [&uarr;](#contents)
Inside the root folder of our project, create a directory named `database`. All our code related to database connection and migrations will reside here. This will become a package related to our database connection related operations, let's name this package `database`. 

We will be using an ORM (Object Relational Mapping) as a middleware between our Go code and SQL database. GORM would be our ORM of choice for this article. It supports Postgres, Associations, Hooks and one feature that will help us a lot initially - Auto Migrations. 
Add gorm and postgres driver for gorm by running - 
```
go get gorm.io/gorm
go get gorm.io/driver/postgres
```

##### connect.go
```go 
package database

import (
	"fmt"
	"log"
	"strconv"

	"github.com/percoguru/notes-api-fiber/config"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

// Declare the variable for the database
var DB *gorm.DB

// ConnectDB connect to db
func ConnectDB() {
	var err error
	p := config.Config("DB_PORT")
	port, err := strconv.ParseUint(p, 10, 32)

	if err != nil {
		log.Println("Idiot")
	}

	// Connection URL to connect to Postgres Database
	dsn := 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"))
	// Connect to the DB and initialize the DB variable
	DB, err = gorm.Open(postgres.Open(dsn))

	if err != nil {
		panic("failed to connect database")
	}

	fmt.Println("Connection Opened to Database")
}

```
Notice how `connect.go` imports the package `config`. It looks for the package inside the folder `./config`.

We have created the database connector, but when we run the application we run `go run main.go`. Right now we have not yet called the function `connectDB()`. We need to call this in order to connect to the database.
Let's go to our `main.go` file and connect to the database. We want to connect to our database whenever the server is run. So we can call the function `connectDB()` from the function `main` of the package main.

##### main.go
```go
package main

import (
	"github.com/gofiber/fiber/v2"
	"github.com/percoguru/notes-api-fiber/database"
)

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

	// Connect to the Database
	database.ConnectDB()

	// Send a string back for GET calls to the endpoint "/"
	app.Get("/", func(c *fiber.Ctx) error {
		err := c.SendString("And the API is UP!")
		return err
	})

	// Listen on PORT 3000
	app.Listen(":3000")
}
``` 
Now run 
```bash
go run main.go
```
And you are connected to the Database. You will see an output like this.
![Connect To Database Go](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/09fh4utyfvfv8urwgxbs.png)

We have not yet done any operations on the database. We will be creating models to represent the tables we want to store in the database. 
### :bookmark: Add Models  <a name="adding-models"></a>

Create a folder `internal` in the root of your directory. All our internal logic (models, types, handlers, routes, constants etc.) will be stored in this folder. 
Within this folder create a folder `model` and then the file `model.go`. Thus creating `internal/model/model.go`. The folder model will contain our `package model`.
We will be creating only one model for now - **note**, you can add more here or use something other than a note, like **product**, **page**, etc. Be sure to be creative. Even if you choose to go with some other model (which I am encouraging btw :wink:) the other parts of code would remain mostly the same, so, don't hesitate to experiment.
Our Notes table would look like - 
- ID **uuid** 
- Title **text**
- SubTitle **text**
- Text **text**

To use uuid type in Go, run - 
```bash
go get github.com/google/uuid
```
To create this model, add this to the `model.go` file - 
```go
package model

import (
	"github.com/google/uuid"
	"gorm.io/gorm"
)

type Note struct {
	gorm.Model           // Adds some metadata fields to the table
	ID         uuid.UUID `gorm:"type:uuid"` // Explicitly specify the type to be uuid
	Title      string
	SubTitle   string
	Text       string
}
```
Notice that in the line 
```go
ID         uuid.UUID `gorm:"type:uuid"`
```
We are first telling Go that the type of this struct field is `uuid.UUID` and then telling GORM to create this column with type uuid by specifying with the tag `gorm:"type:uuid"`

#### :repeat: Auto Migrations
GORM supports auto migrations, thus whenever you make changes to your model structs (like add column, change type, add index) and restart the server the changes will be reflected in the database automatically.
> Note that to save you from accidental loss of data GORM does not delete columns automatically through migrations if you do so in your struct. Though you can configure GORM to do so.

Go into your `database/connect.go` file and add in a line to automigrate after connecting to the database
```go
...
	// Connect to the DB and initialize the DB variable
	DB, err = gorm.Open(postgres.Open(dsn))

	if err != nil {
		panic("failed to connect database")
	}

	fmt.Println("Connection Opened to Database")

	// Migrate the database
	DB.AutoMigrate(&model.Note{})
	fmt.Println("Database Migrated")
}
```
Now, restart the server. And your database is migrated. You can verify the migration by going into postgres, on Linux you can use psql and on Windows pgAdmin or DBeaver.
**On psql on Ubuntu** - 
- Connect to the database notes-api 
```bash
\c notes-api
```
- Get details about the notes table 
**Note that GORM pluralizes the struct name to be the table name, also note in the below image how field names are handled against corresponding field names in struct**
```bash
\d+ notes
```
**Output**
![Notes Table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ouuvrci2e6mxpoewbxhh.png)
The extra fields have been added by GORM, when we added `gorm.Model` at the top of our struct.
Now we have verified our migrations and our model is in the database. :sunglasses:

Let's re-look at our project structure now - 
```
notes-api-fiber
|
- config
|   - config.go
|
- database
|   - connect.go
|
- internal
|   |     
|   |- model 
|   |     - model.go
|
- main.go
- go.mod
- go.sum
- .env

```

## :aerial_tramway: Add The Routes <a name="add-routes" />   [&uarr;](#contents)
Our API will have routes which are endpoints that the browser or a web or mobile application will use to perform CRUD operations on the data.
First we setup a basic router to start routing on the fiber app.
Create a folder `router` in the root directory of our project and inside it a file `router.go`. 
Inside the file add the following code - 
```go
package router

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

func SetupRoutes(app *fiber.App) {

}
```

We just declared a function SetupRoutes inside package router that takes a fiber app as an argument. This function will take a fiber app and route calls to this app to specific routes or route handlers. 
APIs are grouped based on parameters and fiber allows us to do so. For example if there are three API endpoints - 
- GET `api/user/:userId` - **Get User with userId**
- GET `api/user` - **Get All Users** 
- PUT `api/user/:userId` - **Update User with userId**

We do not have to write all the repeated parameters. What we can instead do is - 
```go
...
app := fiber.App()
api := app.Group("api")

user := api.Group("user")

user.GET("/", func(c *fiber.Ctx) {} )

user.GET("/:userId", func(c *fiber.Ctx) {} )

user.PUT("/:userId" ,func(c *fiber.Ctx) {} )
```
This helps a lot when we scale and add a lot of API and complex routing.
**Note that 'api' is of type fiber.Router and 'app' is of type fiber.App and these both have the function Group**

### :hammer: Routes <a name="routes" />    [&uarr;](#contents)
We want to keep our routes like `SERVER_HOST/api/param1/param2`. Thus we add this line to our function `SetupRoutes` - 
```go
api := app.Group("/api", logger.New()) // Group endpoints with param 'api' and log whenever this endpoint is hit.
```
The handler `logger.New()` will also log all the API calls and their statuses.
**Now as I had said earlier we would scale horizontally, we could have added routes related to notes in the `main.go` file itself, but we created a router package to handle API routes. Now, when the API will scale you will be adding a lot of models so we can not add add notes api in router package as well.**
**We will add specific router for each model and right now for Notes**
Inside the `internal` folder create a folder `routes` this is where we will have subdirectories for all routes related to a model. Within the routes folder add a folder `note` and inside the folder add a file `note.go`. 
You have created - 
```bash
internal/routes/note/note.go
```
Inside the file `note.go` add the following code - 
```go
package noteRoutes

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

func SetupNoteRoutes(router fiber.Router) {

}
```
The function SetupNoteRoutes takes a fiber.Router and handles endpoints to the note model. Thus add the line - 
```go
note := router.Group("/note")
```
We will be adding CRUD (Create, Read, Update, Delete) operations to the note routes. Thus - 
```go
package noteRoutes

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

func SetupNoteRoutes(router fiber.Router) {
	note := router.Group("/note")
	// Create a Note
	note.Post("/", func(c *fiber.Ctx) error {})
	// Read all Notes
	note.Get("/", func(c *fiber.Ctx) error {})
	// Read one Note
	note.Get("/:noteId", func(c *fiber.Ctx) error {})
	// Update one Note
	note.Put("/:noteId", func(c *fiber.Ctx) error {})
	// Delete one Note
	note.Delete("/:noteId", func(c *fiber.Ctx) error {})
}
```
**Note that we have to write handler function to do the tasks we have commented for all the API endpoints**
**We will be writing these handler in a seperate package**

### :wrench: Handlers <a name="handlers"/>    [&uarr;](#contents)

Handlers are functions that take in a Fiber Context (fiber.Ctx) and use the request, send the response or just act as a middleware and pass on the authority to the next handler. 
To know more about handlers and middleware visit the [Fiber Documentation](https://docs.gofiber.io/)
Inside the `internal` folder add a folder `handlers` this will contain all the API handlers with a specific sub directory for each model. So create a folder `note` within `handlers` and add a file `note.go` inside the `note` folder.
You just created - 
```bash
internal/handlers/note/note.go
```
Now we will add handlers that we require in `routes/note/note.go` file to `handlers/note/note.go` file.
##### handlers/note/note.go
```go
package noteHandler
```

- Add the Read Notes handler - 
```go
func GetNotes(c *fiber.Ctx) error {
	db := database.DB
	var notes []model.Note

	// find all notes in the database
	db.Find(&notes)

	// If no note is present return an error
	if len(notes) == 0 {
		return c.Status(404).JSON(fiber.Map{"status": "error", "message": "No notes present", "data": nil})
	}

	// Else return notes
	return c.JSON(fiber.Map{"status": "success", "message": "Notes Found", "data": notes})
}
```

- Add the Create Note handler - 
```go

func CreateNotes(c *fiber.Ctx) error {
	db := database.DB
	note := new(model.Note)

	// Store the body in the note and return error if encountered
	err := c.BodyParser(note)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"status": "error", "message": "Review your input", "data": err})
	}
	// Add a uuid to the note
	note.ID = uuid.New()
	// Create the Note and return error if encountered
	err = db.Create(&note).Error
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"status": "error", "message": "Could not create note", "data": err})
	}

	// Return the created note
	return c.JSON(fiber.Map{"status": "success", "message": "Created Note", "data": note})
}
```

- Add the Get Note Handler
```go
func GetNote(c *fiber.Ctx) error {
	db := database.DB
	var note model.Note

	// Read the param noteId
	id := c.Params("noteId")

	// Find the note with the given Id
	db.Find(&note, "id = ?", id)

	// If no such note present return an error
	if note.ID == uuid.Nil {
		return c.Status(404).JSON(fiber.Map{"status": "error", "message": "No note present", "data": nil})
	}

	// Return the note with the Id
	return c.JSON(fiber.Map{"status": "success", "message": "Notes Found", "data": note})
}
```

- Add the Update Note Handler
```go
func UpdateNote(c *fiber.Ctx) error {
	type updateNote struct {
		Title    string `json:"title"`
		SubTitle string `json:"sub_title"`
		Text     string `json:"Text"`
	}
	db := database.DB
	var note model.Note

	// Read the param noteId
	id := c.Params("noteId")

	// Find the note with the given Id
	db.Find(&note, "id = ?", id)

	// If no such note present return an error
	if note.ID == uuid.Nil {
		return c.Status(404).JSON(fiber.Map{"status": "error", "message": "No note present", "data": nil})
	}

	// Store the body containing the updated data and return error if encountered
	var updateNoteData updateNote
	err := c.BodyParser(&updateNoteData)
	if err != nil {
		return c.Status(500).JSON(fiber.Map{"status": "error", "message": "Review your input", "data": err})
	}

	// Edit the note
	note.Title = updateNoteData.Title
	note.SubTitle = updateNoteData.SubTitle
	note.Text = updateNoteData.Text

	// Save the Changes
	db.Save(&note)

	// Return the updated note
	return c.JSON(fiber.Map{"status": "success", "message": "Notes Found", "data": note})
}

```

- Add the Delete Note Handler
```go
func DeleteNote(c *fiber.Ctx) error {
	db := database.DB
	var note model.Note

	// Read the param noteId
	id := c.Params("noteId")

	// Find the note with the given Id
	db.Find(&note, "id = ?", id)

	// If no such note present return an error
	if note.ID == uuid.Nil {
		return c.Status(404).JSON(fiber.Map{"status": "error", "message": "No note present", "data": nil})
	}

	// Delete the note and return error if encountered
	err := db.Delete(&note, "id = ?", id).Error

	if err != nil {
		return c.Status(404).JSON(fiber.Map{"status": "error", "message": "Failed to delete note", "data": nil})
	}

	// Return success message
	return c.JSON(fiber.Map{"status": "success", "message": "Deleted Note"})
}

```

### :incoming_envelope: Connecting handlers to routes <a name="connecting-handlers-routes"/>    [&uarr;](#contents)

 Add the handlers in the note routes, changing the file `routes/note/note.go` to - 
##### routes/note/note.go
```go 
package noteRoutes

import (
	"github.com/gofiber/fiber/v2"
	noteHandler "github.com/percoguru/notes-api-fiber/internals/handlers/note"
)

func SetupNoteRoutes(router fiber.Router) {
	note := router.Group("/note")
	// Create a Note
	note.Post("/", noteHandler.CreateNotes)
	// Read all Notes
	note.Get("/", noteHandler.GetNotes)
	// // Read one Note
	note.Get("/:noteId", noteHandler.GetNote)
	// // Update one Note
	note.Put("/:noteId", noteHandler.UpdateNote)
	// // Delete one Note
	note.Delete("/:noteId", noteHandler.DeleteNote)
}

```
**Notice how noteHandler is imported because of the mismatch between the folder name and the package name, if you want to avoid this, name the folder as noteHandler too**

### :incoming_envelope: Setup Note Routes <a name="setup-note-routes"/>    [&uarr;](#contents)
Setup the Note Routes in the `router/router.go` file, editing it to - 
##### router/router.go
```go
package router

import (
	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/logger"
	noteRoutes "github.com/percoguru/notes-api-fiber/internals/routes/note"
)

func SetupRoutes(app *fiber.App) {
	api := app.Group("/api", logger.New())

        // Setup the Node Routes
	noteRoutes.SetupNoteRoutes(api)
}

```

### :incoming_envelope: Setup Router <a name="setup-router"/>    [&uarr;](#contents)
Until now we have created the router and used it to setup note routes, now we need to setup this router from the main function. 
Inside the `main.go` remove the dummy endpoint we had created and add in the line - 
```
router.setupRoutes(app)
```
Converting your `main.go` into - 
##### main.go
```go
package main

import (
	"github.com/gofiber/fiber/v2"
	"github.com/percoguru/notes-api-fiber/database"
	"github.com/percoguru/notes-api-fiber/router"
)

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

	// Connect to the Database
	database.ConnectDB()

	// Setup the router
	router.SetupRoutes(app)

	// Listen on PORT 3000
	app.Listen(":3000")
}
```
## et voilà ! :collision: :moneybag:
## We did it! 
Run
```bash
make watch
```
And try the endpoints out on Postman. And see it all working so very fine.

## What's Next!!
Now you have built a web API from scratch in Go. You came across the nuances of Go, Fiber, GORM and Postgres. This has been a basic setup and you can grow your api into a full stack web application - 
- Add [JWT](https://jwt.io/) based authentication
- Add more models to your backend and play around with different data types
- Add in a frontend using a UI framework like [React](https://reactjs.org/) or use [Go Templates](https://gowebexamples.com/templates/)

### Supporting Documentations and Resources - 
- [Go](https://golang.org/doc/)
- [Fiber](https://docs.gofiber.io/)
- [GORM](https://gorm.io/docs/)
- [Postgres](https://www.postgresql.org/docs/)

#### Go is an amazing language to learn, play and work with. If you like what you built, make sure to be consistent with Go and you will surely fall in love with it. Every line of code that you wrote here has been written from scratch and there is no boilerplate.
___
#### :metal: Will be back with more stuff!!
##### Got some question, idea or feedback? Comment below, I will love to hear from you! :mega: