Hôm nay mình sẽ xây dựng một RESTful API đơn giản cho ứng dụng todo với ngôn ngữ Golang. Mình sẽ sử dụng framework đơn giản và nhanh nhất của Go là gin-gonic và một ORM gorm cho cơ sở dữ liệu. Để cài đặt những packages này, chuyển đến workspace $GOPATH/src và chạy các lệnh dưới đây:
$ go get gopkg.in/gin-gonic/gin.v1 $ go get -u github.com/jinzhu/gorm $ go get github.com/go-sql-driver/mysql
Trong một ứng dụng CRUD, chúng ta cần những API như bên dưới:
- POST todos/
- GET todos/
- GET todos/{id}
- PUT todos/{id}
- DELETE todos/{id}
Bắt đầu vào code nào, đến đường dẫn $GOPATH/src và tạo một thư mục todo. Bên trong thư mục todo, tạo một tập tin main.go. Import thư viện gin-gonic vào dự án và tạo routes như bên dưới trong hàm main. Mình muốn thên một prefix cho API như là “api/v1” để phục vụ cho API versioning, ở đây chúng ta sẽ sử dụng phương thức router Group.
package main import ( "github.com/gin-gonic/gin" ) func main() {router := gin.Default()v1 := router.Group("/api/v1/todos") { v1.POST("/", createTodo) v1.GET("/", fetchAllTodo) v1.GET("/:id", fetchSingleTodo) v1.PUT("/:id", updateTodo) v1.DELETE("/:id", deleteTodo) } router.Run()}
Chúng ta đã tạo ra 5 routes với các hàm như createTodo, fetchAllTodo… sẽ quay lại với chúng nó sớm thôi.
Bây giờ chúng ta cần cấu hình kết nối tới cơ sở dữ liệu.
Để sử dụng cơ sở dữ liệu, chúng ta cần package gorm và mysql. Tham khảo code bên dưới:
package main import ( "github.com/gin-gonic/gin" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" ) var db *gorm.DBfunc init() { //open a db connection var err error db, err = gorm.Open("mysql", "root:12345@/demo?charset=utf8&parseTime=True&loc=Local") if err != nil { panic("failed to connect database") } //Migrate the schema db.AutoMigrate(&todoModel{}) }
Trong đoạn code ở trên, “mysql” là driver của cơ sở dữ liệu, “root” là username, “12345” là password và “demo” là tên cơ sở dữ liệu. Cập nhật những thông tin này theo tùy chỉnh của bạn.
Tiếp theo hãy tạo một todoModel và transformedTodo struct. Struct đầu tiên sẽ đại diện cho Todo và struct thứ 2 sẽ giữ bản chuyển đổi của todo cho response từ API. Chúng ta cần phải chuyển đổi todo response bởi vì chúng ta không trả về những trường như (updated_at, created_at) về cho frontend
type ( // todoModel describes a todoModel type todoModel struct { gorm.Model Title string `json:"title"` Completed int `json:"completed"` } // transformedTodo represents a formatted todo transformedTodo struct { ID uint `json:"id"` Title string `json:"title"` Completed bool `json:"completed"` } )
Todo struct có một trường gorm.Model. Cái này nghĩa là gì? ya, trường này sẽ nhúng một Model struct cho chúng ta, chúng chứa 4 trường có sẵn đó là “ID, CreatedAt, UpdatedAt, DeletedAt”
Gorm có migration, chúng ta đã sử dụng trong hàm init. Khi chúng ta chạy ứng dụng lần đầu, nó sẽ tạo một kết nối đến cơ sở dữ liệu và chạy migration.
//Migrate the schema db.AutoMigrate(&todoModel{})
![](https://huynhphuchuy.com/wp-content/uploads/2019/12/todo-db-1024x282.png)
Các bạn còn nhớ 5 routes đầu tiên mà chúng ta tạo ra cho RESTful API không? hãy cùng hoàn chỉnh nó nhé.
Khi một người dùng gửi một POST request đến đường dẫn ‘api/v1/todos’, nó sẽ được xử lý bởi route này v1.POST(“/”, createTodo)
Hoàn chỉnh hàm createTodo:
// createTodo add a new todo func createTodo(c *gin.Context) { completed, _ := strconv.Atoi(c.PostForm("completed")) todo := todoModel{Title: c.PostForm("title"), Completed: completed} db.Save(&todo) c.JSON(http.StatusCreated, gin.H{"status": http.StatusCreated, "message": "Todo item created successfully!", "resourceId": todo.ID}) }
Trong đoạn code trên chúng ta sử dụng gin Context để nhận dữ liệu gửi lên và gorm sẽ đảm nhận việc lưu dữ liệu vào cơ sở dữ liệu. Sau khi lưu trữ dữ liệu thành công, chúng ta trả về id và một response có nghĩa cho người dùng.
Hãy hoàn chỉnh những hàm còn lại:
// fetchAllTodo fetch all todos func fetchAllTodo(c *gin.Context) { var todos []todoModel var _todos []transformedTododb.Find(&todos)if len(todos) <= 0 { c.JSON(http.StatusNotFound, gin.H{"status": http.StatusNotFound, "message": "No todo found!"}) return } //transforms the todos for building a good response for _, item := range todos { completed := false if item.Completed == 1 { completed = true } else { completed = false } _todos = append(_todos, transformedTodo{ID: item.ID, Title: item.Title, Completed: completed}) } c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "data": _todos}) } // fetchSingleTodo fetch a single todo func fetchSingleTodo(c *gin.Context) { var todo todoModel todoID := c.Param("id")db.First(&todo, todoID)if todo.ID == 0 { c.JSON(http.StatusNotFound, gin.H{"status": http.StatusNotFound, "message": "No todo found!"}) return } completed := false if todo.Completed == 1 { completed = true } else { completed = false } _todo := transformedTodo{ID: todo.ID, Title: todo.Title, Completed: completed} c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "data": _todo}) } // updateTodo update a todo func updateTodo(c *gin.Context) { var todo todoModel todoID := c.Param("id")db.First(&todo, todoID)if todo.ID == 0 { c.JSON(http.StatusNotFound, gin.H{"status": http.StatusNotFound, "message": "No todo found!"}) return } db.Model(&todo).Update("title", c.PostForm("title")) completed, _ := strconv.Atoi(c.PostForm("completed")) db.Model(&todo).Update("completed", completed) c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "message": "Todo updated successfully!"}) } // deleteTodo remove a todo func deleteTodo(c *gin.Context) { var todo todoModel todoID := c.Param("id")db.First(&todo, todoID)if todo.ID == 0 { c.JSON(http.StatusNotFound, gin.H{"status": http.StatusNotFound, "message": "No todo found!"}) return } db.Delete(&todo) c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "message": "Todo deleted successfully!"}) }
Trong hàm fetchAllTodo, chúng ta lấy về tất cả todos và xây dựng response đã được chuyển đổi với id, title và completed. Chúng ta sẽ bỏ qua những trường CreatedAt, UpdatedAt, DeletedAt và chuyển đổi số nguyên thành bool.
Và bây giờ chúng ta đã gần hoàn chỉnh một RESTful API server với Golang và gin-gonic framework rồi. Mình sẽ test API sử dụng Postman.
Để build ứng dụng golang, các bạn mở terminal lên, đi đến đường dẫn dự án và gõ
$ go build main.go
Câu lệnh này sẽ build một tập tin binary main, để chạy tập tin này chỉ đơn giản là gõ $ ./main. Bây giờ ứng dụng đã chạy được trên cổng 8080.
Nó sẽ hiện thông tin debug trên terminal bởi vì mặc định gin-gonic sẽ chạy trên chế độ debug và cổng 8080.
Test RESTful API sử dụng postman
![](https://huynhphuchuy.com/wp-content/uploads/2019/12/todo-create-1024x254.png)
![](https://huynhphuchuy.com/wp-content/uploads/2019/12/todo-get-all-1024x291.png)
![](https://huynhphuchuy.com/wp-content/uploads/2019/12/todo-get-1024x240.png)
![](https://huynhphuchuy.com/wp-content/uploads/2019/12/todo-update-1024x270.png)
![](https://huynhphuchuy.com/wp-content/uploads/2019/12/todo-delete-1024x222.png)
Khi đưa API lên production cần chú ý những bước sau
- Không lấy về tất cả dữ liệu
select * from todos
, sử dụng phân trang - Không tin vào dữ liệu đưa vào của người dùng. Các bạn phải validate tất cả input
- Kiểm tra và xử lý tất cả các lỗi có thể xảy ra trong golang
- Nên sử dụng logging và authentication cho gin-gonic
Các bạn có thắc mắc gì nữa thì vui lòng để lại bình luận bên dưới nhé. Mình sẽ giải đáp tận tình ^^