Hướng dẫn: Phát triển API RESTful với Go và Gin
This tutorial giới thiệu những kiến thức cơ bản về cách viết API dịch vụ web RESTful bằng Go và Gin Web Framework (Gin).
Bạn sẽ khai thác tối đa hướng dẫn này nếu có kiến thức cơ bản về Go và hệ thống công cụ của Go. Nếu đây là lần đầu tiên bạn làm quen với Go, hãy xem Hướng dẫn: Bắt đầu với Go để có phần giới thiệu nhanh.
Gin đơn giản hóa nhiều tác vụ lập trình liên quan đến việc xây dựng ứng dụng web, bao gồm cả dịch vụ web. Trong hướng dẫn này, bạn sẽ sử dụng Gin để định tuyến yêu cầu, lấy thông tin yêu cầu và marshal JSON cho các phản hồi.
Trong hướng dẫn này, bạn sẽ xây dựng một máy chủ API RESTful với hai endpoint. Dự án ví dụ của bạn sẽ là một kho lưu trữ dữ liệu về các bản thu âm jazz cổ điển.
Hướng dẫn này bao gồm các phần sau:
- Thiết kế các endpoint API.
- Tạo một thư mục cho mã của bạn.
- Tạo dữ liệu.
- Viết một handler để trả về tất cả mục.
- Viết một handler để thêm một mục mới.
- Viết một handler để trả về một mục cụ thể.
Lưu ý: Để xem các hướng dẫn khác, hãy xem Hướng dẫn.
Để thử hướng dẫn tương tác này mà bạn hoàn thành trong Google Cloud Shell, hãy nhấp vào nút bên dưới.
Điều kiện tiên quyết
- Go. Chúng tôi khuyên bạn nên sử dụng phiên bản Go mới nhất để làm theo hướng dẫn này. Để biết hướng dẫn cài đặt, hãy xem Cài đặt Go.
- Một công cụ để chỉnh sửa mã của bạn. Bất kỳ trình soạn thảo văn bản nào bạn có đều hoạt động tốt.
- Một terminal lệnh. Go hoạt động tốt khi sử dụng bất kỳ terminal nào trên Linux và Mac, cũng như PowerShell hoặc cmd trên Windows.
- Công cụ curl. Trên Linux và Mac, công cụ này thường đã được cài đặt. Trên Windows, công cụ này được tích hợp trong Windows 10 Insider build 17063 trở lên. Với các phiên bản Windows cũ hơn, bạn có thể cần cài đặt nó. Để biết thêm, hãy xem Tar và Curl đến với Windows.
Thiết kế các endpoint API
Bạn sẽ xây dựng một API cung cấp quyền truy cập vào một cửa hàng bán các bản thu âm cổ điển trên đĩa vinyl. Vì vậy, bạn sẽ cần cung cấp các endpoint để client có thể lấy và thêm album cho người dùng.
Khi phát triển một API, thông thường bạn bắt đầu bằng việc thiết kế các endpoint. Người dùng API của bạn sẽ thành công hơn nếu các endpoint dễ hiểu.
Dưới đây là các endpoint bạn sẽ tạo trong hướng dẫn này.
/albums
GET– Lấy danh sách tất cả album, được trả về dưới dạng JSON.POST– Thêm một album mới từ dữ liệu yêu cầu được gửi dưới dạng JSON.
/albums/:id
GET– Lấy một album theo ID của nó, trả về dữ liệu album dưới dạng JSON.
Tiếp theo, bạn sẽ tạo một thư mục cho mã của mình.
Tạo thư mục cho mã của bạn
Để bắt đầu, hãy tạo một dự án cho mã bạn sẽ viết.
- Mở dấu nhắc lệnh và chuyển đến thư mục chính của bạn.
Trên Linux hoặc Mac:
```
$ cd
```
Trên Windows:
```
C:\> cd %HOMEPATH%
```
- Sử dụng dấu nhắc lệnh, tạo một thư mục cho mã của bạn có tên
web-service-gin.
```
$ mkdir web-service-gin
$ cd web-service-gin
```
- Tạo một module để bạn có thể quản lý các dependency.
Chạy lệnh go mod init, cung cấp cho lệnh này đường dẫn của module mà mã
của bạn sẽ nằm trong đó.
```
$ go mod init example/web-service-gin
go: creating new go.mod: module example/web-service-gin
```
Lệnh này tạo một tệp go.mod trong đó các dependency bạn thêm vào sẽ được
liệt kê để theo dõi. Để biết thêm về cách đặt tên một module bằng đường dẫn
module, hãy xem Quản lý dependency.
Tiếp theo, bạn sẽ thiết kế các cấu trúc dữ liệu để xử lý dữ liệu.
Tạo dữ liệu
Để giữ mọi thứ đơn giản cho hướng dẫn này, bạn sẽ lưu trữ dữ liệu trong bộ nhớ. Một
API thông thường hơn sẽ tương tác với cơ sở dữ liệu.
Lưu ý rằng việc lưu trữ dữ liệu trong bộ nhớ có nghĩa là tập hợp album sẽ bị mất mỗi
khi bạn dừng máy chủ, sau đó được tạo lại khi bạn khởi động nó.
Viết mã
- Sử dụng trình soạn thảo văn bản, tạo một tệp có tên main.go trong thư mục
web-service. Bạn sẽ viết mã Go của mình trong tệp này. 2. Trong main.go, ở đầu tệp, dán khai báo package sau.
```
package main
```
Một chương trình độc lập (trái với một thư viện) luôn nằm trong package main.
3. Bên dưới khai báo package, dán khai báo sau của struct album. Bạn sẽ sử dụng
struct này để lưu dữ liệu album trong bộ nhớ.
Các thẻ struct như json:"artist" chỉ định tên của một trường nên là gì
khi nội dung của struct được tuần tự hóa thành JSON. Nếu không có chúng, JSON
sẽ sử dụng tên trường được viết hoa của struct – một kiểu không phổ biến bằng
trong JSON.
```
// album đại diện cho dữ liệu về một album thu âm.
type album struct {
ID string json:"id"
Title string json:"title"
Artist string json:"artist"
Price float64 json:"price"
}
```
- Bên dưới khai báo struct bạn vừa thêm, dán slice các struct
albumsau chứa
dữ liệu bạn sẽ dùng để khởi tạo.
```
// slice albums để khởi tạo dữ liệu album thu âm. var albums = []album{ {ID: “1”, Title: “Blue Train”, Artist: “John Coltrane”, Price: 56.99}, {ID: “2”, Title: “Jeru”, Artist: “Gerry Mulligan”, Price: 17.99}, {ID: “3”, Title: “Sarah Vaughan and Clifford Brown”, Artist: “Sarah Vaughan”, Price: 39.99}, } ```
Tiếp theo, bạn sẽ viết mã để triển khai endpoint đầu tiên của mình.
Viết một trình xử lý để trả về tất cả mục
Khi client gửi một yêu cầu tới GET /albums, bạn muốn trả về tất cả album dưới dạng JSON.
Để thực hiện việc này, bạn sẽ viết như sau:
- Logic để chuẩn bị phản hồi
- Mã để ánh xạ đường dẫn yêu cầu tới logic của bạn
Lưu ý rằng đây là thứ tự ngược lại với cách chúng sẽ được thực thi khi chạy, nhưng bạn đang thêm các dependency trước, sau đó là mã phụ thuộc vào chúng.
Viết mã
-
Bên dưới mã struct bạn đã thêm trong phần trước, dán đoạn mã sau để lấy danh sách album.
Hàm
getAlbumsnày tạo JSON từ slice các structalbum, ghi JSON vào phản hồi.// getAlbums phản hồi bằng danh sách tất cả album dưới dạng JSON. func getAlbums(c *gin.Context) { c.IndentedJSON(http.StatusOK, albums) }Trong đoạn mã này, bạn:
-
Viết một hàm
getAlbumsnhận tham sốgin.Context. Lưu ý rằng bạn có thể đặt cho hàm này bất kỳ tên nào – cả Gin lẫn Go đều không yêu cầu một định dạng tên hàm cụ thể.gin.Contextlà phần quan trọng nhất của Gin. Nó chứa thông tin yêu cầu, xác thực và tuần tự hóa JSON, cùng nhiều chức năng khác. (Mặc dù có tên tương tự, nó khác với góicontexttích hợp sẵn của Go.) -
Gọi
Context.IndentedJSONđể tuần tự hóa struct thành JSON và thêm nó vào phản hồi.Đối số đầu tiên của hàm là mã trạng thái HTTP bạn muốn gửi tới client. Ở đây, bạn truyền hằng số
StatusOKtừ góinet/httpđể biểu thị200 OK.Lưu ý rằng bạn có thể thay thế
Context.IndentedJSONbằng lời gọi tớiContext.JSONđể gửi JSON nhỏ gọn hơn. Trong thực tế, dạng có thụt lề dễ làm việc hơn nhiều khi gỡ lỗi và chênh lệch kích thước thường nhỏ.
-
-
Gần đầu
main.go, ngay bên dưới khai báo slicealbums, dán đoạn mã dưới đây để gán hàm xử lý vào một đường dẫn endpoint.Việc này thiết lập một liên kết trong đó
getAlbumsxử lý các yêu cầu tới đường dẫn endpoint/albums.func main() { router := gin.Default() router.GET("/albums", getAlbums) router.Run("localhost:8080") }Trong đoạn mã này, bạn:
-
Khởi tạo một router Gin bằng cách sử dụng
Default. -
Sử dụng hàm
GETđể liên kết phương thức HTTPGETvà đường dẫn/albumsvới một hàm xử lý.Lưu ý rằng bạn đang truyền tên của hàm
getAlbums. Điều này khác với việc truyền kết quả của hàm, việc bạn sẽ làm bằng cách truyềngetAlbums()(lưu ý dấu ngoặc đơn). -
Sử dụng hàm
Runđể gắn router vào mộthttp.Servervà khởi động server.
-
-
Gần đầu
main.go, ngay bên dưới khai báo package, import các package bạn cần để hỗ trợ đoạn mã vừa viết.Các dòng mã đầu tiên sẽ trông như sau:
package main import ( "net/http" "github.com/gin-gonic/gin" ) -
Lưu
main.go.
Chạy mã
-
Bắt đầu theo dõi mô-đun Gin dưới dạng một dependency.
Tại dòng lệnh, sử dụng
go getđể thêm mô-đun github.com/gin-gonic/gin làm dependency cho mô-đun của bạn. Sử dụng đối số dấu chấm để biểu thị “lấy dependency cho mã trong thư mục hiện tại.”$ go get . go get: added github.com/gin-gonic/gin v1.7.2Go đã phân giải và tải xuống dependency này để đáp ứng khai báo
importmà bạn đã thêm ở bước trước. -
Từ dòng lệnh trong thư mục chứa main.go, chạy mã. Sử dụng đối số dấu chấm để biểu thị “chạy mã trong thư mục hiện tại.”
$ go run .Khi mã đang chạy, bạn có một máy chủ HTTP đang hoạt động để có thể gửi yêu cầu đến.
-
Từ một cửa sổ dòng lệnh mới, sử dụng
curlđể tạo một yêu cầu đến dịch vụ web đang chạy của bạn.$ curl http://localhost:8080/albumsLệnh này sẽ hiển thị dữ liệu mà bạn đã nạp sẵn vào dịch vụ.
[ { "id": "1", "title": "Blue Train", "artist": "John Coltrane", "price": 56.99 }, { "id": "2", "title": "Jeru", "artist": "Gerry Mulligan", "price": 17.99 }, { "id": "3", "title": "Sarah Vaughan and Clifford Brown", "artist": "Sarah Vaughan", "price": 39.99 } ]
Bạn đã tạo một API! Trong phần tiếp theo, bạn sẽ tạo một endpoint khác với
mã để xử lý yêu cầu POST nhằm thêm một mục.
Viết handler để thêm một mục mới
Khi client thực hiện yêu cầu POST tại /albums, bạn muốn thêm album
được mô tả trong phần thân yêu cầu vào dữ liệu album hiện có.
Để thực hiện việc này, bạn sẽ viết:
- Logic để thêm album mới vào danh sách hiện có.
- Một đoạn mã để định tuyến yêu cầu
POSTđến logic của bạn.
Viết mã
-
Thêm mã để thêm dữ liệu album vào danh sách album.
Ở đâu đó sau các câu lệnh
import, hãy dán đoạn mã sau. (Cuối tệp là một vị trí phù hợp cho đoạn mã này, nhưng Go không bắt buộc thứ tự mà bạn khai báo các hàm.)// postAlbums thêm một album từ JSON nhận được trong phần thân yêu cầu. func postAlbums(c *gin.Context) { var newAlbum album // Gọi BindJSON để liên kết JSON đã nhận với // newAlbum. if err := c.BindJSON(&newAlbum); err != nil { return } // Thêm album mới vào slice. albums = append(albums, newAlbum) c.IndentedJSON(http.StatusCreated, newAlbum) }Trong đoạn mã này, bạn:
- Sử dụng
Context.BindJSONđể liên kết phần thân yêu cầu vớinewAlbum. - Nối cấu trúc
albumđược khởi tạo từ JSON vào slicealbums. - Thêm mã trạng thái
201vào phản hồi, cùng với JSON biểu diễn album bạn đã thêm.
- Sử dụng
-
Thay đổi hàm
maincủa bạn để bao gồm hàmrouter.POST, như sau.func main() { router := gin.Default() router.GET("/albums", getAlbums) router.POST("/albums", postAlbums) router.Run("localhost:8080") }Trong đoạn mã này, bạn:
-
Liên kết phương thức
POSTtại đường dẫn/albumsvới hàmpostAlbums.Với Gin, bạn có thể liên kết một trình xử lý với tổ hợp phương thức HTTP và đường dẫn. Bằng cách này, bạn có thể định tuyến riêng các yêu cầu gửi đến một đường dẫn duy nhất dựa trên phương thức mà máy khách đang sử dụng.
-
Chạy mã
-
Nếu máy chủ vẫn đang chạy từ phần trước, hãy dừng nó.
-
Từ dòng lệnh trong thư mục chứa main.go, chạy mã.
$ go run . -
Từ một cửa sổ dòng lệnh khác, sử dụng
curlđể tạo yêu cầu đến dịch vụ web đang chạy của bạn.$ curl http://localhost:8080/albums \ --include \ --header "Content-Type: application/json" \ --request "POST" \ --data '{"id": "4","title": "The Modern Sound of Betty Carter","artist": "Betty Carter","price": 49.99}'Lệnh này sẽ hiển thị các header và JSON cho album đã thêm.
HTTP/1.1 201 Created Content-Type: application/json; charset=utf-8 Date: Wed, 02 Jun 2021 00:34:12 GMT Content-Length: 116 { "id": "4", "title": "The Modern Sound of Betty Carter", "artist": "Betty Carter", "price": 49.99 } -
Giống như phần trước, hãy sử dụng
curlđể lấy toàn bộ danh sách album, bạn có thể dùng danh sách này để xác nhận rằng album mới đã được thêm.$ curl http://localhost:8080/albums \ --header "Content-Type: application/json" \ --request "GET"Lệnh này sẽ hiển thị danh sách album.
[ { "id": "1", "title": "Blue Train", "artist": "John Coltrane", "price": 56.99 }, { "id": "2", "title": "Jeru", "artist": "Gerry Mulligan", "price": 17.99 }, { "id": "3", "title": "Sarah Vaughan and Clifford Brown", "artist": "Sarah Vaughan", "price": 39.99 }, { "id": "4", "title": "The Modern Sound of Betty Carter", "artist": "Betty Carter", "price": 49.99 } ]
Trong phần tiếp theo, bạn sẽ thêm mã để xử lý GET cho một mục cụ thể.
Viết một handler để trả về một mục cụ thể
Khi client thực hiện yêu cầu đến GET /albums/[id], bạn muốn trả về album có ID khớp với tham số đường dẫn id.
Để thực hiện việc này, bạn sẽ:
- Thêm logic để truy xuất album được yêu cầu.
- Ánh xạ đường dẫn tới logic.
Viết mã
-
Bên dưới hàm
postAlbumsmà bạn đã thêm trong phần trước, dán đoạn mã sau để truy xuất một album cụ thể.Hàm
getAlbumByIDnày sẽ trích xuất ID trong đường dẫn yêu cầu, sau đó tìm một album phù hợp.// getAlbumByID tìm album có giá trị ID khớp với tham số id // được client gửi, sau đó trả về album đó dưới dạng phản hồi. func getAlbumByID(c *gin.Context) { id := c.Param("id") // Duyệt qua danh sách các album để tìm // album có giá trị ID khớp với tham số. for _, a := range albums { if a.ID == id { c.IndentedJSON(http.StatusOK, a) return } } c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"}) }Trong đoạn mã này, bạn:
-
Sử dụng
Context.Paramđể truy xuất tham số đường dẫnidtừ URL. Khi bạn ánh xạ handler này tới một đường dẫn, bạn sẽ bao gồm một phần giữ chỗ cho tham số trong đường dẫn. -
Duyệt qua các struct
albumtrong slice, tìm struct có giá trị trườngIDkhớp với giá trị tham sốid. Nếu tìm thấy, bạn tuần tự hóa structalbumđó thành JSON và trả về dưới dạng phản hồi với mã HTTP200 OK.Như đã đề cập ở trên, một service thực tế có khả năng sẽ sử dụng truy vấn cơ sở dữ liệu để thực hiện việc tra cứu này.
-
Trả về lỗi HTTP
404vớihttp.StatusNotFoundnếu không tìm thấy album.
-
-
Cuối cùng, thay đổi
mainđể bao gồm một lệnh gọi mới tớirouter.GET, trong đó đường dẫn hiện là/albums/:id, như trong ví dụ sau.func main() { router := gin.Default() router.GET("/albums", getAlbums) router.GET("/albums/:id", getAlbumByID) router.POST("/albums", postAlbums) router.Run("localhost:8080") }Trong đoạn mã này, bạn:
- Liên kết đường dẫn
/albums/:idvới hàmgetAlbumByID. Trong Gin, dấu hai chấm đứng trước một mục trong đường dẫn biểu thị rằng mục đó là một tham số đường dẫn.
- Liên kết đường dẫn
Chạy mã
-
Nếu máy chủ vẫn đang chạy từ phần trước, hãy dừng nó.
-
Từ dòng lệnh trong thư mục chứa main.go, chạy mã để khởi động máy chủ.
$ go run . -
Từ một cửa sổ dòng lệnh khác, sử dụng
curlđể gửi yêu cầu đến dịch vụ web đang chạy của bạn.$ curl http://localhost:8080/albums/2
Lệnh này sẽ hiển thị JSON cho album có ID mà bạn đã sử dụng. Nếu không tìm thấy album, bạn sẽ nhận được JSON có thông báo lỗi.
```
{
"id": "2",
"title": "Jeru",
"artist": "Gerry Mulligan",
"price": 17.99
}
```
Kết luận
Chúc mừng! Bạn vừa sử dụng Go và Gin để viết một dịch vụ web RESTful đơn giản.
Các chủ đề tiếp theo được đề xuất:
- Nếu bạn mới làm quen với Go, bạn sẽ tìm thấy các phương pháp hay hữu ích được mô tả trong Effective Go và How to write Go code.
- Go Tour là một bước giới thiệu tuyệt vời theo từng bước về các kiến thức cơ bản của Go.
- Để biết thêm về Gin, hãy xem tài liệu gói Gin Web Framework hoặc tài liệu Gin Web Framework.
Mã hoàn chỉnh
Phần này chứa mã của ứng dụng mà bạn xây dựng bằng hướng dẫn này.
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
// album represents data about a record album.
type album struct {
ID string `json:"id"`
Title string `json:"title"`
Artist string `json:"artist"`
Price float64 `json:"price"`
}
// albums slice to seed record album data.
var albums = []album{
{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
{ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
{ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.GET("/albums/:id", getAlbumByID)
router.POST("/albums", postAlbums)
router.Run("localhost:8080")
}
// getAlbums responds with the list of all albums as JSON.
func getAlbums(c *gin.Context) {
c.IndentedJSON(http.StatusOK, albums)
}
// postAlbums adds an album from JSON received in the request body.
func postAlbums(c *gin.Context) {
var newAlbum album
// Call BindJSON to bind the received JSON to
// newAlbum.
if err := c.BindJSON(&newAlbum); err != nil {
return
}
// Add the new album to the slice.
albums = append(albums, newAlbum)
c.IndentedJSON(http.StatusCreated, newAlbum)
}
// getAlbumByID locates the album whose ID value matches the id
// parameter sent by the client, then returns that album as a response.
func getAlbumByID(c *gin.Context) {
id := c.Param("id")
// Loop through the list of albums, looking for
// an album whose ID value matches the parameter.
for _, a := range albums {
if a.ID == id {
c.IndentedJSON(http.StatusOK, a)
return
}
}
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
}
