Building CRUD Operations in Golang 🎉
Tags: go, postgres, gorm, fiber
Published: 2023-01-02 14:59:14

## Introduction :
Hey there 👋, In this tutorial, we are going to create a Web API with CRUD operations using Go-Fiber.

### What are we building?
We are going to build a To-Do list API, with CRUD operations.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eo7wzq8qv6dt64od33lw.png)

## Prerequisites💯 :
To continue with the tutorial, firstly you need to have Golang, Fiber and PostgreSQL installed. If you've not gone through the previous tutorials on the Fiber Web Framework series you can see them [here](https://siddhesh-dev.co/series/go-fiber) :)

### Installations :

- [Golang](https://go.dev/doc/install)
- [Go-Fiber](https://docs.gofiber.io/): We'll see this ahead in the tutorial.
- [PostgreSQL](https://www.postgresql.org/download/)
- GORM: We'll understand it from scratch in this tutorial. 🚀

## Getting Started 🚀:
Let's get started by creating the main project directory `todo-list-api` by using the following command.

```
mkdir todo-list-api
cd todo-list-api
```
Now initialize a mod file. _(If you publish a module, this must be a path from which your module can be downloaded by Go tools. That would be your code's repository.)_

```
go mod init <repository-name>
```
In my case repository name is `github.com/Siddheshk02/todo-list-api` .

To install the Fiber Framework run the following command :

```
go get -u github.com/gofiber/fiber/v2
```
To install the Gorm and to install the Gorm Postgres driver, run the following commands resp. :

```
go get -u gorm.io/gorm
go get -u gorm.io/driver/postgres
```
## Initializing 💻:
Let's set up our server by creating a new instance of Fiber. For this create a file `main.go` and add the following code to it :

```
package main

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

func main() {
    app := fiber.New() // Creating a new instance of Fiber.
    list := app.Group("/list")

    list.Get("/", func(c *fiber.Ctx) error {
        return c.SendString("Welcome to the Todo-List-API Tutorial :)")
    }) // "/" - Default route to return the given string.
```
After Running main.go ,

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v1jevmnhm5uumbmqpmjb.png)

### Routes :
Now Let's create a new folder/package `routes` which will contain `routes.go`, for all the functions, called by the API endpoints.

`routes.go` :

```
package routes

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

func GetTask(c *fiber.Ctx) error {
    return c.SendString("A Single Task") // for getting a single task.
}

func GetAllTasks(c *fiber.Ctx) error {
    return c.SendString("ALL Tasks") // for getting all the tasks.
}

func AddTask(c *fiber.Ctx) error {
    return c.SendString("Added a Task") // for adding a new task.
}

func DeleteTask(c *fiber.Ctx) error {
    return c.SendString("Deleted a Task") // for deleting a task.
}

func UpdateTask(c *fiber.Ctx) error {
    return c.SendString("Updated a Task") // for updating a task.
}
```
Now let's update the `main.go` according to the functions in `routes.go` ,

```
package main

import (
    "github.com/Siddheshk02/todo-list-api/routes"
    "github.com/gofiber/fiber/v2"
)

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

    list := app.Group("/list")

    list.Get("/", func(c *fiber.Ctx) error {
        return c.SendString("Welcome to the Todo-List-API Tutorial :)")
    }) // "/" - Default route to return the given string.

    list.Get("/tasks", routes.GetAllTasks) //Get endpoint for fetching all the tasks.

    list.Get("/task/:id", routes.GetTask) //Get endpoint for fetching a single task.

    list.Post("/add_task", routes.AddTask) //Post endpoint for add a new task.

    list.Delete("/delete_task/:id", routes.DeleteTask) //Delete endpoint for removing an existing task.

    list.Patch("/update_task/:id", routes.UpdateTask) //Patch endpoint for updating an existing task.

    app.Listen(":8000")
}
```
It is a convention when building API to prefix all the routes with `/list` which will mark them as list-API. For this, we are using the `app.Group()` function. You can define all the REST endpoints for a particular resource using the resource name after that.

Now, similar to the `routes` package let's create a folder/package `database` and create `dbconn.go` file in it.

In the `dbconn.go` , let's define the Task entity and add Database Credentials:

```
type Task struct {
    gorm.Model
    Name   string `json:"name"`
    Status string `json:"status"`
}

const (
    host     = "localhost"
    port     = 5432
    user     = "postgres"
    password = "<your-password>"
    dbname   = "todo-list-api"
)

var dsn string = fmt.Sprintf("host=%s port=%d user=%s "+
    "password=%s dbname=%s sslmode=disable TimeZone=Asia/Shanghai",
    host, port, user, password, dbname)
```

```
func InitDB() error {
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        return err
    }

    db.AutoMigrate(&Task{})

    return nil
}
```
`InitDB` , the function is defined which will try to establish a connection with the database. If the database table is not present, it will create a new database table with the name `task` .

`AutoMigrate` call helps in creating the table if it is not already present. Database migration is 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.

The function `InitDB` , is called in the `main.go` . Update the `main.go` with the following code.

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

    if dbErr != nil {
        panic(dbErr)
    }

    list := app.Group("/list")

    list.Get("/", func(c *fiber.Ctx) error {
        return c.SendString("Welcome to the Todo-List-API Tutorial :)")
    }) // "/" - Default route to return the given string.

    ...
}
```
### POST :
Now, let's create the function `CreateTask` in the `dbconn.go` file, for adding a new Task.

```
func CreateTask(name string, status string) (Task, error) {
    var newTask = Task{Name: name, Status: status}

    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        return newTask, err
    }
    db.Create(&Task{Name: name, Status: status})

    return newTask, nil
}
```
To call the function `CreateTask` , create a `AddTask` function in the `routes.go` ,

```
func AddTask(c *fiber.Ctx) error {
    newTask := new(database.Task)

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

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

    c.Status(200).JSON(&fiber.Map{
        "data":    result,
        "success": true,
        "message": "Task added!",
    })
    return nil
}
```
It is a good practice to return a predefined structure as a response to the API request along with the Status code and message.

We use the `BodyParser` function to convert the POST request data into our model format.

I am using the Postman tool for sending requests, you can use any tool for sending JSON to the POST request.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bcmd7xhli4e7df6itqm5.png)

#### GET :
Now, adding a function for fetching the Tasks from the record. For this, let's update the `dbconn.go` file add a `GetallTasks` function in it, which will get the list of the tasks.

```
func GetallTasks() ([]Task, error) {
    var tasks []Task

    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        return tasks, err
    }

    db.Find(&tasks)

    return tasks, nil
}
```
Now update the `GetAllTasks` function in the `routes.go` file,

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

    return c.Status(200).JSON(&fiber.Map{
        "data":    result,
        "success": true,
        "message": "All Tasks",
    })
}
```
In the GET request, we don't need to send any data. We just want the list of all the tasks which is added. If there is any error from the Database call, send the status code 500.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/algttwvd7qih4dg1dw3c.png)

#### GET a Single Task :

Now, for getting a single task through the 'Id' let's add a new function `Gettask` in the `dbconn.go` file.

```
func Gettask(id string) (Task, error) {
    var task Task

    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        return task, err
    }

    db.Where("ID = ?", id).First(&task)
    return task, nil
}
```
`db.Where()` is used to check for the Task with that particular 'id'.

Let's create a `GetTask()` function in the `routes.go` file.

```
func GetTask(c *fiber.Ctx) error {

    id := c.Params("id")

    if id == "" {

        return c.Status(500).JSON(&fiber.Map{

            "message": "id cannot be empty",

        })
    }

    result, err := database.Gettask(id)
    if err != nil {
        return c.Status(500).JSON(&fiber.Map{
            "data":    nil,
            "success": false,
            "message": err,
        })
    }

    return c.Status(200).JSON(&fiber.Map{
        "data":    result,
        "success": true,
        "message": "",
    })
}
```
Here we are getting the 'id' with the Fiber function called `param`, which accepts an argument which is the name of the parameter expecting. If the 'id' is not present, an error is sent to the user with the Fiber function.

This 'id' is passed when the `GetTask()` function is called.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2rb8yv8auynasnljw4yy.png)

#### DELETE :

For deleting a task from the list let's add a `Deletetask` function in the `dbconn.go` file.

```
func Deletetask(id string) error {
    var task Task

    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})

    if err != nil {
        return err
    }

    db.Where("ID = ?", id).Delete(&task)
    return nil

}
```
This function is similar to the `Gettask()` function only difference is, here we are using `Delete(&task)` to delete the particular task.

Now, update the `DeleteTask()` function in the `routes.go` file with the following code,

```
func DeleteTask(c *fiber.Ctx) error {
    id := c.Params("id")

    if id == "" {

        return c.Status(500).JSON(&fiber.Map{

            "message": "id cannot be empty",
        })
    }

    err := database.Deletetask(id)
    if err != nil {
        return c.Status(500).JSON(&fiber.Map{
            "data":    nil,
            "success": false,
            "message": err,
        })
    }

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

}
```
Similar to the function `GetTask()` , we are taking the 'id' parameter of the task that needs to be Deleted. If the 'id' is not present, an error is sent to the user with the Fiber function.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mt4rp95y9g2m8c35ol44.png)

#### UPDATE :

For this, create a new function `Updatetask` in the `dbconn.go` file and add the following code,

```
func Updatetask(name string, status string, id string) (Task, error) {
    var newTask = Task{Name: name, Status: status}

    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        return newTask, err
    }

    db.Where("ID = ?", id).Updates(&Task{Name: newTask.Name, Status: newTask.Status})
    return newTask, nil
}
```
Here, we have passed the name, status as well as the id of the Task that needs to Update. `Updates()` function is used for updating multiple columns of the Table.

Now, let's update the `UpdateTask()` function in the `routes.go` file through which we are going to call and pass the parameters(name, status and id) to `Updatetask()` function in the `dbconn.go` file.

```
func UpdateTask(c *fiber.Ctx) error {
    id := c.Params("id")

    if id == "" {

        return c.Status(500).JSON(&fiber.Map{

            "message": "id cannot be empty",
        })
    }

    newTask := new(database.Task)

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

    result, err := database.Updatetask(newTask.Name, newTask.Status, id)

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

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/35d6ncfpke7b7uayr9w6.png)

Here, The name of the Task with 'id: 1' was `Fiber Tutorial Series` now it is updated to `Todo-list-API` , keeping the `status` value same.

So, now you've successfully created the CRUD Operations - Create, Read, Update, Delete.

You can find the complete code repository for this tutorial here 👉[Github](https://github.com/Siddheshk02/todo-list-api)

## Conclusion 🎉:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/empufninxx0qio80arhq.gif)

I hope you must have understand how to create a REST API using Go-Fiber, PostgreSQL DB and GORM. Now, try building something with your ideas that will make you learn faster :)

To get more information about Golang concepts, projects, etc. and to stay updated on the Tutorials do follow [Siddhesh on Twitter](https://twitter.com/siddhesh1102) and [GitHub](https://github.com/Siddheshk02).

Until then **Keep Learning, Keep Building** 🚀🚀