項目實戰:使用 Fiber + Gorm 構建 Rest API
大家好,我是程序員幽鬼。
Fiber 作為一個新的 Go 框架,似乎受追捧程度很高,Star 數飆升很快。不知道這是不是表明,不少 JS/Node 愛好者開始嘗試學習 Go 了,對 Go 是好事。
今天這篇文章介紹如何使用 Fiber + Gorm 構建 REST API。
1 概覽
在這篇文章中,我們將使用 Fiber[1] 框架,它使用起來非常簡單,有一個很好的抽象層,并且包含我們創建 API 所需的一切。
關于與數據庫的交互,我決定使用 ORM 來使整個過程更簡單、更直觀,因此我決定使用Gorm[2],在我看來,Gorm[3] 是 Go 世界中最受歡迎的 ORM,并且特性很多。
2 準備工作
本文基于 Go1.17.5。
在本地創建一個目錄 fibergorm,然后進入該目錄執行如下命令:
- $ go mod init github.com/programmerug/fibergorm
- go: creating new go.mod: module github.com/programmerug/fibergorm
接著執行如下命令,安裝我們需要的依賴:(這個先不執行,之后通過 go mod tidy 安裝)
- go get github.com/gofiber/fiber/v2
- go get gorm.io/gorm
為了方便,本教程中,我們使用 SQLite,因為使用了 Gorm,所以哪種關系型數據庫對核心代碼沒有什么影響。
3 開始編碼
本文以人類的朋友——狗為例。
先從定義實體開始,它總共有四個屬性:
- Name - 狗的名字
- Age - 狗的年齡
- Breed - 狗的種族(類型)
- IsGoodBoy - 狗是否是個好孩子
類似以下內容:
- // 文件名:entities/dog.go
- package entities
- import "gorm.io/gorm"
- type Dog struct {
- gorm.Model
- Name string `json:"name"`
- Age int `json:"age"`
- Breed string `json:"breed"`
- IsGoodBoy bool `json:"is_good_boy" gorm:"default:true"`
- }
注意其中的內嵌類型 gorm.Model,它只是定義了一些通用的字段。
- type Model struct {
- ID uint `gorm:"primarykey"`
- CreatedAt time.Time
- UpdatedAt time.Time
- DeletedAt DeletedAt `gorm:"index"`
- }
因此,我們完全可以自己選擇是否要嵌入 gorm.Model。
接著,我們配置與數據庫的連接。一般我喜歡創建一個名為 Connect() 的函數,它負責初始化連接,此外還負責在我們的數據庫中執行遷移(migration),即生成表結構:
- // 文件名:config/database.go
- package config
- import (
- "github.com/programmerug/fibergorm/entities"
- "gorm.io/driver/sqlite"
- "gorm.io/gorm"
- )
- var Database *gorm.DB
- func Connect() error {
- var err error
- Database, err = gorm.Open(sqlite.Open("fibergorm.db"), &gorm.Config{})
- if err != nil {
- panic(err)
- }
- Database.AutoMigrate(&entities.Dog{})
- return nil
- }
- fibergorm.db 是最后生成的數據庫文件
- 在程序啟動時,需要調用 Connect 函數
現在已經定義了實體并配置了到數據庫的連接,我們可以開始處理我們的處理程序。我們的每個處理程序都將對應來自 API 的一個路由,每個處理程序只負責執行一個操作。首先讓我們獲取數據庫表中的所有記錄。
- // 文件名:handlers/dog.go
- package handlers
- import (
- "github.com/gofiber/fiber/v2"
- "github.com/programmerug/fibergorm/config"
- "github.com/programmerug/fibergorm/entities"
- )
- func GetDogs(c *fiber.Ctx) error {
- var dogs []entities.Dog
- config.Database.Find(&dogs)
- return c.Status(200).JSON(dogs)
- }
- // ...
現在根據將在請求參數中發送的 id 參數獲取一條記錄。
- // 文件名:handlers/dog.go
- package handlers
- // ...
- func GetDog(c *fiber.Ctx) error {
- id := c.Params("id")
- var dog entities.Dog
- result := config.Database.Find(&dog, id)
- if result.RowsAffected == 0 {
- return c.SendStatus(404)
- }
- return c.Status(200).JSON(&dog)
- }
- // ...
現在我們可以得到所有的記錄和根據 id 獲取一條記錄。但缺乏在數據庫表中插入新記錄的功能。
- // 文件名:handlers/dog.go
- package handlers
- // ...
- func AddDog(c *fiber.Ctx) error {
- dog := new(entities.Dog)
- if err := c.BodyParser(dog); err != nil {
- return c.Status(503).SendString(err.Error())
- }
- config.Database.Create(&dog)
- return c.Status(201).JSON(dog)
- }
- // ...
我們還需要添加更新數據庫中現有記錄的功能。與我們已經實現的類似,使用id參數來更新特定記錄。
- // 文件名:handlers/dog.go
- package handlers
- // ...
- func UpdateDog(c *fiber.Ctx) error {
- dog := new(entities.Dog)
- id := c.Params("id")
- if err := c.BodyParser(dog); err != nil {
- return c.Status(503).SendString(err.Error())
- }
- config.Database.Where("id = ?", id).Updates(&dog)
- return c.Status(200).JSON(dog)
- }
- // ...
- 最后,我們需要刪除特定記錄,再次使
最后,我們需要刪除特定記錄,再次使用 id 參數從我們的數據庫中刪除特定記錄。
- // 文件名:handlers/dog.go
- package handlers
- // ...
- func RemoveDog(c *fiber.Ctx) error {
- id := c.Params("id")
- var dog entities.Dog
- result := config.Database.Delete(&dog, id)
- if result.RowsAffected == 0 {
- return c.SendStatus(404)
- }
- return c.SendStatus(200)
- }
- // ...
現在只需要創建我們的 main 文件,該文件將負責初始化與數據庫的連接以及我們的 API 路由將在何處定義,并且將處理程序與它們進行關聯綁定。
- // 文件名:main.go
- package main
- import (
- "log"
- "github.com/gofiber/fiber/v2"
- "github.com/programmerug/fibergorm/config"
- "github.com/programmerug/fibergorm/handlers"
- )
- func main() {
- app := fiber.New()
- config.Connect()
- app.Get("/dogs", handlers.GetDogs)
- app.Get("/dogs/:id", handlers.GetDog)
- app.Post("/dogs", handlers.AddDog)
- app.Put("/dogs/:id", handlers.UpdateDog)
- app.Delete("/dogs/:id", handlers.RemoveDog)
- log.Fatal(app.Listen(":3000"))
- }
至此,我們完成了一個簡單應用的 CRUD。涉及到 fiber 和 gorm 的 API 你應該查閱相關文檔進一步了解。
最終,項目目錄如下:
- ├── config
- │ └── database.go
- ├── entities
- │ └── dog.go
- ├── fibergorm.db
- ├── go.mod
- ├── go.sum
- ├── handlers
- │ └── dog.go
- └── main.go
執行 go run main.go:
- $ go run main.go
- ┌───────────────────────────────────────────────────┐
- │ Fiber v2.24.0 │
- │ http://127.0.0.1:3000 │
- │ (bound on host 0.0.0.0 and port 3000) │
- │ │
- │ Handlers ............. 7 Processes ........... 1 │
- │ Prefork ....... Disabled PID ............. 89910 │
- └───────────────────────────────────────────────────┘
借助 postman、curl 之類的工具驗證接口的正確性。
因為 fiber Ctx 的 BodyParser 能夠解析 Context-Type 值是:application/json, application/xml, application/x-www-form-urlencoded, multipart/form-data 等的數據,所以,驗證 AddDog 的可以采用你喜歡的 Content-Type。
- curl --location --request POST 'http://127.0.0.1:3000/dogs' \
- --header 'Content-Type: application/json' \
- --data '{name:"旺財",age:3,breed:"狼狗",is_good_boy:true}'
可以通過下載 https://sqlitebrowser.org/dl/ 這個 SQLite 工具查看數據是否保存成功。
細心的讀者可能會發現,生成的數據表字段順序是根據 Dog 中的字段定義順序確定的。有強迫癥的人可能接受不了,因此實際中你可以不嵌入 gorm.Model,而是自己定義相關字段。
總結
本文實現了 CRUD 的功能,希望大家實際動手,這樣才能夠真正掌握。
本文參考 https://dev.to/franciscomendes10866/how-to-build-rest-api-using-go-fiber-and-gorm-orm-2jbe。
本文完整代碼:https://github.com/programmerug/fibergorm。
參考資料
[1]Fiber: https://gofiber.io/
[2]Gorm: https://gorm.io/
[3]Gorm: https://gorm.io/