Movatterモバイル変換


[0]ホーム

URL:


CodevoWeb

PressESC to close

CRUD Operations on PostgreSQL with a Golang REST API

0Comments0

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 rundocker-compose up -d to start the PostgreSQL server.
  3. Next, run the commandgo 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 theFeedback 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 namedgo-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/v2go get github.com/google/uuidgo get github.com/go-playground/validator/v10go get -u gorm.io/gormgo get gorm.io/driver/postgresgo 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 amain.go file and add the following code:

main.go

package mainimport ("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 port8000. To automatically restart the server whenever you make changes, install theAir binary with the following:

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

After installingAir, stop the currently running server and run:

air

This will start the server with automatic reloading enabled. Now, open your browser and go tohttp://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 nameddocker-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 anapp.env file. To ensure these credentials are available to Docker Compose, create anapp.env file in your root directory and include the following environment variables:

app.env

POSTGRES_HOST=127.0.0.1POSTGRES_USER=postgresPOSTGRES_PASSWORD=password123POSTGRES_DB=golang-gormPOSTGRES_PORT=6500PORT=8000PGADMIN_DEFAULT_EMAIL=admin@admin.comPGADMIN_DEFAULT_PASSWORD=password123

With the environment variables set, run the commanddocker-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 theapp.env file so that we can access them within our application.

To do this, create a new directory calledinitializers at the root level of your project. Within theinitializers directory, create a file namedenv.go and add the following code:

initializers/env.go

package initializersimport ("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 theAutoMigrate function provided by GORM to apply our migrations to the Postgres schema whenever we start our Fiber server.

Navigate to theinitializers directory and create a file nameddb.go, then insert the following code:

initializers/db.go

package initializersimport ("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.DBfunc ConnectDB(env *Env) {var err errordsn := 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 thelogger.Default.LogMode() method.

Create the Database Model and Request Schemas

Since we referenced themodels.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 calledmodels in the root directory of your project.

Inside themodels directory, create a file namedfeedback.model.go and include the following code:

models/feedback.model.go

package modelsimport ("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 []*ErrorResponseerr := validate.Struct(payload)if err != nil {for _, err := range err.(validator.ValidationErrors) {var element ErrorResponseelement.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 theValidateStruct function to validate the data according to the defined validation rules.

Once the data passes validation, theDB.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 acontrollers directory. Within this directory, create afeedback.controller.go file and include the following code:

controllers/feedback.controller.go

func CreateFeedbackHandler(c *fiber.Ctx) error {var payload *models.CreateFeedbackSchemaif 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 thefeedback.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) * intLimitvar feedbacks []models.Feedbackresults := 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 thepage andlimit query parameters from the request URL. We then parsed these parameters into integers and calculated the offset accordingly.

Using thelimit,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 thefeedback.controller.go file.

controllers/feedback.controller.go

func FindFeedbackByIdHandler(c *fiber.Ctx) error {feedbackId := c.Params("feedbackId")var feedback models.Feedbackresult := 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 thefeedback.controller.go file.

controllers/feedback.controller.go

func UpdateFeedbackHandler(c *fiber.Ctx) error {feedbackId := c.Params("feedbackId")var payload *models.UpdateFeedbackSchemaif err := c.BodyParser(&payload); err != nil {return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"status": "fail", "message": err.Error()})}var feedback models.Feedbackresult := 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 yourfeedback.controller.go file.

controllers/feedback.controller.go

package controllersimport ("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.CreateFeedbackSchemaif 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) * intLimitvar feedbacks []models.Feedbackresults := 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.UpdateFeedbackSchemaif err := c.BodyParser(&payload); err != nil {return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"status": "fail", "message": err.Error()})}var feedback models.Feedbackresult := 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.Feedbackresult := 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 themain.go file and replace its existing content with the code provided below.

main.go

package mainimport ("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 runningair, provided you have the Air binary installed, or by using the commandgo 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 ashttp://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!

Share Article:

Tags:

How to Validate React.js Forms Without External Libraries

Left Arrow

JSON Web Token Authentication and Authorization in Golang

Right Arrow

Leave a ReplyCancel reply

This site is protected by reCAPTCHA and the GooglePrivacy Policy andTerms of Service apply.

This site uses Akismet to reduce spam.Learn how your comment data is processed.

Support Me!

paypal donate button

Recent posts

Categories


[8]ページ先頭

©2009-2025 Movatter.jp