使用Go,Gin和Gorm開發簡單的CRUD API

原文鏈接: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是一個靈活的語言,具有強大的環境。它非常容易使用少量代碼快速構建功能豐富的應用程序。我希望這是一個有用的練習。請隨時分享您的想法和問題。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。