所有文章 > API开发 > 教程:使用Go和Gin开发RESTful API
教程:使用Go和Gin开发RESTful API

教程:使用Go和Gin开发RESTful API

本教程介绍了使用Go和Gin Web框架(Gin)编写RESTful网络服务API的基础知识。

如果您对Go及其工具有基本的了解,将能最大限度地利用本教程。如果这是您第一次接触Go,请参见《开始使用Go》以快速入门。

Gin简化了许多与构建Web应用程序相关的编程任务,包括网络服务。在本教程中,您将使用Gin来路由请求、检索请求详情,以及为响应进行JSON序列化。

在本教程中,您将构建一个具有两个终端节点的 RESTful API 服务器。您的示例项目将是关于老式爵士乐唱片的数据存储库。

本教程包括以下部分:

  1. 设计 API 端点。
  2. 为您的代码创建一个文件夹。
  3. 创建数据。
  4. 编写处理程序以返回所有项目。
  5. 编写处理程序以添加新项目。
  6. 编写处理程序以返回特定项目。

注意:有关其他教程,请参阅教程。

先决条件

  • 安装 Go 1.16 或更高版本。有关安装说明,请参阅安装 Go。
  • 用于编辑代码的工具。您拥有的任何文本编辑器都可以正常工作。
  • 命令终端。Go 在 Linux 和 Mac 上的任何终端上都能很好地运行, 以及 Windows 中的 PowerShell 或 cmd 上。
  • curl 工具。在 Linux 和 Mac 上,应该已经安装了它。在Windows上,它包含在 Windows 10 Insider build 17063 及更高版本中。对于更早的 Windows 版本,您可能需要安装它。有关详细信息,请参阅 Tar 和 Curl 进入 Windows。

设计 API 端点

您将构建一个提供访问出售老式黑胶唱片的商店的API。因此,您需要提供一些端点,通过这些端点,客户端可以获取和添加专辑供用户使用。

在开发 API 时,您通常从设计终端节点开始。如果端点易于理解,您的API用户将更成功。

以下是您将在本教程中创建的终端节点。

/albums

  • GET – 获取所有专辑的列表,以JSON格式返回。
  • POST – 从作为JSON发送的请求数据中添加新专辑

/albums/:id

  • GET – 通过其ID获取专辑,并以JSON格式返回专辑数据。

接下来,您将为代码创建一个文件夹。

为您的代码创建一个文件夹

首先,为您将编写的代码创建一个项目。

  1. 打开命令提示符并切换到您的主目录。 在 Linux 或 Mac 上:$ cd 在 Windows 上:C:\> cd %HOMEPATH%
  2. 使用命令提示符,为您的代码创建一个名为web-service-gin的目录。 $ mkdir web-service-gin $ cd web-service-gin
  3. 创建一个模块来管理依赖项。运行go mod init命令,为其提供您的代码所在模块的路径。 go mod init$ go mod init example/web-service-gin go: creating new go.mod: module example/web-service-gin 此命令将创建一个 go.mod 文件,您添加的依赖项将在其中 列出以进行跟踪。有关使用模块路径命名模块的更多信息,请参阅管理依赖项。

接下来,您将设计用于处理数据的数据结构。

创建数据

为了简化本教程,您需要将数据存储在内存中。更典型的API将与数据库交互。

请注意,将数据存储在内存中意味着每次停止服务器时,专辑集都将丢失,然后在启动服务器时重新创建。

编写代码

  1. 使用文本编辑器,在 Web 服务中创建一个名为 main.go 的文件 目录。您将在此文件中编写 Go 代码。
  2. 在 main.go 中,在文件顶部,粘贴以下包声明。 package main 一个独立程序(与库相反)始终位于 package main 中。
  3. 在 package 声明下方,粘贴结构的以下声明。您将使用它来将相册数据存储在内存中。 album结构体标签(如指定字段的名称) 当结构的内容序列化为 JSON 时。如果没有它们,JSON 将使用结构体的大写字段名称 – 这种样式在 JSON 的 JSON 格式。 // album represents data about a record album. type album struct { ID string `json:"id"` Title string `json:"title"` Artist string `json:"artist"` Price float64 `json:"price"` }
  4. 在您刚刚添加的结构声明下方,粘贴以下album结构体的声明,您将使用此结构体在内存中存储专辑数据。 // albums slice to seed record album data. var albums = []album{ {ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99}, {ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99}, {ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99}, }

接下来,您将编写实现第一个端点的代码。

编写处理程序以返回所有项目

当客户端在GET/albums发出请求时,您希望所有专辑作为JSON返回.

为此,您将编写以下内容:

  • 准备响应的逻辑
  • 用于将请求路径映射到逻辑的代码

请注意,这与运行时的执行顺序相反,但你是 首先添加依赖项,然后添加依赖于它们的代码。

编写代码

  1. 在上一节中添加的结构体代码下方,粘贴以下代码以获取专辑列表。 此getAlbums函数从专辑结构体的切片创建JSON,并将JSON写入响应 // getAlbums responds with the list of all albums as JSON. func getAlbums(c *gin.Context) { c.IndentedJSON(http.StatusOK, albums) } 在此代码中,您将: 编写一个getAlbums函数,该函数接受一个gin.Context参数。注意,您可以给这个函数起任何名字——Gin或Go都不要求特定的函数名格式。 gin.Context是Gin最重要的部分。它携带请求细节、验证和序列化JSON等。 (尽管名称相似,但这与Go的内置context包不同。) 调用Context.IndentedJSON将结构体序列化为JSON并添加到响应中。 函数的第一个参数是您想要发送给客户的HTTP状态码。在这里,您传递了来自net/http包的StatusOK常量,表示200 OK。 注意,您可以将Context.IndentedJSON替换为对Context.JSON的调用,以发送更紧凑的JSON。在实践中,调试时缩进形式更容易使用,而且大小差异通常很小。
  2. 在 main.go 顶部附近,就在 slice 声明的正下方,粘贴 以下代码将 Handler 函数分配给终端节点路径。 这将设置一个关联,用于处理对终端节点路径的请求。 func main() { router := gin.Default() router.GET("/albums", getAlbums) router.Run("localhost:8080") } 在此代码中,您将: 使用 Default 初始化 Gin 路由器。 使用GET函数将GET HTTP方法和/albums路径与处理程序函数相关联。 请注意,您传递的是getAlbums函数的名称。这与通过传递getAlbums()(注意圆括号)来传递函数的结果是不同的。 使用Run函数将路由器附加到http.Server并启动服务器。
  3. 在main.go的顶部附近,就在包声明的正下方,导入支持您刚刚编写的代码所需的包。 代码的前几行应该如下所示: package main import ( "net/http" "github.com/gin-gonic/gin" )
  4. 保存main.go。

运行代码

  1. 开始跟踪Gin模块作为依赖项。 在命令行中,使用go get将github.com/gin-gonic/gin模块添加为您模块的依赖项。使用点参数表示“获取当前目录中的代码的依赖项”。 $ go get . go get: added github.com/gin-gonic/gin v1.7.2 Go resolved 并下载了此依赖项,以满足您在上一步中添加的声明。import
  2. 在包含main.go的目录的命令行中运行代码。使用点参数表示“运行当前目录中的代码”。 $ go run . 一旦代码运行起来,您就有了一个可以发送请求的正在运行的HTTP服务器。
  3. 在新的命令行窗口中,使用curl向运行中的Web服务发送请求。 $ curl http://localhost:8080/albums 该命令应显示您为服务提供的数据。 [ { "id": "1", "title": "Blue Train", "artist": "John Coltrane", "price": 56.99 }, { "id": "2", "title": "Jeru", "artist": "Gerry Mulligan", "price": 17.99 }, { "id": "3", "title": "Sarah Vaughan and Clifford Brown", "artist": "Sarah Vaughan", "price": 39.99 } ]

您已经启动了一个API!在下一节中,您将创建另一个端点,编写代码来处理对项目的POST请求。

编写处理程序以添加新项目

当客户端在/albums上发送POST请求时,您希望将请求体中描述的专辑添加到现有的专辑数据中。

为此,您将编写以下内容:

  • 逻辑以将新专辑添加到现有列表中。
  • 一小段代码来将POST请求路由到您的逻辑。

编写代码

  1. 添加代码以将专辑数据添加到专辑列表中。在语句后面的某个位置,粘贴以下代码(文件的末尾是一个很好的地方,但Go不强制要求函数声明的顺序。)。 // postAlbums adds an album from JSON received in the request body. func postAlbums(c *gin.Context) { var newAlbum album // Call BindJSON to bind the received JSON to // newAlbum. if err := c.BindJSON(&newAlbum); err != nil { return } // Add the new album to the slice. albums = append(albums, newAlbum) c.IndentedJSON(http.StatusCreated, newAlbum) } 在此代码中,您将: 使用Context.BindJSON将请求体绑定到newAlbum。 将初始化自JSON的专辑结构追加到专辑切片中。 为响应添加201状态码,以及表示您添加的专辑的JSON。
  2. 更改您的主函数,使其包括router.POST函数,如下所示。 func main() { router := gin.Default() router.GET("/albums", getAlbums) router.POST("/albums", postAlbums) router.Run("localhost:8080") } 在此代码中,您将: 将POST方法与/albums路径关联到postAlbums函数。 使用Gin,您可以将处理程序与HTTP方法-和-路径组合关联。通过这种方式,您可以根据客户端正在使用的方法单独路由发送到单个路径的请求。
  3. 将初始化自JSON的专辑结构追加到专辑切片中。
  4. 为响应添加201状态码,以及表示您添加的专辑的JSON。

运行代码

  1. 如果服务器仍在从最后一部分运行,请将其停止。
  2. 从包含main.go的命令行目录运行代码。 $ go run .
  3. 从另一个命令行窗口,使用curl向您的运行中的Web服务发送请求。 $ curl http://localhost:8080/albums \ --include \ --header "Content-Type: application/json" \ --request "POST" \ --data '{"id": "4","title": "The Modern Sound of Betty Carter","artist": "Betty Carter","price": 49.99}' 该命令应显示已添加相册的标头和 JSON。 HTTP/1.1 201 Created Content-Type: application/json; charset=utf-8 Date: Wed, 02 Jun 2021 00:34:12 GMT Content-Length: 116 { "id": "4", "title": "The Modern Sound of Betty Carter", "artist": "Betty Carter", "price": 49.99 }
  4. 与上一节一样,用于检索专辑的完整列表, 您可以使用它来确认已添加新专辑。 curl$ curl http://localhost:8080/albums \ --header "Content-Type: application/json" \ --request "GET" 该命令应显示专辑列表。 [ { "id": "1", "title": "Blue Train", "artist": "John Coltrane", "price": 56.99 }, { "id": "2", "title": "Jeru", "artist": "Gerry Mulligan", "price": 17.99 }, { "id": "3", "title": "Sarah Vaughan and Clifford Brown", "artist": "Sarah Vaughan", "price": 39.99 }, { "id": "4", "title": "The Modern Sound of Betty Carter", "artist": "Betty Carter", "price": 49.99 } ]

在下一部分中,您将添加代码以处理特定项的GET请求。

编写处理程序以返回特定项目

当客户端请求GET /albums/[id]时,您希望返回ID与id路径参数匹配的专辑。

为此,您将:

  • 添加逻辑以检索请求的相册。
  • 将路径映射到逻辑。

编写代码

  1. 在上一部分中添加的postAlbums函数下方粘贴以下代码以检索特定专辑。getAlbumByID函数将提取请求路径中的ID,然后查找与之匹配的专辑。 // getAlbumByID定位ID值与客户发送的id参数相匹配的专辑,然后将该专辑作为响应返回 func getAlbumByID(c *gin.Context) { id := c.Param("id") // 遍历专辑列表,寻找ID值与参数相匹配的专辑。 for _, a := range albums { if a.ID == id { c.IndentedJSON(http.StatusOK, a) return } } c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"}) }
  2. 在此代码中,您将: 使用Context.Param从URL中检索id路径参数。当您将此处理程序映射到路径时,将在路径中包含参数的占位符。 遍历切片中的专辑结构,寻找ID字段值与id参数值匹配的一个。如果找到了,将该专辑结构序列化为JSON并返回它作为带有200 OK HTTP代码的响应。 如上所述,真实的服务可能会使用数据库查询来执行此查找。 如果未找到专辑,返回HTTP 404错误和http.StatusNotFound。
  3. 最后,更改您的main,使其包括一个新的router.GET调用,现在路径是/albums/:id,如下例所示。 func main() { router := gin.Default() router.GET("/albums", getAlbums) router.GET("/albums/:id", getAlbumByID) router.POST("/albums", postAlbums) router.Run("localhost:8080") } 在此代码中,您将:/albums/:id路径与getAlbumByID函数关联。在Gin中,路径中的冒号表示该项是路径参数。

运行代码

  1. 如果服务器仍在运行上一部分的内容,请停止它。
  2. 从包含main.go的命令行目录运行代码以启动服务器。 $ go run .
  3. 从另一个命令行窗口,使用curl向您正在运行的Web服务发送请求 。 curl$ curl http://localhost:8080/albums/2 该命令应显示您使用的ID的专辑的JSON。如果未找到专辑,您将获得带有错误消息的JSON。 { "id": "2", "title": "Jeru", "artist": "Gerry Mulligan", "price": 17.99 }

结论

祝贺!您刚刚使用 Go 和 Gin 编写了一个简单的 RESTful Web 服务。

建议的下一个主题:

  • 如果您是Go新手,您会发现《Effective Go》和《How to write Go code》中描述的最佳实践非常有用。
  • Go Tour是一个逐步介绍Go基础知识的绝佳资源。
  • 要了解更多关于Gin的信息,请参阅Gin Web Framework包文档或Gin Web Framework文档。

已完成的代码

本部分包含您使用本教程构建的应用程序的代码。

package main

import (
"net/http"

"github.com/gin-gonic/gin"
)

// album represents data about a record album.
type album struct {
ID string `json:"id"`
Title string `json:"title"`
Artist string `json:"artist"`
Price float64 `json:"price"`
}

// albums slice to seed record album data.
var albums = []album{
{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
{ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
{ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}

func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.GET("/albums/:id", getAlbumByID)
router.POST("/albums", postAlbums)

router.Run("localhost:8080")
}

// getAlbums responds with the list of all albums as JSON.
func getAlbums(c *gin.Context) {
c.IndentedJSON(http.StatusOK, albums)
}

// postAlbums adds an album from JSON received in the request body.
func postAlbums(c *gin.Context) {
var newAlbum album

// Call BindJSON to bind the received JSON to
// newAlbum.
if err := c.BindJSON(&newAlbum); err != nil {
return
}

// Add the new album to the slice.
albums = append(albums, newAlbum)
c.IndentedJSON(http.StatusCreated, newAlbum)
}

// getAlbumByID locates the album whose ID value matches the id
// parameter sent by the client, then returns that album as a response.
func getAlbumByID(c *gin.Context) {
id := c.Param("id")

// Loop through the list of albums, looking for
// an album whose ID value matches the parameter.
for _, a := range albums {
if a.ID == id {
c.IndentedJSON(http.StatusOK, a)
return
}
}
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
}

原文链接:https://go.dev/doc/tutorial/web-service-gin

#你可能也喜欢这些API文章!