Chào các bạn, trong bài viết trước chúng ta đã xử lý các API liên quan đến resource users như: 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.

Trong bài viết này chúng ta sẽ tiếp tục công cuộc hoàn thành ứng dụng REST API với các xử lý liên quan đến resource posts. Các xử lý chúng ta cần thực hiện bao gồm:

  • GET /users/:id/posts : Lấy danh sách post của user
  • POST /users/:id/posts : Tạo post mới
  • GET /users/:id/posts/:postId : Lấy chi tiết post
  • PUT /users/:id/posts/:postId : Cập nhật post
  • DELETE /users/:id/posts/:postId : Xóa post

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

Lấy danh sách post của user

Đầu tiên chúng ta định nghĩa struct Post

Trong model này, chúng ta cần có ý trường tableName struct{} pg:"test.posts". Nó mô tả bảng trong database mà model này ánh xạ tới, cụ thể trong trường hợp này là schema test, table posts

// file model/post.go

type Post struct {
    tableName struct{}  `pg:"test.posts"`
    Id        string    `pg:"id,pk" json:"id"`
    Title     string    `json:"title"`
    Content   string    `json:"content"`
    AuthorId  string    `json:"author_id"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

Tương ứng với API GET /users/:id/posts chúng ta sẽ định nghĩa controller GetPostsOfUser để lấy danh sách post tương ứng với user

// file controller/post.go

func GetPostsOfUser(c *fiber.Ctx) error {
    // Đọc id của user trên URL
    userId := c.Params("id")

    // Lấy thông tin post tương ứng với userID
    posts, err := repo.GetPostsOfUser(userId)
    if err != nil {
        return ResponseErr(c, fiber.StatusNotFound, err.Error())
    }

    // Trả về kết quả cho client
    return c.JSON(posts)
}

Khai báo function GetPostsOfUser trong post_repo có tác dụng thao tác với database để lấy ra danh sách post tương ứng với user

// file repo/post_repo.go

func GetPostsOfUser(userId string) (posts []model.Post, err error) {
    _, err = DB.Query(&posts, `
        SELECT p.id, p.title, p.content, p.author_id, p.created_at, p.updated_at
        FROM test.posts p
        INNER JOIN test.users u
        ON p.author_id = u.id
        WHERE u.id = ?
    `, userId)

    if err != nil {
        return nil, err
    }

    return posts, err
}

Tạo post mới

Để tạo post mới, chúng ta cần đọc thông tin của body của request khi client gửi lên

Định nghĩa struct CreatePost để hứng dữ liệu từ body của request

type CreatePost struct {
    Title    string `json:"title"`
    Content  string `json:"content"`
}

Sau đó gọi hàm CreatePost trong repo để xử lý insert bản ghi mới vào trong database

func CreatePost(c *fiber.Ctx) error {
    // Đọc id của user trên URL
    userId := c.Params("id")

    // Đọc dữ liệu từ body của request
    req := new(model.CreatePost)
    if err := c.BodyParser(req); err != nil {
        return ResponseErr(c, fiber.StatusBadRequest, err.Error())
    }

    // Tạo post mới dựa trên uesrId và thông tin dữ liệu đọc được từ request
    post, err := repo.CreatePost(userId, req)
    if err != nil {
        return ResponseErr(c, fiber.StatusNotFound, err.Error())
    }

    // Trả về post mới sau khi tạo thành công cho client
    return c.JSON(post)
}

Trong CreatePost, chúng ta kiểm tra xem giá trị của thuộc tính "Title" rỗng hay không? Nếu có thì báo lỗi

Còn không thì chúng ta sẽ khởi tạo 1 đối tượng post tương ứng với userId và insert vào trong database

// file repo/post_repo.go

func CreatePost(userId string, req *model.CreatePost) (post *model.Post, err error) {
    // Kiểm tra title trong request có rỗng hay không?
    if req.Title == "" {
        return nil, errors.New("tiêu đề không được để trống")
    }

    // Tạo instance post dựa trên dữ liệu từ request
    post = &model.Post{
        Id:        NewID(),
        Title:     req.Title,
        Content:   req.Content,
        AuthorId:  userId,
        CreatedAt: time.Now(),
    }

    // Insert vào trong database
    _, err = DB.Model(post).WherePK().Returning("*").Insert()
    if err != nil {
        return nil, err
    }

    return post, nil
}

Lấy chi tiết post

Muốn lấy thông tin của post, chúng ta cần biết id của user và post, sử dụng c.Params để lấy id của user và post trên URL

Định nghĩa function GetPostDetail trong repo để lấy thông tin của post

// file controller/post.go
func GetPostDetail(c *fiber.Ctx) error {
    // Đọc id của user trên URL
    userId := c.Params("id")

    // Đọc id của post trên URL
    postId := c.Params("postId")

    // Lấy thông tin của post dựa trên userId và postId
    post, err := repo.GetPostDetail(userId, postId)
    if err != nil {
        return ResponseErr(c, fiber.StatusNotFound, err.Error())
    }

    // Trả thông tin post về cho client
    return c.JSON(post)
}

// file repo/user_post.go
func GetPostDetail(userId string, postId string) (post model.Post, err error) {
    _, err = DB.Query(&post, `
        SELECT p.id, p.title, p.content, p.author_id, p.created_at, p.updated_at
        FROM test.posts p
        INNER JOIN test.users u
        ON p.author_id = u.id
        WHERE p.author_id = ? AND p.id = ?
    `, userId, postId)

    if err != nil {
        return model.Post{}, err
    }

    return post, err
}

Cập nhật post

Chúng ta sử dụng chúng model CreatePost để cập nhật thông tin của post

Tương tự như lấy thông tin của post, chúng ta cần phải biết chúng ta sẽ cập nhật thông tin cho post nào, bằng cách sử dụng c.Params("id") để lấy thông tin id của user và post tương ứng trên URL

// file controller/post.go
func UpdatePost(c *fiber.Ctx) error {
    // Đọc id của user trên URL
    userId := c.Params("id")

    // Đọc id của post trên URL
    postId := c.Params("postId")

    // Đọc dữ liệu từ body của request
    req := new(model.CreatePost)
    if err := c.BodyParser(req); err != nil {
        return ResponseErr(c, fiber.StatusBadRequest, err.Error())
    }

    // Cập nhật thông tin post dựa trên uesrId và thông tin dữ liệu đọc được từ request
    post, err := repo.UpdatePost(userId, postId, req)
    if err != nil {
        return ResponseErr(c, fiber.StatusNotFound, err.Error())
    }

    // Trả về post sau khi cập nhật thành công cho client
    return c.JSON(post)
}

// file repo/post_repo.go
func UpdatePost(userId string, postId string, req *model.CreatePost) (post *model.Post, err error) {
    // Kiểm tra title trong request có rỗng hay không?
    if req.Title == "" {
        return nil, errors.New("tiêu đề không được để trống")
    }

    // Tạo instance post dựa trên dữ liệu từ request để tiến hành cập nhật dữ liệu
    post = &model.Post{
        Id:        postId,
        Title:     req.Title,
        Content:   req.Content,
        AuthorId:  userId,
        UpdatedAt: time.Now(),
    }

    // Cập nhật các trường cần thiết trong database
    _, err = DB.Model(post).Column("title", "content", "updated_at").Returning("*").Where("id = ? AND author_id = ?", postId, userId).Update()
    if err != nil {
        return nil, err
    }

    return post, nil
}

Xóa post

Định nghĩa function DeletePost để thực hiện xóa post của user tương ứng trong database

// file controller/post.go
func DeletePost(c *fiber.Ctx) error {
    // Đọc id của user trên URL
    userId := c.Params("id")

    // Đọc id của post trên URL
    postId := c.Params("postId")

    // Xóa post dựa trên uesrId và postId
    err := repo.DeletePost(userId, postId)
    if err != nil {
        return ResponseErr(c, fiber.StatusNotFound, err.Error())
    }

    return c.JSON("Xóa post thành công")
}

// file repo/post_repo.go
func DeletePost(userId string, postId string) (err error) {
    _, err = DB.Exec(`
        DELETE FROM test.posts
        WHERE id = ? AND author_id = ?
    `, postId, userId)
    if err != nil {
        return err
    }

    return nil
}

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

Vậy là chúng ta đã thực hiện xong ứng dụng REST Api sử dụng Fiber Framework của Golang và Postgresql. Hi vọng các bạn thấy bài viết hay và hữu ích