如何使用 Gin 和 Gorm 搭建一个简单的 API 服务 (二)
创建 API
我们之前已经跑过 Gin 框架的代码,现在是时候加些功能进去了。
读取全部信息
我们先从"增删改查"中的"查"入手,查询我们之前添加的信息。我接下来要删除几行代码,并把 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("g/", GetProjects) r.Run("g: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 服务器的响应了,而且大部分代码都是用来错误处理的。
读取特定信息
好,为了把 API 接口写的更符合 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("g/", GetProjects) r.GET("/people/:id", GetPerson) r.Run("g: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) } } 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) } }
现在运行程序,但请注意,如果要访问全部信息,你需要访问的地址变成了 http://localhost:8080/people/ ,如果 在 URL 的末尾加入了 ID,你就会得到特定的信息 http://localhost:8080/people/1
{"id": 1, "firstname": "John", "lastname": "Doe"}
添加信息
只有一条记录是看不大出来查询全部信息和查询单条信息的区别的,所以咱们来把添加信息的功能加上吧。
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("g/", GetProjects) r.GET("/people/:id", GetPerson) r.POST("/people", CreatePerson) r.Run("g: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) } } 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 CreatePerson(c *gin.Context) { var person Person c.BindJSON(&person) db.Create(&person) c.JSON(200, person) }
接下来让我们从终端运行 curl 命令测试一下新加的功能是不是可用,当然还是先要把程序运行起来。
在终端运行:
$ 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 -d ‘{ "FirstName": "Madison"}‘
刷新一下浏览器,发现只添加了我们发送的信息。
[{"id": 1,"firstname": "John","lastname": "Doe"},{"id": 2,"firstname": "Elvis","lastname": "Presley"},{"id": 3,"firstname": "Madison","lastname": ""}]
这就是 Gin 如何工作的了,留意一下 c.BindJSON(&person)
这行,它会自动匹配请求消息中的数据信息。
虽然请求消息里可能缺某些信息,就比如刚才那个例子,而且大小写不匹配也没有关系,Gin 的容错性非常高。非常简单!
更新信息
我们不能把 Madison 这条记录没有姓氏啊,是时候加入更新功能了。
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("g/", GetProjects) r.GET("/people/:id", GetPerson) r.POST("/people", CreatePerson) r.PUT("/people/:id", UpdatePerson) r.Run("g: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) } } 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 CreatePerson(c *gin.Context) { var person Person c.BindJSON(&person) db.Create(&person) c.JSON(200, person) } func UpdatePerson(c *gin.Context) { var person Person id