DEV Community

Cover image for Building CRUD Operations in Golang πŸŽ‰
Siddhesh Khandagale
Siddhesh Khandagale

Posted on

Building CRUD Operations in Golang πŸŽ‰

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

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

After Running main.go ,

Image description

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.
}
Enter fullscreen mode Exit fullscreen mode

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")
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode
func InitDB() error {
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        return err
    }

    db.AutoMigrate(&Task{})

    return nil
}
Enter fullscreen mode Exit fullscreen mode

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.

    ...
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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

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
}
Enter fullscreen mode Exit fullscreen mode

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",
    })
}
Enter fullscreen mode Exit fullscreen mode

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

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
}
Enter fullscreen mode Exit fullscreen mode

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": "",
    })
}
Enter fullscreen mode Exit fullscreen mode

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

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

}
Enter fullscreen mode Exit fullscreen mode

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",
    })

}
Enter fullscreen mode Exit fullscreen mode

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

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Image description

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 πŸŽ‰:

Image description

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 πŸš€πŸš€

Top comments (0)