Chào các bạn, trong bài viết này mình xin giới thiệu với các bạn cách tạo 1 ứng dụng REST API đơn giản sử dụng FiberPostgresql. Mục tiêu của bài viết để giới thiệu cho những người mới làm quen với Go và Fiber về cách hoạt động cũng như sự đơn giản của ngôn ngữ và framework này.

Bắt đầu thôi nào 😁😁

Tạo project

Bước đầu tiên chúng ta sẽ khởi tạo thư mục chứa project

$ mkdir crud-app
$ cd crud-app

Trong thư mục chứa project, chúng ta tạo go module cho project

$ go mod init fiber-postgres

Cấu trúc của project của chúng ta như sau

.
├── controller    // Code xử lý routes
├── ddl           // Các câu lệnh ddl tạo bảng
├── go.mod
├── go.sum
├── main.go       // File chạy chính
├── model         // Định nghĩa các model có trong project
├── repo          // Code xử lý thao tác trực tiếp với CSDL
├── router        // Định nghĩa các routes
└── test          // Viết các unit test

Tạo cơ sở dữ liệu postgresql

Trong bài viết này chúng ta sẽ sử dụng GoPG để kết nối với hệ CSDL Postgresql, vì vậy chúng ta cần cài đặt Postgresql trước

Chúng ta có thể cài trực tiếp hệ cơ sở dữ liệu Postgresql trên máy hoặc sử dụng docker để cài đặt. Ở bài viết này thì mình sẽ cài Postgresql sử dụng docker

Chúng ta có thể cài Postgresql thông qua docker compose hoặc docker run

1. Sử dụng docker compose

Tạo file docker-compose.yml

volumes:
  data_db:

services:
  postgres:
    image: postgres-14:alpine
    volumes:
      - db_data:/var/lib/postgresql/data
    ports:
      - 5432:5432
    environment:
      POSTGRES_PASSWORD: 123

Sau đó chạy file docker compose để khởi tạo container

docker-compose up -d

2. Sử dụng docker run

docker run -d --name db_postgresql -p 5432:5432 \
-e POSTGRES_PASSWORD=123 \
postgres-14:alpine

Kết nối với CSDL sử dụng DBeaver

DBeaver là ứng dụng UI giúp chúng ta kết nối và thao tác với các hệ CSDL : Postgresql, mysql, DB2, Oracle, ...

Kết nối với CSDL sử dụng DBeaver

Nếu thành công, chúng ta sẽ có giao diện về Postgresql như sau

Giao diện DBeaver kết nối với Postgresql

Bây giờ chúng ta sẽ tạo bảng users và bảng posts trong database sử dụng DBeaver để tạo

CREATE TABLE test.users (
    id text PRIMARY KEY,
    full_name text,
    email text,
    phone text,
    created_at TIMESTAMP with time zone,
    updated_at TIMESTAMP with time zone
)

CREATE TABLE test.posts (
    id text PRIMARY KEY,
    title text,
    content text,
    author_id text REFERENCES test.users(id) ON DELETE CASCADE,
    created_at TIMESTAMP with time zone,
    updated_at TIMESTAMP with time zone
)

Khởi tạo server và kết nối database

Để làm việc với Fiber framework và Postgresql trong golang, chúng ta sẽ download 2 package về

go get github.com/gofiber/fiber/v2
go get github.com/go-pg/pg/v10

Trong file main.go. Chúng ta thực hiên các công việc sau:

  • Kết nối đến database
  • Khởi tạo fiber app
  • Định nghĩa các routes có trong ứng dụng
  • Lắng nghe server
// file main.go

package main

import (
    "fiber-postgres/repo"
    "fiber-postgres/router"

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

func main() {
    // Connect Database
    db := repo.ConnectDatabase()
    defer db.Close()

    // Init Fiber App
    app := fiber.New()

    // Register router
    router.InitRouter(app)

    // Fiber App listen port
    app.Listen(":3000")
}

Tiếp đến chúng ta sẽ kết nối với database bằng function ConnectDatabase

Trường hợp nếu như muốn in câu lệnh query trong màn hình terminal, chúng ta có thể sử dụng QueryHook của go-pg

// file repo/base

package repo

import (
    "context"
    "fmt"
    "math/rand"
    "time"

    "github.com/go-pg/pg/v10"
)

var (
    DB     *pg.DB     //kết nối vào CSDL Postgresql
    random *rand.Rand // Đối tượng dùng để tạo random number
)

func ConnectDatabase() *pg.DB {
    // Mở kết nối vào CSDL Postgresql
    DB = pg.Connect(&pg.Options{
        Addr:     "localhost:5432",
        User:     "postgres",
        Password: "123",
        Database: "postgres",
    })

    // Log các câu lệnh SQL thực thi để debug
    DB.AddQueryHook(dbLogger{}) //Log query to console

    //Khởi động engine sinh số ngẫu nhiên
    s1 := rand.NewSource(time.Now().UnixNano())
    random = rand.New(s1)

    return DB
}

type dbLogger struct{}

// Hàm hook (móc câu vào lệnh truy vấn) để in ra câu lệnh SQL query
func (d dbLogger) BeforeQuery(c context.Context, q *pg.QueryEvent) (context.Context, error) {
    return c, nil
}

// Hàm hook chạy sau khi query được thực thi
func (d dbLogger) AfterQuery(c context.Context, q *pg.QueryEvent) error {
    bytes, _ := q.FormattedQuery()
    fmt.Println(string(bytes))
    return nil
}

Trong project này chúng ta sẽ thao tác chủ yếu với 2 resources là : usersposts

Vì vậy chúng ta sẽ định nghĩa các REST API liên quan đến 2 resources này trong file router/router.go

// file router/router.go

package router

import (
    "fiber-postgres/controller"

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

func InitRouter(app *fiber.App) {
    // User router
    app.Get("/users", controller.GetAllUser)
    app.Post("/users", controller.CreateUser)
    app.Get("/users/:id", controller.GetUserById)
    app.Put("/users/:id", controller.UpdateUser)
    app.Delete("/users/:id", controller.DeleteUser)

    // Post router
    app.Get("/users/:id/posts", controller.GetPostsOfUser)
    app.Post("/users/:id/posts", controller.CreatePost)
    app.Get("/users/:id/posts/:postId", controller.GetPostDetail)
    app.Put("/users/:id/posts/:postId", controller.UpdatePost)
    app.Delete("/users/:id/posts/:postId", controller.DeletePost)
}

Khởi tạo dữ liệu mẫu cho database

Chúng ta có thể insert trực tiếp các bản ghi vào trong database hoặc có thể viết function trong golang để thực hiện công việc insert này

Chúng ta sử dụng package "github.com/brianvoe/gofakeit/v6" để fake các dữ liệu cho các bản ghi

Để random id cho các bản ghi chúng ta sử dụng package "github.com/matoous/go-nanoid/v2"

// file repo/util.go

package repo

import (
    gonanoid "github.com/matoous/go-nanoid/v2"
)

/* Sinh mã unique cho primary key
 */
func NewID() (id string) {
    id, _ = gonanoid.New(8)
    return id
}

Tiếp theo chúng ta định nghĩa function MockData để thực hiện công việc mockup data

Vì chúng ta thực hiện insert dữ liệu vào nhiều bảng cùng 1 lúc, nên cần sử dụng transaction

Với mỗi bản ghi user chúng ta sẽ insert 1 vài bản ghi post tương ứng với user đó

// file repo/create.go

package repo

import (
    "fiber-postgres/model"
    "math/rand"

    "github.com/brianvoe/gofakeit/v6"
)

func MockData() (err error) {
    // Khởi tạo transaction khi chúng ta muốn insert dữ liệu trong nhiều bảng
    transaction, err := DB.Begin()
    if err != nil {
        return err
    }

    // Insert 1 số bản ghi user
    for i := 0; i < 10; i++ {
        userId := NewID()
        _, err = transaction.Model(&model.User{
            Id:        userId,
            FullName:  gofakeit.Animal(),
            Email:     gofakeit.Email(),
            Phone:     gofakeit.Phone(),
            CreatedAt: gofakeit.Date(),
        }).Insert()

        if err != nil {
            transaction.Rollback()
            return err
        }

        // Với mỗi bản ghi user, insert 1 số bản ghi post liên quan đến user đó
        for j := 0; j < 5+rand.Intn(5); j++ {
            _, err = transaction.Model(&model.Post{
                Id:        NewID(),
                Title:     gofakeit.Quote(),
                Content:   gofakeit.LoremIpsumSentence(200),
                AuthorId:  userId,
                CreatedAt: gofakeit.Date(),
            }).Insert()

            if err != nil {
                transaction.Rollback()
                return err
            }
        }
    }

    return transaction.Commit()
}

Run test để insert data

// test/create_test.go

package test

import (
    "fiber-postgres/repo"
    "testing"

    "github.com/stretchr/testify/assert"
)

func Test_MockData(t *testing.T) {
    err := repo.MockData()
    assert.Nil(t, err)
}

Kết quả sau khi chạy run test để tạo dữ liệu mẫu

Bảng users

Bảng users

Bảng posts

Bảng posts

Trong bài viết tiếp theo chúng ta sẽ thực hiện xử lý các API liên quan đến resources user

  • Lấy danh sách user
  • Tạo user mới
  • Lấy chi tiết user
  • Cập nhật thông tin user
  • Xóa user

Các bạn có thể tham khảo sources phần nào tại đây: https://github.com/buihien0109/goapp-crud/tree/main/part-1