原文鏈接:https://medium.com/@cgrant/developing-a-simple-crud-api-with-go-gin-and-gorm-df87d98e6ed1
翻譯:devabel
介紹
Golang是一種令人興奮的語言,但新手可能會被新的語法和各種框架所淹沒。基礎知識入門可能會成為一項挑戰。
在這個例子中,我想展示創建功能API所需的最少代碼。我們將開發一個簡單的API,為基本模型提供創建,讀取,更新和刪除(CRUD)功能。使用和對象關系映射(ORM)工具,我們將能夠快速更新我們的數據模型,所有數據模型都在100行代碼之下。所以讓我們開始吧。
在https://github.com/cgrant/gin-gorm-api-example可以找到這篇文章的所有代碼。
入門
這個例子假設你已經安裝并運行go語言的環境。如果您還沒有安裝,請轉到http://cgrant.io/tutorials/go/getting-started-with-go/獲取快速入門。
Gin Web框架
由于我們將通過HTTP提供我們的API,因此我們需要一個Web框架來處理路由并提供請求。有許多框架可用,具有不同的功能和性能指標。在這個例子中,我們將使用Gin框架https://github.com/gin-gonic/gin 。由于速度和簡單性,Gin是API開發的一個很好的框架。
首先,讓我們在$ GOPATH / src / simple-api中為我們的服務創建一個新文件夾,然后添加一個main.go文件,如下所示
package main
import “fmt”
func main() {
fmt.Println(“Hello World”)
}
在我們繼續學習前,讓我們測試一下,確保一切正常運行。
$ go run main.go
Hello World
程序運行正常。現在讓我們使用Gin框架將它變成一個Web應用程序。
package main
import “github.com/gin-gonic/gin”
func main() {
r := gin.Default()
r.GET(“/”, func(c *gin.Context) {
c.String(200, “Hello World”)
})
r.Run()
}
保存并運行它
$ go run main.go
[GIN-debug] [WARNING] Running in “debug” mode. Switch to “release” mode in production.
— using env: export GIN_MODE=release
— using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET / → main.main.func1 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
[GIN] 2016/12/02–14:57:52 | 200 | 33.798μs | ::1 | GET /
然后瀏覽器訪問http://localhost:8080/
Hello World
成功!!!
我們正在構建一個API,但不是一個Web應用程序,所以讓我們將其切換到JSON響應
package main
import “github.com/gin-gonic/gin”
func main() {
r := gin.Default()
r.GET(“/”, func(c *gin.Context) {
c.JSON(200, gin.H{
“message”: “Hello World”,
})
})
r.Run() // listen and server on 0.0.0.0:8080
}
保存文件,重新運行并刷新瀏覽器,您應該看到我們的JSON 消息
{“message”:“Hello World”}
GORM的數據持久性
現在讓我們看看我們的持久層。對于本節我們將使用本地的基于SQLite文件的數據庫開始。稍后我們將切換到使用MySql來證明。
Gorm http://jinzhu.me/gorm/是一個用于go的對象關系映射(ORM)框架。它大大簡化了模型到數據庫的映射和持久化。盡管我不是大型復雜系統的ORM的忠實擁躉,但他們對原型開發新的綠地應用程序確實很有效。Gorm在Go領域是一個非常受歡迎的工具,我們將在這里看看它。
為了解決gorm問題,我們將把我們剛寫的Gin代碼換掉,并且演示一下gorm的功能。學習完這塊,我們會將Gin重新添加到應用程序中。
讓我們開始一個小例子。
package main
import (
“github.com/jinzhu/gorm”
_ “github.com/jinzhu/gorm/dialects/sqlite”
)
func main() {
db, _ := gorm.Open(“sqlite3”, “./gorm.db”)
defer db.Close()
}
如果你現在運行這個程序,你會在你的文件系統中看到一個名為gorm.db的新文件。這是我們的數據庫文件系統將在應用程序中使用。雖然我們可以看到我們的應用程序正在運行,而且gorm正在被使用,但我們的系統還沒有完成。讓我們添加更多的代碼。
package main
import (
“fmt”
“github.com/jinzhu/gorm”
_ “github.com/jinzhu/gorm/dialects/sqlite”
)
type Person struct {
ID uint `json:”id”`
FirstName string `json:”firstname”`
LastName string `json:”lastname”`
}
func main() {
db, _ := gorm.Open(“sqlite3”, “./gorm.db”)
defer db.Close()
p1 := Person{FirstName: “John”, LastName: “Doe”}
p2 := Person{FirstName: “Jane”, LastName: “Smith”}
fmt.Println(p1.FirstName)
fmt.Println(p2.LastName)
}
在這里,我們剛剛添加了一個簡單的Person結構,并在使用它們打印出值之前創建了一些實例。請記住,Person結構中的字段需要以大寫字母開頭,因為Go定義這些是公共字段。
現在我們已經有了一個可以使用Gorm的對象。
package main
import (
“fmt”
“github.com/jinzhu/gorm”
_ “github.com/jinzhu/gorm/dialects/sqlite”
)
type Person struct {
ID uint `json:”id”`
FirstName string `json:”firstname”`
LastName string `json:”lastname”`
}
func main() {
db, _ := gorm.Open(“sqlite3”, “./gorm.db”)
defer db.Close()
db.AutoMigrate(&Person{})
p1 := Person{FirstName: “John”, LastName: “Doe”}
p2 := Person{FirstName: “Jane”, LastName: “Smith”}
db.Create(&p1)
var p3 Person // identify a Person type for us to store the results in
db.First(&p3) // Find the first record in the Database and store it in p3
fmt.Println(p1.FirstName)
fmt.Println(p2.LastName)
fmt.Println(p3.LastName) // print out our record from the database
}
現在讓我們運行它,看看輸出什么
$ go run main.go
John
Smith
Doe
哇,非常簡單。只需幾行代碼,我們就可以保存并從數據庫中檢索。Gorm在如何存儲和查詢他們的網站上有更多的選項。接下來我們將介紹幾個核心部分,但請查看他們的文檔以獲取更多選項。
制作API
我們已經回顧了框架如何獨立運作。現在是時候把所有東西放在一起成為一個可用的API
查詢所有信息
讓我們從查閱我們之前添加的數據開始閱讀CRUD的部分。我將刪除我們剛剛通過的一些行,并用一個新路由添加到Gin框架中,以查詢我們的數據庫。
package main
import (
“fmt”
“github.com/gin-gonic/gin”
“github.com/jinzhu/gorm”
_ “github.com/jinzhu/gorm/dialects/sqlite”
)
var db *gorm.DB
var err error
type Person struct {
ID uint `json:”id”`
FirstName string `json:”firstname”`
LastName string `json:”lastname”`
}
func main() {
// NOTE: See we’re using = to assign the global var
// instead of := which would assign it only in this function
db, err = gorm.Open(“sqlite3”, “./gorm.db”)
if err != nil {
fmt.Println(err)
}
defer db.Close()
db.AutoMigrate(&Person{})
r := gin.Default()
r.GET(“/”, GetProjects)
r.Run(“:8080”)
}
func GetProjects(c *gin.Context) {
var people []Person
if err := db.Find(&people).Error; err != nil {
c.AbortWithStatus(404)
fmt.Println(err)
} else {
c.JSON(200, people)
}
}
現在運行它并轉到http://localhost:8080/,你應該看到
[{“id”: 1,”firstname”: “John”,”lastname”: “Doe”}]
哇只是幾行代碼,我們已經獲得API響應。大部分都是錯誤處理!
查詢單條信息
OK讓我們以REST為導向更新上下文,并增加查找單條信息的功能。
package main
import (
“fmt”
“github.com/gin-gonic/gin”
“github.com/jinzhu/gorm”
_ “github.com/jinzhu/gorm/dialects/sqlite”
)
var db *gorm.DB
var err error
type Person struct {
ID uint `json:”id”`
FirstName string `json:”firstname”`
LastName string `json:”lastname”`
}
func main() {
// NOTE: See we’re using = to assign the global var
// instead of := which would assign it only in this function
db, err = gorm.Open(“sqlite3”, “./gorm.db”)
if err != nil {
fmt.Println(err)
}
defer db.Close()
db.AutoMigrate(&Person{})
r := gin.Default()
r.GET(“/people/”, GetPeople)
r.GET(“/people/:id”, GetPerson)
r.Run(“:8080”)
}
func GetPerson(c *gin.Context) {
id := c.Params.ByName(“id”)
var person Person
if err := db.Where(“id = ?”, id).First(&person).Error; err != nil {
c.AbortWithStatus(404)
fmt.Println(err)
} else {
c.JSON(200, person)
}
}
func GetPeople(c *gin.Context) {
var people []Person
if err := db.Find(&people).Error; err != nil {
c.AbortWithStatus(404)
fmt.Println(err)
} else {
c.JSON(200, people)
}
}
現在運行服務器,但請注意,我們更改了上下文,現在您將轉到http:// localhost:8080 / people /查看您的列表。一旦將該ID添加到網址的末尾,您將得到單個記錄返回http:// localhost:8080 / people / 1
{“id”: 1,”firstname”: “John”,”lastname”: “Doe”}
創建
很難用僅有一條記錄來看出差異。很難區分[{...}]和{...}之間的區別所以讓我們添加Create函數和路由
package main
import (
“fmt”
“github.com/gin-gonic/gin”
“github.com/jinzhu/gorm”
_ “github.com/jinzhu/gorm/dialects/sqlite”
)
var db *gorm.DB
var err error
type Person struct {
ID uint `json:”id”`
FirstName string `json:”firstname”`
LastName string `json:”lastname”`
}
func main() {
// NOTE: See we’re using = to assign the global var
// instead of := which would assign it only in this function
db, err = gorm.Open(“sqlite3”, “./gorm.db”)
if err != nil {
fmt.Println(err)
}
defer db.Close()
db.AutoMigrate(&Person{})
r := gin.Default()
r.GET(“/people/”, GetPeople)
r.GET(“/people/:id”, GetPerson)
r.POST(“/people”, CreatePerson)
r.Run(“:8080”)
}
func CreatePerson(c *gin.Context) {
var person Person
c.BindJSON(&person)
db.Create(&person)
c.JSON(200, person)
}
func GetPerson(c *gin.Context) {
id := c.Params.ByName(“id”)
var person Person
if err := db.Where(“id = ?”, id).First(&person).Error; err != nil {
c.AbortWithStatus(404)
fmt.Println(err)
} else {
c.JSON(200, person)
}
}
func GetPeople(c *gin.Context) {
var people []Person
if err := db.Find(&people).Error; err != nil {
c.AbortWithStatus(404)
fmt.Println(err)
} else {
c.JSON(200, people)
}
}
現在要測試這一個,我們將從命令行運行一個curl命令。我們還需要運行服務器,以便打開另一個終端窗口,以便可以運行這兩個命令。使用$ go run main.go在第一個窗口中運行服務器
一旦運行,在第二個窗口運行:
$ curl -i -X POST http://localhost:8080/people -d ‘{ “FirstName”: “Elvis”, “LastName”: “Presley”}’
你應該看到一個成功的回應
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sat, 03 Dec 2016 00:14:06 GMT
Content-Length: 50
{“id”:2,”firstname”:”Elvis”,”lastname”:”Presley”}
現在讓我們在瀏覽器中列出我們的人員,看看它是否列出了我們所有的條目
http:// localhost:8080 / people /
[{“id”: 1,”firstname”: “John”,”lastname”: “Doe”},{“id”: 2,”firstname”: “Elvis”,”lastname”: “Presley”}]
太棒了,輸出結果!看到這里你認為這很酷。
這次只發送部分Person數據
$ curl -i -X POST [http://localhost:8080/people](http://localhost:8080/people) -d ‘{ “FirstName”: “Madison”}’
刷新瀏覽器并查看它只添加了我們發送的數據
[{“id”: 1,”firstname”: “John”,”lastname”: “Doe”},{“id”: 2,”firstname”: “Elvis”,”lastname”: “Presley”},{“id”: 3,”firstname”: “Madison”,”lastname”: “”}]
這是Gin的一部分,請注意CreatePerson函數中的c.BindJSON(&person)行。它會自動填充請求中的任何匹配數據字段。
你也可能錯過了它,但我的數據庫中的情況和我通過的情況是不同的。Gin 對大小寫不敏感。我傳入了FirstName ,但數據庫使用了firstname.。
很簡單!
更新
盡管如此,我們不能讓Madison的last name為空。是時候添加我們的更新功能了
package main
import (
“fmt”
“github.com/gin-gonic/gin”
“github.com/jinzhu/gorm”
_ “github.com/jinzhu/gorm/dialects/sqlite”
)
var db *gorm.DB
var err error
type Person struct {
ID uint `json:”id”`
FirstName string `json:”firstname”`
LastName string `json:”lastname”`
}
func main() {
// NOTE: See we’re using = to assign the global var
// instead of := which would assign it only in this function
db, err = gorm.Open(“sqlite3”, “./gorm.db”)
if err != nil {
fmt.Println(err)
}
defer db.Close()
db.AutoMigrate(&Person{})
r := gin.Default()
r.GET(“/people/”, GetPeople)
r.GET(“/people/:id”, GetPerson)
r.POST(“/people”, CreatePerson)
r.PUT(“/people/:id”, UpdatePerson)
r.Run(“:8080”)
}
func UpdatePerson(c *gin.Context) {
var person Person
id := c.Params.ByName(“id”)
if err := db.Where(“id = ?”, id).First(&person).Error; err != nil {
c.AbortWithStatus(404)
fmt.Println(err)
}
c.BindJSON(&person)
db.Save(&person)
c.JSON(200, person)
}
func CreatePerson(c *gin.Context) {
var person Person
c.BindJSON(&person)
db.Create(&person)
c.JSON(200, person)
}
func GetPerson(c *gin.Context) {
id := c.Params.ByName(“id”)
var person Person
if err := db.Where(“id = ?”, id).First(&person).Error; err != nil {
c.AbortWithStatus(404)
fmt.Println(err)
} else {
c.JSON(200, person)
}
}
func GetPeople(c *gin.Context) {
var people []Person
if err := db.Find(&people).Error; err != nil {
c.AbortWithStatus(404)
fmt.Println(err)
} else {
c.JSON(200, people)
}
}
為了測試這個,我們將使用一個類似的curl命令,但是我們會在特定的用戶上調用PUT方法
$ curl -i -X PUT http://localhost:8080/people/3 -d ‘{ “FirstName”: “Madison”, “LastName”:”Sawyer” }’
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sat, 03 Dec 2016 00:25:35 GMT
Content-Length: 51
{“id”:3,”firstname”:”Madison”,”lastname”:”Sawyer”}
果然,如果我們刷新我們的瀏覽器,我們看到它添加了Sawyer這條數據。
[{“id”: 1,”firstname”: “John”,”lastname”: “Doe”},{“id”: 2,”firstname”: “Elvis”,”lastname”: “Presley”},{“id”: 3,”firstname”: “Madison”,”lastname”: “Sawyer”}]
我們再次可以發送部分數據進行部分更新
$ curl -i -X PUT http://localhost:8080/people/3 -d ‘{ “FirstName”: “Tom” }’
顯示為
[{“id”: 1,”firstname”: “John”,”lastname”: “Doe”},{“id”: 2,”firstname”: “Elvis”,”lastname”: “Presley”},{“id”: 3,”firstname”: “Tom”,”lastname”: “Sawyer”}]
刪除
現在要完成該解決方案讓dd在刪除功能
package main
import (
“fmt”
“github.com/gin-gonic/gin”
“github.com/jinzhu/gorm”
_ “github.com/jinzhu/gorm/dialects/sqlite”
)
var db *gorm.DB
var err error
type Person struct {
ID uint `json:”id”`
FirstName string `json:”firstname”`
LastName string `json:”lastname”`
}
func main() {
// NOTE: See we’re using = to assign the global var
// instead of := which would assign it only in this function
db, err = gorm.Open(“sqlite3”, “./gorm.db”)
if err != nil {
fmt.Println(err)
}
defer db.Close()
db.AutoMigrate(&Person{})
r := gin.Default()
r.GET(“/people/”, GetPeople)
r.GET(“/people/:id”, GetPerson)
r.POST(“/people”, CreatePerson)
r.PUT(“/people/:id”, UpdatePerson)
r.DELETE(“/people/:id”, DeletePerson)
r.Run(“:8080”)
}
func DeletePerson(c *gin.Context) {
id := c.Params.ByName(“id”)
var person Person
d := db.Where(“id = ?”, id).Delete(&person)
fmt.Println(d)
c.JSON(200, gin.H{“id #” + id: “deleted”})
}
func UpdatePerson(c *gin.Context) {
var person Person
id := c.Params.ByName(“id”)
if err := db.Where(“id = ?”, id).First(&person).Error; err != nil {
c.AbortWithStatus(404)
fmt.Println(err)
}
c.BindJSON(&person)
db.Save(&person)
c.JSON(200, person)
}
func CreatePerson(c *gin.Context) {
var person Person
c.BindJSON(&person)
db.Create(&person)
c.JSON(200, person)
}
func GetPerson(c *gin.Context) {
id := c.Params.ByName(“id”)
var person Person
if err := db.Where(“id = ?”, id).First(&person).Error; err != nil {
c.AbortWithStatus(404)
fmt.Println(err)
} else {
c.JSON(200, person)
}
}
func GetPeople(c *gin.Context) {
var people []Person
if err := db.Find(&people).Error; err != nil {
c.AbortWithStatus(404)
fmt.Println(err)
} else {
c.JSON(200, people)
}
}
為了測試這個,我們將使用帶curl的Delete方法來調用它
$ curl -i -X DELETE http://localhost:8080/people/1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sat, 03 Dec 2016 00:32:40 GMT
Content-Length: 20
{“id #1”:”deleted”}
刷新瀏覽器,你會看到我們的John Doe已被刪除
[{“id”: 2,”firstname”: “Elvis”,”lastname”: “Presley”},{“id”: 3,”firstname”: “Tom”,”lastname”: “Sawyer”}]
修改模型
在定義基本的API后,現在是開始改變Person對象的好時機。我們可以通過只更改person結構來輕松修改數據庫和api。
我要做的就是在Person Struct中添加一個city字段。沒有其他的,就一行。
type Person struct {
ID uint `json:”id”`
FirstName string `json:”firstname”`
LastName string `json:”lastname”`
City string `json:”city”`
}
刷新我們的瀏覽器并拉出列表,您可以看到我所有的對象現在都有city
[{“id”: 2,”firstname”: “Elvis”,”lastname”: “Presley”,”city”: “”},{“id”: 3,”firstname”: “Tom”,”lastname”: “Sawyer”,”city”: “”}]
我可以創建并更新新字段,而無需進行其他更改
$ curl -i -X PUT http://localhost:8080/people/2 -d ‘{ “city”: “Memphis” }’
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sat, 03 Dec 2016 00:40:57 GMT
Content-Length: 67
{“id”:2,”firstname”:”Elvis”,”lastname”:”Presley”,”city”:”Memphis”}
這全部由我們主函數中的db.AutoMigrate(&Person {})行處理。在生產環境中,我們希望更接近地管理架構,但對于原型來說,這是完美的
使用MySql
好的,我明白,沒問題,你想使用MySql而不是SQLite。
為此,我們只需要修改一個導入聲明和連接..
導入_“github.com/go-sql-driver/mysql”
連接
db, _ = gorm.Open(“mysql”, “user:pass@tcp(127.0.0.1:3306)/samples?charset=utf8&parseTime=True&loc=Local”)
完整的例子
package main
// only need mysql OR sqlite
// both are included here for reference
import (
“fmt”
“github.com/gin-gonic/gin”
_ “github.com/go-sql-driver/mysql”
“github.com/jinzhu/gorm”
_ “github.com/jinzhu/gorm/dialects/sqlite”
)
var db *gorm.DB
var err error
type Person struct {
ID uint `json:”id”`
FirstName string `json:”firstname”`
LastName string `json:”lastname”`
City string `json:”city”`
}
func main() {
// NOTE: See we’re using = to assign the global var
// instead of := which would assign it only in this function
//db, err = gorm.Open(“sqlite3”, “./gorm.db”)
db, _ = gorm.Open(“mysql”, “user:pass@tcp(127.0.0.1:3306)/database?charset=utf8&parseTime=True&loc=Local”)
if err != nil {
fmt.Println(err)
}
defer db.Close()
db.AutoMigrate(&Person{})
r := gin.Default()
r.GET(“/people/”, GetPeople)
r.GET(“/people/:id”, GetPerson)
r.POST(“/people”, CreatePerson)
r.PUT(“/people/:id”, UpdatePerson)
r.DELETE(“/people/:id”, DeletePerson)
r.Run(“:8080”)
}
func DeletePerson(c *gin.Context) {
id := c.Params.ByName(“id”)
var person Person
d := db.Where(“id = ?”, id).Delete(&person)
fmt.Println(d)
c.JSON(200, gin.H{“id #” + id: “deleted”})
}
func UpdatePerson(c *gin.Context) {
var person Person
id := c.Params.ByName(“id”)
if err := db.Where(“id = ?”, id).First(&person).Error; err != nil {
c.AbortWithStatus(404)
fmt.Println(err)
}
c.BindJSON(&person)
db.Save(&person)
c.JSON(200, person)
}
func CreatePerson(c *gin.Context) {
var person Person
c.BindJSON(&person)
db.Create(&person)
c.JSON(200, person)
}
func GetPerson(c *gin.Context) {
id := c.Params.ByName(“id”)
var person Person
if err := db.Where(“id = ?”, id).First(&person).Error; err != nil {
c.AbortWithStatus(404)
fmt.Println(err)
} else {
c.JSON(200, person)
}
}
func GetPeople(c *gin.Context) {
var people []Person
if err := db.Find(&people).Error; err != nil {
c.AbortWithStatus(404)
fmt.Println(err)
} else {
c.JSON(200, people)
}
}
結論
Go是一個靈活的語言,具有強大的環境。它非常容易使用少量代碼快速構建功能豐富的應用程序。我希望這是一個有用的練習。請隨時分享您的想法和問題。