Xây dựng RESTful API server với Go và gin-gonic framework

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:

  1. POST todos/
  2. GET todos/
  3. GET todos/{id}
  4. PUT todos/{id}
  5. 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 gormmysql. 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 todoModeltransformedTodo 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{})

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

Tạo một todo
Lấy tất cả các todos
Lấy một todo
Cập nhật một todo
Xóa một todo

Khi đưa API lên production cần chú ý những bước sau

  1. Không lấy về tất cả dữ liệu select * from todos , sử dụng phân trang
  2. 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
  3. Kiểm tra và xử lý tất cả các lỗi có thể xảy ra trong golang
  4. 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 ^^

Write a comment