- vừa được xem lúc

Hiểu về go-pg trong golang qua ví dụ mẫu về blog - P1

0 0 12

Người đăng: Nguyen Tran Nhat Duc

Theo Viblo Asia

Database là một nền tảng quan trọng của mọi ngôn ngữ lập trình. Hãy thử tưởng tượng một trang web code đầy đủ, giao diện đẹp, nhưng thiếu đi cơ sở dữ liệu để lưu trữ thông tin đăng ký, đăng nhập người dùng, các bài viết, trang web đó sẽ có mà như không. Hôm nay mình sẽ giới thiệu cách kết nối và sử dụng cơ sở dữ liệu postgresql trong ngôn ngữ golang bằng ví dụ mẫu về blog đơn giản.

1. Cài đặt

1.1 Cài đặt package

Đầu tiên luôn là thêm package, ta sẽ dùng go-pg version 10

go get github.com/go-pg/pg/v10

1.2. Cấu trúc thư mục

├──config/
| ├──database.go
├──controller/
| ├──base.go
| ├──user.go
| ├──blog.go
| ├──comment.go
├──model
| ├──user.go
| ├──blog.go
| ├──comment.go
├──router/
| ├──routes.go
├──main.go -- this is where the app starts
├──...

2. Bắt đầu

Trong ví dụ này mình sẽ dùng Postman để truyền data gọi API mà không có giao diện.

Cứ cho rằng bạn đã chuẩn bị sẵn pgadmin có sẵn database, giờ ta chỉ việc code kết nối và thao tác với database bằng package go-pg này.

2.1 Kết nối cơ sở dữ liệu, tạo model

config/database: tạo func kết nối cơ sở dữ liệu

package config import ( "golangpostgre/model" "github.com/go-pg/pg/v10/orm" "github.com/go-pg/pg/v10"
) func ConnectDatabase() (db *pg.DB){ db = pg.Connect(&pg.Options{	//Kết nối database gồm nhập addr, user, password và database Addr: ":5432", User: "postgres", Password: "123456", Database: "postgres", }) err := createSchema(db)	//Nếu đã có đầy đủ bảng database có thể bỏ qua bước này if err != nil{ panic(err) } return db
} func createSchema(db *pg.DB) error { models := []interface{}{ //đây là những model sẽ được định nghĩa ra (*model.User)(nil), (*model.Post)(nil), (*model.Comment)(nil), } for _, model := range models {	//Tạo từng bảng trong database err := db.Model(model).CreateTable(&orm.CreateTableOptions{ Temp: false, //Nếu đặt Temp: true sẽ biến thành in-memory database IfNotExists: true,	//Nếu tồn tại bảng sẽ không tạo thêm }) if err != nil { return err } } return nil
}

Bạn có thể chọn định nghĩa model rồi chạy lệnh tạo bảng lên database (như trong ví dụ này sẽ làm để giúp bạn hiểu về go-pg), hoặc có thể tự tạo bảng định nghĩa các cột bên ngoài và bỏ qua bước createschema trên.

In-memory database có thể hiểu đơn giản ở đây là bảng được tạo ra và lưu trữ tạm thời khi chương trình đang chạy, khi bạn tắt chương trình đi toàn bộ bảng và bản ghi sẽ biến mất và bạn lại tạo lại từ đầu.

model/user.go: định nghĩa model user

package model type User struct{ tableName struct{} `pg:"auth.users"`	//bảng users có schema auth Id int `pg:"type:serial,pk"`	//trường id primary key, auto_increment. FirstName string	//trường first_name text LastName string //trường last_name	text Email string	`pg:",unique"`	//trường email không được trùng lặp Password string //trường password text Posts []Post `pg:"rel:has-many"` //quan hệ một nhiều với model Post
}

Lưu ý: khi một trường có tên là id được tạo ra sẽ là primary key

model/blog.go: định nghĩa model post

package model import "time" type Post struct{ tableName struct{} `pg:"blog.post"` Id int `pg:"type:serial" ` Content string `pg:",notnull"`	//trường content text khác null Title string	`pg:",notnull"` CreatedAt time.Time `pg:"type:timestamp without time zone,default:now()"` //trường created_at mặc định là thời điểm hiện tại UpdatedAt time.Time `pg:"type:timestamp without time zone"` UserId int `pg:"type:integer"`	//nếu để mặc định sẽ là bigint, ta override thành integer User User `pg:"rel:has-one"`	//Post quan hệ nhiều một với User
}

model/comment.go: định nghĩa model comment

package model import "time" type Comment struct{ tableName struct{} `pg:"blog.comment"` Id int `pg:"type:serial"` Content string	`pg:",notnull"` CreatedAt time.Time	`pg:"type:timestamp without time zone,default:now()"` UserId int	`pg:"type:integer,notnull"` User *User `pg:"rel:has-one"` PostId int	`pg:"type:integer,notnull"` Post *Post	`pg:"rel:has-one"`
}

2.2 Tạo các route API

controller/base.go: khai báo DB tại controller

package controller import "github.com/go-pg/pg/v10" var DB *pg.DB

Biến DB trên sẽ sử dụng xuyên suốt ví dụ

router/routes.go: định nghĩa các route.

package router import ( "golangpostgre/controller" "github.com/kataras/iris/v12"
) func AllRoutes(app *iris.Application){ app.Get("/api/user",controller.GetUsers)	//Lấy tất cả user app.Get("/api/user/{userId}",controller.GetUserById)	//Lấy một user app.Post("/api/register",controller.Register)	//Tạo user mới app.Put("/api/user/{id}",controller.UpdateUser)	//Chỉnh sửa user app.Get("/api/posts",controller.GetPosts)	//Lấy tất cả post app.Get("/api/posts/{postId}",controller.GetPostById)	//Lấy một post app.Post("/api/post/create",controller.CreatePost)	//tạo post mới app.Put("/api/post/{id}",controller.UpdatePost)	//chỉnh sửa post app.Delete("/api/post/{id}",controller.DeletePost)	//Xóa post app.Get("/api/comment",controller.GetComments)	//Lấy tất cả comment app.Get("/api/comment/{commentId}",controller.GetCommentById)	//Lấy một comment app.Post("/api/comment/create",controller.CreateComment)	//Tạo comment app.Put("/api/comment/{id}",controller.UpdateComment)	//Chỉnh sửa comment app.Delete("/api/comment/{id}",controller.DeleteComment)	//Xóa comment
}

Các function trong package controller trên sẽ dùng để thực hiện các nghiệp vụ xử lý logic với bảng database.

2.3 Viết code để khởi chạy

main.go:

package main import ( "golangpostgre/config" "golangpostgre/controller" "golangpostgre/router" "github.com/kataras/iris/v12"
) func main() { db := config.ConnectDatabase()	//Kết nối database defer db.Close()	//Đóng database trước kết thúc chương trình app := iris.New()	//Sử dụng framework iris controller.DB = db router.AllRoutes(app) //Đăng ký các route app.Run(iris.Addr(":8080")) //chạy ở cổng 8080 }

2.4 Thực hiện xử lý logic cho từng route.

controller/user.go: xử lý cho từng route liên quan user

 package controller import ( "golangpostgre/model" "github.com/go-pg/pg/v10" "github.com/kataras/iris/v12" "golang.org/x/crypto/bcrypt"
) func GetUsers(ctx iris.Context){	// GET http://localhost:8080/api/user var user []model.User //Select toàn bộ user, .Relation có thể thêm vào để lấy các Post của user err := DB.Model(&user).Relation("Posts").Select() if err != nil{ ctx.StatusCode(iris.StatusInternalServerError) return } ctx.JSON(user)
} func GetUserById(ctx iris.Context){ // GET http://localhost:8080/api/user/{userId} id := ctx.Params().Get("userId")	//Lấy giá trị userId truyền vào var user model.User //Select user với id err := DB.Model(&user).Relation("Posts").Where("id = ?",id).Select() if err != nil{ ctx.StatusCode(iris.StatusInternalServerError) return } ctx.JSON(user)
} func Register(ctx iris.Context){	//POST http://localhost:8080/api/register var data map[string]string ctx.ReadJSON(&data) if data["password"] != data["passwordconfirm"]{ //check password có khớp passwordconfirm không ctx.StatusCode(400) ctx.JSON(map[string]string{ "message":"password doesn't match", }) return } password, _ :=bcrypt.GenerateFromPassword([]byte(data["password"]),14)	//mã hóa password user := model.User{ FirstName: data["first_name"], LastName: data["last_name"], Email:data["email"], Password:string(password), } _,err := DB.Model(&user).Insert()	//Insert vào bảng users if err != nil{ panic(err) } ctx.JSON(user)
} func UpdateUser(ctx iris.Context){	//PUT http://localhost:8080/api/user/{id} id := ctx.Params().Get("id") var data map[string]interface{} ctx.ReadJSON(&data) //Đọc dữ liệu truyền vào qua map string interface, các trường trong map dùng để update bảng user //Update bảng user _,err := DB.Model(&data).TableExpr("auth.users").Where("id = ?",id).Update() if err != nil{ ctx.StatusCode(iris.StatusInternalServerError) return } ctx.StatusCode(iris.StatusOK) ctx.JSON("Cập nhật thành công")
}

Lưu ý: Update, Select, Insert truyền *map[string]interface{} như bên trên chỉ áp dụng cho go-pg v10 trở lên.

controller/blog.go: xử lý cho từng route liên quan post

package controller import ( "golangpostgre/model" "log" "github.com/kataras/iris/v12"
) func GetPosts(ctx iris.Context){	// GET http://localhost:8080/api/posts var posts []model.Post err := DB.Model(&posts).Relation("User").Select() if err != nil{ panic(err) } ctx.JSON(posts)
} func GetPostById(ctx iris.Context){ // GET http://localhost:8080/api/posts/{postId} id := ctx.Params().Get("userId") var post model.Post err := DB.Model(&post).Relation("User").Where("id = ?",id).Select() if err != nil{ ctx.StatusCode(iris.StatusInternalServerError) return } ctx.JSON(post)
} func CreatePost(ctx iris.Context){ // POST http://localhost:8080/api/post/create var data map[string]interface{} ctx.ReadJSON(&data) data["user_id"]=1	//Truyền thêm tham số chưa có khi đọc dữ liệu _,err := DB.Model(&data).TableExpr("blog.post").Insert() if err != nil{ log.Println(err) ctx.StatusCode(500) return } ctx.JSON("Tạo bài viết thành công")
} func UpdatePost(ctx iris.Context){ // PUT http://localhost:8080/api/post/{id} var data map[string]interface{} ctx.ReadJSON(&data) id := ctx.Params().Get("id") data["updated_at"]=time.Now() _,err := DB.Model(&data).TableExpr("blog.post").Where("id = ?",id).Update() if err != nil{ log.Println(err) ctx.StatusCode(iris.StatusInternalServerError) return } ctx.JSON("Cập nhật thành công")
} func DeletePost(ctx iris.Context){ // DELETE http://localhost:8080/api/post/{id} id:= ctx.Params().Get("id") post := new(model.Post)	//tương ứng với = (*model.Post)(nil) //Xóa post với id _,err := DB.Model(post).Where("id = ?", id).Delete() if err != nil{ log.Println(err) ctx.StatusCode(iris.StatusInternalServerError) return } ctx.JSON("Xóa thành công")
}

Lưu ý: Đây chỉ là ví dụ mẫu giúp hiểu bản chất của go-pg, nên ở trên user_id được gán cho một giá trị cố định = 1 qua việc xem thông tin bảng database. Nhiều chỗ cũng tương tự ở bên dưới. Nên thay đổi giá trị phù hợp với bảng database.

controller/comment.go: xử lý cho từng route liên quan comment

package controller import ( "golangpostgre/model" "log" "time" "github.com/kataras/iris/v12"
) func GetComments(ctx iris.Context){ // GET http://localhost:8080/api/comment var comments []model.Comment err := DB.Model(&comments).Relation("User").Relation("Post").Select() if err != nil{ panic(err) } ctx.JSON(comments)
} func GetCommentById(ctx iris.Context){ // GET http://localhost:8080/api/comment/{commentId} id := ctx.Params().Get("userId") var comment model.Comment err := DB.Model(&comment).Relation("Post").Relation("User").Where("id = ?",id).Select() if err != nil{ ctx.StatusCode(iris.StatusInternalServerError) return } ctx.JSON(comment)
} func UpdateComment(ctx iris.Context){	// PUT http://localhost:8080/api/comment/{id} var data map[string]interface{} ctx.ReadJSON(&data) id:= ctx.Params().Get("id") _,err := DB.Model(&data).TableExpr("blog.comment").Where("id = ?",id).Update() if err != nil{ log.Println(err) ctx.StatusCode(iris.StatusInternalServerError) return } ctx.JSON("Update thành công")
} func DeleteComment(ctx iris.Context){	// DELETE http://localhost:8080/api/comment/{id} id:= ctx.Params().Get("id") _,err := DB.Model((*model.Comment)(nil)).Where("id = ?",id).Delete() if err != nil{ log.Println(err) ctx.StatusCode(iris.StatusInternalServerError) return } ctx.JSON("Xóa thành công")
} func CreateComment(ctx iris.Context){	// POST http://localhost:8080/api/comment/create var data map[string]interface{} ctx.ReadJSON(&data) data["user_id"]=1 data["post_id"]=1 _,err := DB.Model(&data).TableExpr("blog.comment").Insert() if err != nil{ log.Println(err) ctx.StatusCode(iris.StatusInternalServerError) return } ctx.JSON("Comment thành công")
}

3. Chạy code

Khi bắt đầu chạy, chương trình sẽ tạo ra các bảng database (nếu chưa có) với các trường, constraint, quan hệ ứng với các model được định nghĩa. Tuy nhiên các bảng được định nghĩa trong model có tên sau: auth.users, blog.post, blog.comment

auth, blog chính là các schema, nên nếu trong database bạn chưa có hay thêm các schema này như sau: vào Servers → PostgreSQL 13 →Databases → postgres → click chuột phải schemas → Create → Schemas → Nhập tên schema rồi bấm lưu vào hộp thoại như bên dưới đây

Giờ chạy chương trình và ta đã có bảng với các trường tương ứng. Ví dụ auth.users:

3.1 Chạy API POST, PUT, DELETE

Kết quả trên database:

Lỗi như sau:

Do chúng ta đã buộc title, content khác null, nên ta phải bổ sung thêm content.

Kết quả bảng database:

Bạn có thể tự tạo thêm bản ghi bằng việc gọi lại API trên truyền dữ liệu title, content khác.

Kết quả bảng database:

Kết quả database:

Kết quả database

3.2 Chạy các API GET

Kết quả trả ra:

[ { "Id": 1, "FirstName": "Nguyễn Trần", "LastName": "Nhật Đức", "Email": "nhatduc@techmaster.vn", "Password": "$2a$14$HpP97XwOOVobmYsH7w6Ak.IEz5p0lAwanC/THXI.WFZy2exi2zA8K", "Posts": null }
]

Trường hợp có relation: err := DB.Model(&user).Relation("Posts").Select()

Kết quả trả ra:

[ { "Id": 1, "FirstName": "Nguyễn Trần", "LastName": "Nhật Đức", "Email": "nhatduc@techmaster.vn", "Password": "$2a$14$HpP97XwOOVobmYsH7w6Ak.IEz5p0lAwanC/THXI.WFZy2exi2zA8K", "Posts": [ { "Id": 2, "Content": "Content được chỉnh sửa .....", "Title": "Sử dụng logrus thay thế logging mặc định golang", "CreatedAt": "2021-07-20T17:03:14.513294Z", "UpdatedAt": "2021-07-20T10:17:13.82935Z", "UserId": 1, "User": null } ] }
]
[ { "Id": 2, "Content": "Content được chỉnh sửa .....", "Title": "Sử dụng logrus thay thế logging mặc định golang", "CreatedAt": "2021-07-20T17:03:14.513294Z", "UpdatedAt": "2021-07-20T10:17:13.82935Z", "UserId": 1, "User": { "Id": 1, "FirstName": "Nguyễn Trần", "LastName": "Nhật Đức", "Email": "nhatduc@techmaster.vn", "Password": "$2a$14$HpP97XwOOVobmYsH7w6Ak.IEz5p0lAwanC/THXI.WFZy2exi2zA8K", "Posts": null } }
]
[ { "Id": 2, "Content": "thôi chả thấy hay nữa", "CreatedAt": "2021-07-20T17:33:23.811122Z", "UserId": 1, "User": { "Id": 1, "FirstName": "Nguyễn Trần", "LastName": "Nhật Đức", "Email": "nhatduc@techmaster.vn", "Password": "$2a$14$HpP97XwOOVobmYsH7w6Ak.IEz5p0lAwanC/THXI.WFZy2exi2zA8K", "Posts": null }, "PostId": 2, "Post": { "Id": 2, "Content": "Content được chỉnh sửa .....", "Title": "Sử dụng logrus thay thế logging mặc định golang", "CreatedAt": "2021-07-20T17:03:14.513294Z", "UpdatedAt": "2021-07-20T10:17:13.82935Z", "UserId": 1, "User": null } }
]

4. Kết

Phần này chủ yếu tập trung vào SELECT, UPDATE, DELETE và quan hệ một - nhiều, định nghĩa model. Các bạn hãy tự gõ lại code và chạy thử để nắm rõ hơn. Mong rằng với ví dụ trên sẽ giúp các bạn hiểu được căn bản về golang kết nối postgresql.

Phần sau ta sẽ ví dụ tiếp về quan hệ nhiều nhiều, bố sung thêm định nghĩa model và join các bảng với nhau.

Bình luận

Bài viết tương tự

- vừa được xem lúc

gRPC - Nó là gì và có nên sử dụng hay không?

Nhân một ngày rảnh rỗi, mình ngồi đọc lại RPC cũng như gRPC viết lại để nhớ lâu hơn. Vấn đề là gì và tại sao cần nó .

0 0 131

- vừa được xem lúc

Embedded Template in Go

Getting Start. Part of developing a web application usually revolves around working with HTML as user interface.

0 0 56

- vừa được xem lúc

Tạo Resful API đơn giản với Echo framework và MySQL

1. Giới thiệu.

0 0 60

- vừa được xem lúc

Sử dụng goquery trong golang để crawler thông tin các website Việt Nam bị deface trên mirror-h.org

. Trong bài viết này, mình sẽ cùng mọi người khám phá một package thu thập dữ liệu có tên là goquery của golang. Mục tiêu chính của chương trình crawler này sẽ là lấy thông tin các website Việt Nam bị deface (là tấn công, phá hoại website, làm thay đổi giao diện hiển thị của một trang web, khi người

0 0 237

- vừa được xem lúc

Tạo ứng dụng craw dữ liệu bing với Golang, Mysql driver

Chào mọi người . Lâu lâu ta lại gặp nhau 1 lần, để tiếp tục series chia sẻ kiến thức về tech, hôm nay mình sẽ tìm hiểu và chia sẻ về 1 ngôn ngữ đang khá hot trong cộng đồng IT đó là Golang.

0 0 75

- vừa được xem lúc

Golang: Rest api and routing using MUX

Routing with MUX. Let's create a simple CRUD api for a blog site. # All . GET articles/ .

0 0 54