項(xiàng)目實(shí)戰(zhàn):使用 Fiber + Gorm 構(gòu)建 REST API
Fiber 作為一個新的 Go 框架,似乎受追捧程度很高,Star 數(shù)飆升很快。不知道這是不是表明,不少 JS/Node 愛好者開始嘗試學(xué)習(xí) Go 了,對 Go 是好事。
今天這篇文章介紹如何使用 Fiber + Gorm 構(gòu)建 REST API。
1 概覽
在這篇文章中,我們將使用 Fiber[1] 框架,它使用起來非常簡單,有一個很好的抽象層,并且包含我們創(chuàng)建 API 所需的一切。
關(guān)于與數(shù)據(jù)庫的交互,我決定使用 ORM 來使整個過程更簡單、更直觀,因此我決定使用Gorm[2],在我看來,Gorm[3] 是 Go 世界中最受歡迎的 ORM,并且特性很多。
2 準(zhǔn)備工作
本文基于 Go1.17.5。
在本地創(chuàng)建一個目錄 fibergorm,然后進(jìn)入該目錄執(zhí)行如下命令:
$?go?mod?init?github.com/programmerug/fibergorm
go:?creating?new?go.mod:?module?github.com/programmerug/fibergorm
接著執(zhí)行如下命令,安裝我們需要的依賴:(這個先不執(zhí)行,之后通過 go mod tidy 安裝)
go?get?github.com/gofiber/fiber/v2
go?get?gorm.io/gorm
為了方便,本教程中,我們使用 SQLite,因?yàn)槭褂昧?Gorm,所以哪種關(guān)系型數(shù)據(jù)庫對核心代碼沒有什么影響。
3 開始編碼
本文以人類的朋友——狗為例。
先從定義實(shí)體開始,它總共有四個屬性:
Name- 狗的名字Age- 狗的年齡Breed- 狗的種族(類型)IsGoodBoy- 狗是否是個好孩子
類似以下內(nèi)容:
//?文件名: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"`
}
注意其中的內(nèi)嵌類型 gorm.Model,它只是定義了一些通用的字段。
type?Model?struct?{
????ID????????uint?`gorm:"primarykey"`
????CreatedAt?time.Time
????UpdatedAt?time.Time
????DeletedAt?DeletedAt?`gorm:"index"`
}
因此,我們完全可以自己選擇是否要嵌入 gorm.Model。
接著,我們配置與數(shù)據(jù)庫的連接。一般我喜歡創(chuàng)建一個名為 Connect() 的函數(shù),它負(fù)責(zé)初始化連接,此外還負(fù)責(zé)在我們的數(shù)據(jù)庫中執(zhí)行遷移(migration),即生成表結(jié)構(gòu):
//?文件名: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 是最后生成的數(shù)據(jù)庫文件
在程序啟動時,需要調(diào)用 Connect 函數(shù)
現(xiàn)在已經(jīng)定義了實(shí)體并配置了到數(shù)據(jù)庫的連接,我們可以開始處理我們的處理程序。我們的每個處理程序都將對應(yīng)來自 API 的一個路由,每個處理程序只負(fù)責(zé)執(zhí)行一個操作。首先讓我們獲取數(shù)據(jù)庫表中的所有記錄。
//?文件名: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)
}
//?...
現(xiàn)在根據(jù)將在請求參數(shù)中發(fā)送的 id 參數(shù)獲取一條記錄。
//?文件名: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)
}
//?...
現(xiàn)在我們可以得到所有的記錄和根據(jù) id 獲取一條記錄。但缺乏在數(shù)據(jù)庫表中插入新記錄的功能。
//?文件名: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)
}
//?...
我們還需要添加更新數(shù)據(jù)庫中現(xiàn)有記錄的功能。與我們已經(jīng)實(shí)現(xiàn)的類似,使用id參數(shù)來更新特定記錄。
//?文件名: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 參數(shù)從我們的數(shù)據(jù)庫中刪除特定記錄。
//?文件名: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)
}
//?...
現(xiàn)在只需要創(chuàng)建我們的 main 文件,該文件將負(fù)責(zé)初始化與數(shù)據(jù)庫的連接以及我們的 API 路由將在何處定義,并且將處理程序與它們進(jìn)行關(guān)聯(lián)綁定。
//?文件名: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"))
}
至此,我們完成了一個簡單應(yīng)用的 CRUD。涉及到 fiber 和 gorm 的 API 你應(yīng)該查閱相關(guān)文檔進(jìn)一步了解。
最終,項(xiàng)目目錄如下:
├──?config
│???└──?database.go
├──?entities
│???└──?dog.go
├──?fibergorm.db
├──?go.mod
├──?go.sum
├──?handlers
│???└──?dog.go
└──?main.go
執(zhí)行 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 之類的工具驗(yàn)證接口的正確性。
因?yàn)?fiber Ctx 的 BodyParser 能夠解析 Context-Type 值是:application/json, application/xml, application/x-www-form-urlencoded, multipart/form-data 等的數(shù)據(jù),所以,驗(yàn)證 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 工具查看數(shù)據(jù)是否保存成功。
細(xì)心的讀者可能會發(fā)現(xiàn),生成的數(shù)據(jù)表字段順序是根據(jù) Dog 中的字段定義順序確定的。有強(qiáng)迫癥的人可能接受不了,因此實(shí)際中你可以不嵌入 gorm.Model,而是自己定義相關(guān)字段。
總結(jié)
本文實(shí)現(xiàn)了 CRUD 的功能,希望大家實(shí)際動手,這樣才能夠真正掌握。
本文參考 https://dev.to/franciscomendes10866/how-to-build-rest-api-using-go-fiber-and-gorm-orm-2jbe。
本文完整代碼:https://github.com/programmerug/fibergorm。
參考資料
Fiber: https://gofiber.io/
[2]Gorm: https://gorm.io/
[3]Gorm: https://gorm.io/
推薦閱讀
