使用Go,Gin和Gorm开发一个简单的CRUDAPI

(原文作者:Christopher Grant)

介绍

Golang是一种令人兴奋的语言,但是新来者可能会被新的语义和可用的各种框架所淹没。入门入门可能会是一个挑战。

在此示例中,我想展示创建功能性api所需的最少代码集。我们将开发一个简单的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 Web框架https://github.com/gin-gonic/gin。由于Gin的速度和简便性,它是API开发的一个不错的框架。

首先,让我们在$ GOPATH/src/simple-api中为我们的服务创建一个新文件夹,并添加一个main.go文件,如下所示

packagemainimportfmtfuncmain(){
 fmt.Println(Hello World)
}

在我们走得太远之前,让我们对其进行测试以确保一切正常运行。

$gorun main.goHello World

完美,我们都准备好了。现在,让我们使用Gin框架将其制作到Web应用程序中。

package mainimportgithub.com/gin-gonic/ginfuncmain(){
 r := gin.Default()
 r.GET(/,func(c*gin.Context){c.String(200,HelloWorld)
 })
 r.Run() 
}

保存并运行

$ go run main.go
[GIN-debug] [WARNING] Running in debug mode. Switch toreleasemodeinproduction.
 —usingenv:exportGIN_MODE=releaseusingcode: gin.SetMode(gin.ReleaseMode)
[GIN-debug]GET/ → main.main.func1 (3handlers)
[GIN-debug] EnvironmentvariablePORTisundefined.Usingport :8080bydefault[GIN-debug] ListeningandservingHTTPon:8080[GIN]2016/12/0214:57:52|200|33.798µs | ::1|GET/

然后浏览到http://localhost:8080

HelloWorld

成功!!!

我们正在构建一个API(尽管不是Web应用程序),因此我们将其切换为JSON响应

package mainimportgithub.com/gin-gonic/ginfuncmain(){
 r := gin.Default()
 r.GET(/,func(c*gin.Context){c.JSON(200, gin.H{
 message:HelloWorld,
 })
 })
 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重新添加到应用程序中。

让我们从一个小例子开始。

packagemainimport(
 github.com/jinzhu/gorm
 _ github.com/jinzhu/gorm/dialects/sqlite
)funcmain(){
 db, _ := gorm.Open(sqlite3, ./gorm.db)deferdb.Close()
}

如果立即运行此命令,将会在文件系统中看到一个名为gorm.db的新文件。这是系统将在应用程序中使用的我们的数据库文件。虽然我们可以看到我们的应用程序正在运行,并且gorm正在被使用,但我们的系统还没有做什么工作。让我们添加更多代码。

packagemainimport(
 fmt
github.com/jinzhu/gorm
 _ github.com/jinzhu/gorm/dialects/sqlite
)typePersonstruct{
 IDuint`json:id`FirstNamestring`json:firstname`LastNamestring`json:lastname`}funcmain(){
 db, _ := gorm.Open(sqlite3, ./gorm.db)deferdb.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。

packagemainimport(
 fmt
github.com/jinzhu/gorm
 _ github.com/jinzhu/gorm/dialects/sqlite
)typePersonstruct{
 IDuint`json:id`FirstNamestring`json:firstname`LastNamestring`json:lastname`}funcmain(){
 db, _ := gorm.Open(sqlite3, ./gorm.db)deferdb.Close()
 db.AutoMigrate(&Person{})
 p1 := Person{FirstName: John, LastName: Doe}
 p2 := Person{FirstName: Jane, LastName: Smith}
 db.Create(&p1)varp3 Person// identify a Person type for us to store the results indb.First(&p3)// Find the first record in the Database and store it in p3fmt.Println(p1.FirstName)
 fmt.Println(p2.LastName)
 fmt.Println(p3.LastName)// print out our record from the database}

太好了,现在让我们运行它,看看我们有什么

$gorun main.goJohn
Smith
Doe

哇,这很容易。只需几行代码,我们就可以保存和检索数据库。Gorm在如何存储和在其网站上进行查询方面有更多选择。接下来,我们将介绍几个核心部分,但请查看其文档以获取更多选择

制作API

我们已经回顾了框架如何独立运作。现在是时候将所有内容组合到可用的API中了

阅读全部

让我们从查询CRUD的Read部分开始,首先查询我们之前添加的数据。我将删除几行内容,并在Gin框架中添加一条查询数据库的新路线。

packagemainimport(
 fmt
 github.com/gin-gonic/gin
 github.com/jinzhu/gorm
 _ github.com/jinzhu/gorm/dialects/sqlite
)vardb *gorm.DBvarerr errortypePersonstruct{
 IDuint`json:id`FirstNamestring`json:firstname`LastNamestring`json:lastname`}funcmain(){//NOTE:See we’re using = to assign the global var// instead of := which would assign it only in this functiondb, err = gorm.Open(sqlite3, ./gorm.db)iferr !=nil{
   fmt.Println(err)
 }deferdb.Close()
 db.AutoMigrate(&Person{})
 r := gin.Default()
 r.GET(/, GetProjects)
 r.Run(:8080)
}funcGetProjects(c *gin.Context){varpeople []Personiferr := 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,并增加了查找单个人的能力。

packagemainimport(
 fmt
 github.com/gin-gonic/gin
 github.com/jinzhu/gorm
 _ github.com/jinzhu/gorm/dialects/sqlite
)vardb *gorm.DBvarerr errortypePersonstruct{
 IDuint`json:id`FirstNamestring`json:firstname`LastNamestring`json:lastname`}funcmain(){//NOTE:See we’re using = to assign the global var// instead of := which would assign it only in this functiondb, err = gorm.Open(sqlite3, ./gorm.db)iferr !=nil{
    fmt.Println(err)
 }deferdb.Close()
 db.AutoMigrate(&Person{})
 r := gin.Default()
 r.GET(/people/, GetPeople)
 r.GET(/people/:id, GetPerson)
 r.Run(:8080)
}funcGetPerson(c *gin.Context){
 id := c.Params.ByName(id)varperson Personiferr := db.Where(id = ?, id).First(&person).Error; err !=nil{
    c.AbortWithStatus(404)
    fmt.Println(err)
 }else{
    c.JSON(200, person)
 }
}funcGetPeople(c *gin.Context){varpeople []Personiferr := db.Find(&people).Error; err !=nil{
 c.AbortWithStatus(404)
    fmt.Println(err)
 }else{
    c.JSON(200, people)
 }
}

现在运行服务器,但请注意我们已更改了上下文,因此现在您将转到http:// localhost:8080 / people /查看您的列表。在那里,将ID添加到url的末尾,您将获得单条记录http:// localhost:8080 / people / 1

{id: 1,firstname: John,lastname: Doe}

创建

仅使用一个记录就很难看出差异。很难说出[{…}]和{…}之间的区别,所以让我们添加Create函数和路由

packagemainimport(
 fmt
 github.com/gin-gonic/gin
 github.com/jinzhu/gorm
 _ github.com/jinzhu/gorm/dialects/sqlite
)vardb *gorm.DBvarerr errortypePersonstruct{
 IDuint`json:id`FirstNamestring`json:firstname`LastNamestring`json:lastname`}funcmain(){//NOTE:See we’re using = to assign the global var// instead of := which would assign it only in this functiondb, err = gorm.Open(sqlite3, ./gorm.db)iferr !=nil{
 fmt.Println(err)
 }deferdb.Close()
db.AutoMigrate(&Person{})
 r := gin.Default()
 r.GET(/people/, GetPeople)
 r.GET(/people/:id, GetPerson)
 r.POST(/people, CreatePerson)
r.Run(:8080)
}funcCreatePerson(c *gin.Context){varperson Person
 c.BindJSON(&person)
 db.Create(&person)
 c.JSON(200, person)
}funcGetPerson(c *gin.Context){
 id := c.Params.ByName(id)varperson Personiferr := db.Where(id = ?, id).First(&person).Error; err !=nil{
    c.AbortWithStatus(404)
    fmt.Println(err)
 }else{
    c.JSON(200, person)
 }
}funcGetPeople(c *gin.Context){varpeople []Personiferr := db.Find(&people).Error; err !=nil{
    c.AbortWithStatus(404)
    fmt.Println(err)
 }else{
    c.JSON(200, people)
 }
}

现在要测试这一点,我们将从命令行运行curl命令。我们还需要服务器运行,因此打开另一个终端窗口,以便您可以运行这两个命令。使用$ go在第一个窗口中运行服务器main.go

一旦运行,在第二个窗口中运行:

$ curl -i -X POSThttp://localhost:8080/people-d ‘{ FirstName: Elvis, LastName: Presley}’

您应该会看到成功的回复

HTTP/1.1200OKContent-Type:application/json;charset=utf-8Date:Sat,03Dec201600:14:06GMTContent-Length:50{id:2,firstname:Elvis,lastname:Presley}

现在,让我们在浏览器中列出人员,以查看它列出了我们所有的条目http:// localhost:8080 / people /

[{id:1,firstname: John,lastname: Doe},{id:2,firstname: Elvis,lastname: Presley}]

太棒了,它有效!您认为这很酷。

这次仅发送部分人

$ curl -i -X POSThttp://localhost:8080/people-d ‘{ FirstName: Madison}’

刷新浏览器,发现它仅添加了我们发送的数据

[{{id}:1, firstname: John, lastname: Doe},{id2, firstname: Elvis, lastname: Presley},{id3, firstname: Madison, lastname:}} ]

这是Gin的一部分,请注意CreatePerson函数中的c.BindJSON(&person)行。它会自动从请求中填充所有匹配的数据字段。

另外,您可能已经错过了它,但是我的数据库中的情况与我传入的情况不同。杜松子酒对田野的情况也非常宽容。我传入了名字,但数据库使用了名字。

很简单!

更新

但是,我们不能不给麦迪逊一个姓氏。是时候添加我们的更新功能了

package mainimport(
 fmt
 github.com/gin-gonic/gin
 github.com/jinzhu/gorm_github.com/jinzhu/gorm/dialects/sqlite
)vardb *gorm.DBvarerr error
typePersonstruct{IDuint `json:id`FirstNamestring `json:firstname`LastNamestring `json:lastname`
}funcmain(){//NOTE:See we’re using = to assign the global var// instead of := which would assign it only in this functiondb, err = gorm.Open(sqlite3, ./gorm.db)iferr !=nil{
    fmt.Println(err)
 }deferdb.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)
}funcUpdatePerson(c*gin.Context){varpersonPersonid :=c.Params.ByName(id)iferr := 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)
}funcCreatePerson(c*gin.Context){varpersonPersonc.BindJSON(&person)
 db.Create(&person)c.JSON(200, person)
}funcGetPerson(c*gin.Context){
 id :=c.Params.ByName(id)varpersonPersoniferr := db.Where(id = ?, id).First(&person).Error; err !=nil{c.AbortWithStatus(404)
    fmt.Println(err)
 }else{c.JSON(200, person)
 }
}funcGetPeople(c*gin.Context){varpeople []Personiferr := db.Find(&people).Error; err !=nil{c.AbortWithStatus(404)
    fmt.Println(err)
 }else{c.JSON(200, people)
 }
}

为了测试这一点,我们将使用类似的curl命令,但是我们将在特定用户上调用PUT方法

$curl-i-XPUThttp://localhost:8080/people/3-d‘{FirstName:Madison,LastName:Sawyer}’HTTP/1.1200OKContent-Type:application/json;charset=utf-8Date:Sat,03Dec201600:25:35GMTContent-Length:51{id:3,firstname:Madison,lastname:Sawyer}

当然,如果刷新浏览器,我们会看到它在姓氏中添加了sawyer。

[{{id}:1, firstname: John, lastname: Doe},{id2, firstname: Elvis, lastname: Presley},{id3, firstname: Madison, lastname: Sawyer} ]

同样,我们可以发送部分数据以进行部分更新

$ curl -i -X PUThttp://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进入delete函数。

package mainimport(
 fmt
 github.com/gin-gonic/gin
 github.com/jinzhu/gorm_github.com/jinzhu/gorm/dialects/sqlite
)vardb *gorm.DBvarerr error
typePersonstruct{IDuint `json:id`FirstNamestring `json:firstname`LastNamestring `json:lastname`
}funcmain(){//NOTE:See we’re using = to assign the global var// instead of := which would assign it only in this functiondb, err = gorm.Open(sqlite3, ./gorm.db)iferr !=nil{
    fmt.Println(err)
 }deferdb.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)
}funcDeletePerson(c*gin.Context){
 id :=c.Params.ByName(id)varpersonPersond := db.Where(id = ?, id).Delete(&person)
 fmt.Println(d)c.JSON(200, gin.H{id  + id: deleted})
}funcUpdatePerson(c*gin.Context){varpersonPersonid :=c.Params.ByName(id)iferr := 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)
}funcCreatePerson(c*gin.Context){varpersonPersonc.BindJSON(&person)
 db.Create(&person)c.JSON(200, person)
}funcGetPerson(c*gin.Context){
 id :=c.Params.ByName(id)varpersonPersoniferr := db.Where(id = ?, id).First(&person).Error; err !=nil{c.AbortWithStatus(404)
    fmt.Println(err)
 }else{c.JSON(200, person)
 }
}funcGetPeople(c*gin.Context){varpeople []Personiferr := db.Find(&people).Error; err !=nil{c.AbortWithStatus(404)
    fmt.Println(err)
 }else{c.JSON(200, people)
 }
}

为了测试这一点,我们将使用带有curl的Delete方法来调用它

$curl-i-XDELETEhttp://localhost:8080/people/1HTTP/1.1200OKContent-Type:application/json;charset=utf-8Date:Sat,03Dec201600:32:40GMTContent-Length:20{id1:deleted}

刷新浏览器,您将看到我们的John Doe已被删除

[{id:2,firstname: Elvis,lastname: Presley},{id:3,firstname: Tom,lastname: Sawyer}]

改变模型

定义了基本的API之后,现在是开始更改Person对象的好时机。我们只需更改person结构即可轻松修改数据库和api。

我要做的就是在Person Struct中添加一个city字段。没有其他的。一条线。

typePersonstruct{
 IDuint`json:id`FirstNamestring`json:firstname`LastNamestring`json:lastname`Citystring`json:city`}

刷新我们的浏览器并拉出一个列表,您可以看到我所有的对象现在都有城市

[{id:2,firstname: Elvis,lastname: Presley,city: },{id:3,firstname: Tom,lastname: Sawyer,city: }]

我可以在没有其他更改的情况下创建和更新新字段

$curl-i-XPUThttp://localhost:8080/people/2-d‘{city:Memphis}’HTTP/1.1200OKContent-Type:application/json;charset=utf-8Date:Sat,03Dec201600:40:57GMTContent-Length:67{id:2,firstname:Elvis,lastname:Presley,city:Memphis}

所有这些都由我们主要函数中的db.AutoMigrate(&Person {})行处理。在生产环境中,我们希望更紧密地管理模式,但是对于原型来说,这是完美的。

使用MySql

我听到了。很好,但是使用MySql代替SQLite怎么办。

为此,我们只需要换出一个import语句和连接。

Import_ 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 referenceimport(
 fmt
 github.com/gin-gonic/gin_github.com/go-sql-driver/mysql 
 github.com/jinzhu/gorm_github.com/jinzhu/gorm/dialects/sqlite
)vardb *gorm.DBvarerr error
typePersonstruct{IDuint `json:id`FirstNamestring `json:firstname`LastNamestring `json:lastname`Citystring `json:city`
}funcmain(){//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)iferr !=nil{
    fmt.Println(err)
 }deferdb.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)
}funcDeletePerson(c*gin.Context){
 id :=c.Params.ByName(id)varpersonPersond := db.Where(id = ?, id).Delete(&person)
 fmt.Println(d)c.JSON(200, gin.H{id  + id: deleted})
}funcUpdatePerson(c*gin.Context){varpersonPersonid :=c.Params.ByName(id)iferr := 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)
}funcCreatePerson(c*gin.Context){varpersonPersonc.BindJSON(&person)
 db.Create(&person)c.JSON(200, person)
}funcGetPerson(c*gin.Context){
 id :=c.Params.ByName(id)varpersonPersoniferr := db.Where(id = ?, id).First(&person).Error; err !=nil{c.AbortWithStatus(404)
    fmt.Println(err)
 }else{c.JSON(200, person)
 }
}funcGetPeople(c*gin.Context){varpeople []Personiferr := db.Find(&people).Error; err !=nil{c.AbortWithStatus(404)
    fmt.Println(err)
 }else{c.JSON(200, people)
 }
}

结论

Go是一种灵活的语言,具有强大的环境。它非常容易构建,并且只需少量代码即可快速构建功能丰富的应用程序。我希望这是一个有用的演练。请随时评论您的想法和问题

原文:https://medium.com/@cgrant/developing-a-simple-crud-api-with-go-gin-and-gorm-df87d98e6ed1

原创文章 使用Go,Gin和Gorm开发一个简单的CRUDAPI,版权所有
如若转载,请注明出处:https://www.itxiaozhan.cn/202211739.html

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注