In this article, you will learn how to create a CRUD REST API in Golang using Fiber as the web framework and PostgreSQL as the database. By the end of this tutorial, you will be able to create, read, update, and delete feedback entries in the PostgreSQL database using HTTP requests.

We will start from scratch, covering everything from setting up the Go project and spinning up a PostgreSQL server using Docker, to connecting the Go application to the PostgreSQL database and creating route controllers and corresponding routes.

More practice:

CRUD Operations on PostgreSQL with a Golang REST API

Run and Test the REST API

To get the Go API project up and running on your machine, follow these simple steps:

  1. Download or clone the Golang REST API project from its GitHub repository: https://github.com/wpcodevo/go-postgres-crud-rest-api. Afterwards, open the project in your preferred IDE or text editor.
  2. Make sure Docker is running on your computer, then open the terminal in the project’s root directory and run docker-compose up -d to start the PostgreSQL server.
  3. Next, run the command go run main.go. This will install all necessary packages, migrate the GORM schema to the PostgreSQL database, and start the Fiber HTTP server.
  4. First, import the Feedback App.postman_collection.json file from the root directory into Postman or the Thunder Client extension in VS Code. This collection includes predefined CRUD HTTP requests, sample request data, and other settings to streamline testing the CRUD endpoints.
  5. With the collection file now imported into Postman, you can test the various CRUD endpoints by sending requests to the Go server.

Set Up the Golang Project

To start, we need to initialize the Go project. Navigate to your desktop or any preferred location where you’d like to store the source code, and create a new directory named go-postgres-crud-rest-api. You can name the project folder anything you like.

Once the directory is created, open it in your IDE or text editor. In the integrated terminal, run the following command to initialize the folder as a Go project. Be sure to replace <github_username> with your GitHub username:


go mod init github.com//go-postgres-crud-rest-api

Next, install the dependencies required for building the REST API. This tutorial will use the Fiber framework to handle HTTP requests and GORM to interact with a PostgreSQL database. Run the following commands to add these dependencies:


go get github.com/gofiber/fiber/v2
go get github.com/google/uuid
go get github.com/go-playground/validator/v10
go get -u gorm.io/gorm
go get gorm.io/driver/postgres
go get github.com/spf13/viper

  • fiber – A web framework inspired by Express.js. It provides routing, middleware, and other tools for creating REST APIs efficiently in Go.
  • uuid – A package for generating universally unique identifiers (UUIDs) in Go.
  • validator – A package that provides a set of tools for validating struct fields.
  • gorm – An ORM (Object-Relational Mapping) library for Go.
  • postgres – A PostgreSQL driver for GORM.
  • viper – A configuration management package that loads configuration values from different sources, like environment variables or config files.

With the dependencies installed, let’s set up a basic Fiber HTTP server to get us started before diving into the actual CRUD implementation. We’ll create a health check endpoint that returns a simple message in a JSON object.

In the root directory of your project, create a main.go file and add the following code:

main.go


package main

import (
	"log"

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

func main() {

	app := fiber.New()

	app.Get("/api/healthchecker", func(c *fiber.Ctx) error {
		return c.Status(200).JSON(fiber.Map{
			"status":  "success",
			"message": "CRUD Operations on PostgreSQL using Golang REST API",
		})
	})

	log.Fatal(app.Listen(":8000"))
}

Next, start the Fiber HTTP server by running:

go run main.go

Within a few seconds, the server should be listening on port 8000. To automatically restart the server whenever you make changes, install the Air binary with the following:

go install github.com/air-verse/air@latest

After installing Air, stop the currently running server and run:

air

This will start the server with automatic reloading enabled. Now, open your browser and go to http://localhost:8000/api/healthchecker. You should see a JSON response indicating that the Go server is running correctly.

testing the health checker endpoint of the fiber HTTP server

Set Up PostgreSQL with Docker

Next, let’s set up a PostgreSQL database using Docker. You can skip this step if you plan to use a cloud-based PostgreSQL database instead.

In the root directory of your project, create a file named docker-compose.yml and add the following configuration:

docker-compose.yml


services:
  postgres:
    image: postgres:latest
    container_name: postgres
    ports:
      - '6500:5432'
    volumes:
      - progresDB:/var/lib/postgresql/data
    env_file:
      - ./app.env
  pgAdmin:
    image: dpage/pgadmin4
    container_name: pgAdmin
    env_file:
      - ./app.env
    ports:
      - '5050:80'
volumes:
  progresDB:

In addition to the Postgres service, we have included a pgAdmin server, which provides a web-based tool for managing and administering PostgreSQL databases.

In the Docker Compose configuration, both the Postgres and pgAdmin services retrieve their secrets from an app.env file. To ensure these credentials are available to Docker Compose, create an app.env file in your root directory and include the following environment variables:

app.env


POSTGRES_HOST=127.0.0.1
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password123
POSTGRES_DB=golang-gorm
POSTGRES_PORT=6500

PORT=8000

PGADMIN_DEFAULT_EMAIL=admin@admin.com
PGADMIN_DEFAULT_PASSWORD=password123

With the environment variables set, run the command docker-compose up -d to start the Postgres and pgAdmin servers in their respective Docker containers.

both the pgAdmin and PostgreSQL servers running in Docker Containers

Load the Environment Variables

Next, let’s use the Viper package to load the environment variables from the app.env file so that we can access them within our application.

To do this, create a new directory called initializers at the root level of your project. Within the initializers directory, create a file named env.go and add the following code:

initializers/env.go


package initializers

import (
	"github.com/spf13/viper"
)

type Env struct {
	DBHost         string `mapstructure:"POSTGRES_HOST"`
	DBUserName     string `mapstructure:"POSTGRES_USER"`
	DBUserPassword string `mapstructure:"POSTGRES_PASSWORD"`
	DBName         string `mapstructure:"POSTGRES_DB"`
	DBPort         string `mapstructure:"POSTGRES_PORT"`
	ServerPort     string `mapstructure:"PORT"`
}

func LoadEnv(path string) (Env Env, err error) {
	viper.AddConfigPath(path)
	viper.SetConfigType("env")
	viper.SetConfigName("app")

	viper.AutomaticEnv()

	err = viper.ReadInConfig()
	if err != nil {
		return
	}

	err = viper.Unmarshal(&Env)
	return
}

Connect the App to the PostgreSQL Database

Now, let’s create the code that will enable our application to connect to the PostgreSQL server. We will also use the AutoMigrate function provided by GORM to apply our migrations to the Postgres schema whenever we start our Fiber server.

Navigate to the initializers directory and create a file named db.go, then insert the following code:

initializers/db.go


package initializers

import (
	"fmt"
	"log"

	"github.com/wpcodevo/go-postgres-crud-rest-api/models"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

var DB *gorm.DB

func ConnectDB(env *Env) {
	var err error
	dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Shanghai", env.DBHost, env.DBUserName, env.DBUserPassword, env.DBName, env.DBPort)

	DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
	if err != nil {
		log.Fatal("Failed to connect to the Database")
	}

	DB.Logger = logger.Default.LogMode(logger.Info)

	DB.AutoMigrate(&models.Feedback{})

	fmt.Println("🚀 Connected Successfully to the Database")
}

In the code above, we configured GORM to output logs related to database operations in the terminal by using the logger.Default.LogMode() method.

Create the Database Model and Request Schemas

Since we referenced the models.Feedback{} struct in the database connection code but haven’t created it yet, let’s proceed to define it.

To create our database model along with the validation logic, start by creating a new folder called models in the root directory of your project.

Inside the models directory, create a file named feedback.model.go and include the following code:

models/feedback.model.go


package models

import (
	"time"

	"github.com/go-playground/validator/v10"
	"github.com/google/uuid"
	"gorm.io/gorm"
)

type Feedback struct {
	ID        string    `gorm:"type:char(36);primary_key" json:"id,omitempty"`
	Name      string    `gorm:"not null" json:"name,omitempty"`
	Email     string    `gorm:"not null" json:"email,omitempty"`
	Feedback  string    `gorm:"uniqueIndex:idx_feedback;not null" json:"feedback,omitempty"`
	Rating    *float32  `gorm:"not null" json:"rating,omitempty"`
	Status    string    `json:"status,omitempty"`
	CreatedAt time.Time `gorm:"not null;default:'1970-01-01 00:00:01'" json:"createdAt,omitempty"`
	UpdatedAt time.Time `gorm:"not null;default:'1970-01-01 00:00:01';ON UPDATE CURRENT_TIMESTAMP" json:"updatedAt,omitempty"`
}

func (feedback *Feedback) BeforeCreate(tx *gorm.DB) (err error) {
	feedback.ID = uuid.New().String()
	return nil
}

var validate = validator.New()

type ErrorResponse struct {
	Field string `json:"field"`
	Tag   string `json:"tag"`
	Value string `json:"value,omitempty"`
}

func ValidateStruct[T any](payload T) []*ErrorResponse {
	var errors []*ErrorResponse
	err := validate.Struct(payload)
	if err != nil {
		for _, err := range err.(validator.ValidationErrors) {
			var element ErrorResponse
			element.Field = err.StructNamespace()
			element.Tag = err.Tag()
			element.Value = err.Param()
			errors = append(errors, &element)
		}
	}
	return errors
}

type CreateFeedbackSchema struct {
	Name     string   `json:"name" validate:"required"`
	Email    string   `json:"email" validate:"required"`
	Feedback string   `json:"feedback" validate:"required"`
	Rating   *float32 `json:"rating" validate:"required"`
	Status   string   `json:"status,omitempty"`
}

type UpdateFeedbackSchema struct {
	Name     string   `json:"name,omitempty"`
	Email    string   `json:"email,omitempty"`
	Feedback string   `json:"feedback,omitempty"`
	Rating   *float32 `json:"rating,omitempty"`
	Status   string   `json:"status,omitempty"`
}

  • ValidateStruct – This function allows us to validate the data fields included in the incoming request body.
  • CreateFeedbackSchema – This struct defines the expected format of the incoming data when creating new feedback.
  • UpdateFeedbackSchema – This struct defines the expected format of the incoming data when updating an existing feedback entry in the application.

Perform the CRUD Operations

Now that we have established a connection between our application and the running PostgreSQL database, we can proceed to implement the CRUD endpoint handlers that will perform the necessary operations on the database.

Handle the Create Operation

Let’s begin with the Create operation. We will start by extracting the data from the incoming request body. Next, we will use the ValidateStruct function to validate the data according to the defined validation rules.

Once the data passes validation, the DB.Create function will insert the new feedback into the database. If the operation is successful, a copy of the feedback will be returned to the user in a JSON response.

To implement this, create a controllers directory. Within this directory, create a feedback.controller.go file and include the following code:

controllers/feedback.controller.go


func CreateFeedbackHandler(c *fiber.Ctx) error {
	var payload *models.CreateFeedbackSchema

	if err := c.BodyParser(&payload); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"status": "fail", "message": err.Error()})
	}

	errors := models.ValidateStruct(payload)
	if errors != nil {
		return c.Status(fiber.StatusBadRequest).JSON(errors)

	}

	now := time.Now()
	newFeedback := models.Feedback{
		Name:      payload.Name,
		Email:     payload.Email,
		Feedback:  payload.Feedback,
		Rating:    payload.Rating,
		Status:    payload.Status,
		CreatedAt: now,
		UpdatedAt: now,
	}

	result := initializers.DB.Create(&newFeedback)

	if result.Error != nil && strings.Contains(result.Error.Error(), "duplicate key value violates unique") {
		return c.Status(fiber.StatusConflict).JSON(fiber.Map{"status": "fail", "message": "Feedback already exists"})
	} else if result.Error != nil {
		return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"status": "error", "message": result.Error.Error()})
	}

	return c.Status(fiber.StatusCreated).JSON(fiber.Map{"status": "success", "data": fiber.Map{"note": newFeedback}})
}

Handle the Read Operation

Next, let’s implement the Read operations. We will create two route handlers: one will allow the user to retrieve a paginated list of feedback items from the database, while the other will enable the user to retrieve a single feedback item by its ID.

Let’s begin with the handler for retrieving multiple feedback items. In the feedback.controller.go file, add the following code:

controllers/feedback.controller.go


func FindFeedbacksHandler(c *fiber.Ctx) error {
	var page = c.Query("page", "1")
	var limit = c.Query("limit", "10")

	intPage, _ := strconv.Atoi(page)
	intLimit, _ := strconv.Atoi(limit)
	offset := (intPage - 1) * intLimit

	var feedbacks []models.Feedback
	results := initializers.DB.Limit(intLimit).Offset(offset).Find(&feedbacks)
	if results.Error != nil {
		return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"status": "error", "message": results.Error})
	}

	return c.Status(fiber.StatusOK).JSON(fiber.Map{"status": "success", "results": len(feedbacks), "feedbacks": feedbacks})
}

In the code above, we extracted the page and limit query parameters from the request URL. We then parsed these parameters into integers and calculated the offset accordingly.

Using the limit, offset, and GORM, we queried the database to retrieve a paginated list of feedback items. Finally, we returned the feedback items retrieved by GORM to the user in a JSON object.

Now, let’s move on to retrieving a single feedback item by its ID. To implement this functionality, add the following code to the feedback.controller.go file.

controllers/feedback.controller.go


func FindFeedbackByIdHandler(c *fiber.Ctx) error {
	feedbackId := c.Params("feedbackId")

	var feedback models.Feedback
	result := initializers.DB.First(&feedback, "id = ?", feedbackId)
	if err := result.Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"status": "fail", "message": "No feedback with that Id exists"})
		}
		return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"status": "fail", "message": err.Error()})
	}

	return c.Status(fiber.StatusOK).JSON(fiber.Map{"status": "success", "data": fiber.Map{"feedback": feedback}})
}

In the code above, we extracted the feedback ID from the request URL parameter. Using GORM, we attempted to retrieve the corresponding feedback item from the database. If the item is not found, we respond with a 404 error. Conversely, if a feedback item is found, we return it in the JSON response.

Handle the Update Operation

Now, let’s implement the Update operation. This process is similar to retrieving a single feedback item; however, this time we will update the fields of the feedback item based on the data provided in the request body. To achieve this, add the following code to the feedback.controller.go file.

controllers/feedback.controller.go


func UpdateFeedbackHandler(c *fiber.Ctx) error {
	feedbackId := c.Params("feedbackId")

	var payload *models.UpdateFeedbackSchema

	if err := c.BodyParser(&payload); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"status": "fail", "message": err.Error()})
	}

	var feedback models.Feedback
	result := initializers.DB.First(&feedback, "id = ?", feedbackId)
	if err := result.Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"status": "fail", "message": "No feedback with that Id exists"})
		}
		return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"status": "fail", "message": err.Error()})
	}

	updates := make(map[string]interface{})
	if payload.Name != "" {
		updates["name"] = payload.Name
	}
	if payload.Email != "" {
		updates["email"] = payload.Email
	}
	if payload.Feedback != "" {
		updates["feedback"] = payload.Feedback
	}
	if payload.Status != "" {
		updates["status"] = payload.Status
	}

	if payload.Rating != nil {
		updates["rating"] = payload.Rating
	}

	updates["updated_at"] = time.Now()

	initializers.DB.Model(&feedback).Updates(updates)

	return c.Status(fiber.StatusOK).JSON(fiber.Map{"status": "success", "data": fiber.Map{"feedback": feedback}})
}

Let’s break down the steps taken in the code above. First, we extracted the feedback item to be updated from the query parameters. Next, we queried the database to retrieve the record that matches the provided ID.

If a record is found, we update the fields with the data from the request body. Finally, we save the changes to the database and return a copy of the updated feedback item in the JSON response.

Handle the Delete Operation

Let’s now handle the final operation: Delete. This implementation is straightforward; we will query the database to find the feedback item that matches the provided ID and then remove it. Below is the implementation:

controllers/feedback.controller.go


func DeleteFeedbackHandler(c *fiber.Ctx) error {
	feedbackId := c.Params("feedbackId")

	result := initializers.DB.Delete(&models.Feedback{}, "id = ?", feedbackId)

	if result.RowsAffected == 0 {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"status": "fail", "message": "No note with that Id exists"})
	} else if result.Error != nil {
		return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"status": "error", "message": result.Error})
	}

	return c.SendStatus(fiber.StatusNoContent)
}

The Complete CRUD Code

If you skipped the implementation of the CRUD handlers, you can catch up by adding the complete CRUD code provided below to your feedback.controller.go file.

controllers/feedback.controller.go


package controllers

import (
	"strconv"
	"strings"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/wpcodevo/go-postgres-crud-rest-api/initializers"
	"github.com/wpcodevo/go-postgres-crud-rest-api/models"
	"gorm.io/gorm"
)

func CreateFeedbackHandler(c *fiber.Ctx) error {
	var payload *models.CreateFeedbackSchema

	if err := c.BodyParser(&payload); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"status": "fail", "message": err.Error()})
	}

	errors := models.ValidateStruct(payload)
	if errors != nil {
		return c.Status(fiber.StatusBadRequest).JSON(errors)

	}

	now := time.Now()
	newFeedback := models.Feedback{
		Name:      payload.Name,
		Email:     payload.Email,
		Feedback:  payload.Feedback,
		Rating:    payload.Rating,
		Status:    payload.Status,
		CreatedAt: now,
		UpdatedAt: now,
	}

	result := initializers.DB.Create(&newFeedback)

	if result.Error != nil && strings.Contains(result.Error.Error(), "duplicate key value violates unique") {
		return c.Status(fiber.StatusConflict).JSON(fiber.Map{"status": "fail", "message": "Feedback already exists"})
	} else if result.Error != nil {
		return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"status": "error", "message": result.Error.Error()})
	}

	return c.Status(fiber.StatusCreated).JSON(fiber.Map{"status": "success", "data": fiber.Map{"note": newFeedback}})
}

func FindFeedbacksHandler(c *fiber.Ctx) error {
	var page = c.Query("page", "1")
	var limit = c.Query("limit", "10")

	intPage, _ := strconv.Atoi(page)
	intLimit, _ := strconv.Atoi(limit)
	offset := (intPage - 1) * intLimit

	var feedbacks []models.Feedback
	results := initializers.DB.Limit(intLimit).Offset(offset).Find(&feedbacks)
	if results.Error != nil {
		return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"status": "error", "message": results.Error})
	}

	return c.Status(fiber.StatusOK).JSON(fiber.Map{"status": "success", "results": len(feedbacks), "feedbacks": feedbacks})
}

func UpdateFeedbackHandler(c *fiber.Ctx) error {
	feedbackId := c.Params("feedbackId")

	var payload *models.UpdateFeedbackSchema

	if err := c.BodyParser(&payload); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"status": "fail", "message": err.Error()})
	}

	var feedback models.Feedback
	result := initializers.DB.First(&feedback, "id = ?", feedbackId)
	if err := result.Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"status": "fail", "message": "No feedback with that Id exists"})
		}
		return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"status": "fail", "message": err.Error()})
	}

	updates := make(map[string]interface{})
	if payload.Name != "" {
		updates["name"] = payload.Name
	}
	if payload.Email != "" {
		updates["email"] = payload.Email
	}
	if payload.Feedback != "" {
		updates["feedback"] = payload.Feedback
	}
	if payload.Status != "" {
		updates["status"] = payload.Status
	}

	if payload.Rating != nil {
		updates["rating"] = payload.Rating
	}

	updates["updated_at"] = time.Now()

	initializers.DB.Model(&feedback).Updates(updates)

	return c.Status(fiber.StatusOK).JSON(fiber.Map{"status": "success", "data": fiber.Map{"feedback": feedback}})
}

func FindFeedbackByIdHandler(c *fiber.Ctx) error {
	feedbackId := c.Params("feedbackId")

	var feedback models.Feedback
	result := initializers.DB.First(&feedback, "id = ?", feedbackId)
	if err := result.Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"status": "fail", "message": "No feedback with that Id exists"})
		}
		return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"status": "fail", "message": err.Error()})
	}

	return c.Status(fiber.StatusOK).JSON(fiber.Map{"status": "success", "data": fiber.Map{"feedback": feedback}})
}

func DeleteFeedbackHandler(c *fiber.Ctx) error {
	feedbackId := c.Params("feedbackId")

	result := initializers.DB.Delete(&models.Feedback{}, "id = ?", feedbackId)

	if result.RowsAffected == 0 {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"status": "fail", "message": "No note with that Id exists"})
	} else if result.Error != nil {
		return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"status": "error", "message": result.Error})
	}

	return c.SendStatus(fiber.StatusNoContent)
}

Set Up CORS and the CRUD Endpoints

It’s time to create routes with Fiber that will invoke our CRUD handlers. We will also set up CORS on the server to allow it to accept requests from cross-origin domains.

To do this, open the main.go file and replace its existing content with the code provided below.

main.go


package main

import (
	"log"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/cors"
	"github.com/gofiber/fiber/v2/middleware/logger"
	"github.com/wpcodevo/go-postgres-crud-rest-api/controllers"
	"github.com/wpcodevo/go-postgres-crud-rest-api/initializers"
)

func main() {
	env, err := initializers.LoadEnv(".")
	if err != nil {
		log.Fatal("🚀 Could not load environment variables", err)
	}
	initializers.ConnectDB(&env)
	app := fiber.New()
	micro := fiber.New()

	app.Mount("/api", micro)
	app.Use(logger.New())
	app.Use(cors.New(cors.Config{
		AllowOrigins:     "http://localhost:3000",
		AllowHeaders:     "Origin, Content-Type, Accept",
		AllowMethods:     "GET, POST, PATCH, DELETE",
		AllowCredentials: true,
	}))

	micro.Route("/feedbacks", func(router fiber.Router) {
		router.Post("/", controllers.CreateFeedbackHandler)
		router.Get("", controllers.FindFeedbacksHandler)
	})

	micro.Route("/feedbacks/:feedbackId", func(router fiber.Router) {
		router.Get("", controllers.FindFeedbackByIdHandler)
		router.Patch("", controllers.UpdateFeedbackHandler)
		router.Delete("", controllers.DeleteFeedbackHandler)
	})

	micro.Get("/healthchecker", func(c *fiber.Ctx) error {
		return c.Status(200).JSON(fiber.Map{
			"status":  "success",
			"message": "CRUD Operations on PostgreSQL using Golang REST API",
		})
	})

	log.Fatal(app.Listen(":" + env.ServerPort))
}

We have completed the REST API implementation. You can start the Fiber HTTP server by running air, provided you have the Air binary installed, or by using the command go run main.go.

Once the server is up and running, you can use the Postman extension in VS Code to make requests and test the CRUD functionality.

Let me demonstrate how to create a feedback item. Define the URL as http://localhost:8000/api/feedbacks/ and set the HTTP method to POST.

Next, click on the “Body” tab and add the following data, ensuring that the body type is set to JSON.


{
    "name": "John Doe",
    "email": "johndoe@gmail.com",
    "feedback": "Thanks CodevoWeb. I improved my Rust skills by following your Rust articles.",
    "rating": 4.5,
    "status": "active"
}

Once you have completed this, send the request. The server will insert the feedback into the database and respond with a copy of the newly created feedback in the JSON response.

creating a feedback item with the Go REST CRUD API

Conclusion

In this comprehensive tutorial, you learned how to perform CRUD operations on a PostgreSQL database using a Golang REST API. I hope you found this article both informative and enjoyable. If you have any questions or feedback, please feel free to leave them in the comment section. Thank you for reading!