Building CRUD Operations in Golang ๐ŸŽ‰

Building CRUD Operations in Golang ๐ŸŽ‰

Creating a REST-API using Go, Fiber, PostgreSQL DB and GORM.

ยท

9 min read

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.

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 :)

Installations :

  • Golang

  • Go-Fiber: We'll see this ahead in the tutorial.

  • PostgreSQL

  • 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 ,

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.

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.

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.

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.

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
}

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

Conclusion ๐ŸŽ‰:

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 and GitHub.

Until then Keep Learning, Keep Building ๐Ÿš€๐Ÿš€

ย