欢迎来到我们的Go教程系列,我们将介绍实用的概念和如何编写一个Go Web服务,涵盖了REST API、数据库、授权和部署等方面。
在本篇文章中,我们将重点关注使用Gin框架(https://github.com/gin-gonic/gin)创建API的主题。
本教程假设你具有一些基本的Go编程知识(例如语法和基本概念)。如果你不知道Go是什么或如何编写基本的Go编程,请先从这里开始学习(https://go.dev/tour),或者你可以从Skooldio的课程中学习(https://www.skooldio.com/courses/go-the-fundamentals)。
先决条件
- 安装Go(了解如何安装Go,请参阅此处https://go.dev/doc/install)
项目设置
- 创建项目目录
mkdir go-rest-api
- 初始化Go模块
go mod init example/go-rest-api
- 创建
main.go
文件并编写基本的Hello World代码
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello World!")
}
- 在命令行中运行代码
go run main.go
- 你应该在命令行中看到输出
Hello World!
使用Gin创建基本API
- 从实现基本API开始。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.New()
r.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Hello World!",
})
})
r.Run()
}
首先,我们使用 gin.New()
创建路由器,然后使用 r.GET
方法注册路由 /
并指定我们的请求处理函数,最后使用 r.Run()
运行路由器。
在我们的处理函数中,我们使用 c.JSON
方法将响应写回客户端,以返回我们的JSON {"message": "Hello World!"}
。
- 使用以下命令安装依赖项。
go get
go mod tidy
- 运行代码以启动API服务器。
go run main.go
- 通过在浏览器中输入URL
http://localhost:8080
来测试API,你应该会看到结果。
实现REST API
1. 创建数据结构和模拟数据
添加 Book Struct
和一个用于存储模拟API数据的 Slice
。
type Book struct {
ID string `json:"id"`
Title string `json:"title"`
Author string `json:"author"`
}
var books = []Book{
{ID: "1", Title: "Harry Potter", Author: "J. K. Rowling"},
{ID: "2", Title: "The Lord of the Rings", Author: "J. R. R. Tolkien"},
{ID: "3", Title: "The Wizard of Oz", Author: "L. Frank Baum"},
}
请注意,在 Book Struct
中,每个字段都有一个标记 json:"xxx"
遵循字段声明。这是为了JSON序列化API,使服务知道JSON中的哪个字段与Struct中的哪个字段相对应。
2. 实现GET端点以列出所有图书。
实现如下所示的 GET /books
端点。
r.GET("/books", func(c *gin.Context) {
c.JSON(http.StatusOK, books)
})
请注意,我们可以简单地传递我们创建的 Slice
以返回响应的图书列表。
现在,完整的 main.go
文件看起来像这样。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type Book struct {
ID string `json:"id"`
Title string `json:"title"`
Author string `json:"author"`
}
var books = []Book{
{ID: "1", Title: "Harry Potter", Author: "J. K. Rowling"},
{ID: "2", Title: "The Lord of the Rings", Author: "J. R. R. Tolkien"},
{ID: "3", Title: "The Wizard of Oz", Author: "L. Frank Baum"},
}
func main() {
r := gin.New()
r.GET("/books", func(c *gin.Context) {
c.JSON(http.StatusOK, books)
})
r.Run()
}
运行代码以启动API服务器。
go run main.go
通过在浏览器中输入URL http://localhost:8080/books
来测试API,你应该会看到我们的图书列表。
3. 实现POST端点以创建图书。
然后实现 POST /albums
。
r.POST("/books", func(c *gin.Context) {
var book Book
if err := c.ShouldBindJSON(&book); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
books = append(books, book)
c.JSON(http.StatusCreated, book)
})
此处理程序需要接收请求体以创建新的图书,我们可以使用 c.ShouldBindJSON
方法并将指针 &book
传递给它,以将数据体绑定到我们的结构中。
还要注意,c.ShouldBindJSON
将验证请求体并在存在任何验证问题时返回错误。我们将处理它以返回 400 Bad Request
状态和我们的错误消息。
之后,我们将新的 Book Struct
添加到 Slice
中,然后返回新创建的 Book Struct
。
现在,完整的 main.go
文件看起来像这样。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type Book struct {
ID string `json:"id"`
Title string `json:"title"`
Author string `json:"author"`
}
var books = []Book{
{ID: "1", Title: "Harry Potter", Author: "J. K. Rowling"},
{ID: "2", Title: "The Lord of the Rings", Author: "J. R. R. Tolkien"},
{ID: "3", Title: "The Wizard of Oz", Author: "L. Frank Baum"},
}
func main() {
r := gin.New()
r.GET("/books", func(c *gin.Context) {
c.JSON(http.StatusOK, books)
})
r.POST("/books", func(c *gin.Context) {
var book Book
if err := c.ShouldBindJSON(&book); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
books = append(books, book)
c.JSON(http.StatusCreated, book)
})
r.Run()
}
view raw
现在,我们将测试 post
端点,但是在浏览器中输入URL将是 get
请求。因此,我们需要另一个工具来测试 post
请求。
有许多替代工具可用,但我将使用名为 REST Client 的VSCode扩展。
然后,你可以创建一个类似于以下内容的 test.http
文件。
现在,你可以发送 post
请求,结果如下所示。
我们再次使用扩展程序测试 http://localhost:8080/books
的 get
,你应该会看到新创建的图书。
请注意,创建的图书将不是持久的,这意味着如果我们重新启动API服务并再次运行 go run main.go
,则创建的图书将丢失。(我们将在本教程系列的下一篇文章中讨论如何持久化数据)。
4. 实现DELETE端点
最后一个端点是DELETE /albums/:id
。
r.DELETE("/books/:id", func(c *gin.Context) {
id := c.Param("id")
for i, a := range books {
if a.ID == id {
books = append(books[:i], books[i+1:]...)
break
}
}
c.Status(http.StatusNoContent)
})
这次,我们必须从请求的URL中获取路径参数,我们可以使用路径中的 :id
并使用 c.Param("id")``` 方法在处理程序中获取它。然后我们实现了根据书籍id从切片中删除书籍的逻辑,然后向客户端返回
204 No Content` http状态。
完整的main.go
文件现在如下所示。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type Book struct {
ID string `json:"id"`
Title string `json:"title"`
Author string `json:"author"`
}
var books = []Book{
{ID: "1", Title: "Harry Potter", Author: "J. K. Rowling"},
{ID: "2", Title: "The Lord of the Rings", Author: "J. R. R. Tolkien"},
{ID: "3", Title: "The Wizard of Oz", Author: "L. Frank Baum"},
}
func main() {
r := gin.New()
r.GET("/books", func(c *gin.Context) {
c.JSON(http.StatusOK, books)
})
r.POST("/books", func(c *gin.Context) {
var book Book
if err := c.ShouldBindJSON(&book); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
books = append(books, book)
c.JSON(http.StatusCreated, book)
})
r.DELETE("/books/:id", func(c *gin.Context) {
id := c.Param("id")
for i, a := range books {
if a.ID == id {
books = append(books[:i], books[i+1:]...)
break
}
}
c.Status(http.StatusNoContent)
})
r.Run()
}
另外,我们将通过向test.http
文件添加删除请求来测试我们的API的结果。
首先运行GET端点以查看我们的书籍列表
然后运行DELETE端点
然后再次运行GET端点以检查结果,我们将看到第二本书已从我们的书籍列表中删除。
重构代码
为了使代码更易读,我们将把处理程序函数提取到它自己的函数中。
r.GET("/books", listBooksHandler)
r.POST("/books", createBookHandler)
r.DELETE("/books/:id", deleteBookHandler)
func listBooksHandler(c *gin.Context) {
c.JSON(http.StatusOK, books)
}
func createBookHandler(c *gin.Context) {
var book Book
if err := c.ShouldBindJSON(&book); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
books = append(books, book)
c.JSON(http.StatusCreated, book)
}
func deleteBookHandler(c *gin.Context) {
id := c.Param("id")
for i, a := range books {
if a.ID == id {
books = append(books[:i], books[i+1:]...)
break
}
}
c.Status(http.StatusNoContent)
}
并将Book结构体和切片移到主函数下。
func main() {
r := gin.New()
r.GET("/books", listBooksHandler)
r.POST("/books", createBookHandler)
r.DELETE("/books/:id", deleteBookHandler)
r.Run()
}
type Book struct {
ID string `json:"id"`
Title string `json:"title"`
Author string `json:"author"`
}
var books = []Book{
{ID: "1", Title: "Harry Potter", Author: "J. K. Rowling"},
{ID: "2", Title: "The Lord of the Rings", Author: "J. R. R. Tolkien"},
{ID: "3", Title: "The Wizard of Oz", Author: "L. Frank Baum"},
}
完成了!我们使用Gin框架创建了一个简单的API服务。
最终代码
最终的main.go
文件如下所示。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.New()
r.GET("/books", listBooksHandler)
r.POST("/books", createBookHandler)
r.DELETE("/books/:id", deleteBookHandler)
r.Run()
}
type Book struct {
ID string `json:"id"`
Title string `json:"title"`
Author string `json:"author"`
}
var books = []Book{
{ID: "1", Title: "Harry Potter", Author: "J. K. Rowling"},
{ID: "2", Title: "The Lord of the Rings", Author: "J. R. R. Tolkien"},
{ID: "3", Title: "The Wizard of Oz", Author: "L. Frank Baum"},
}
func listBooksHandler(c *gin.Context) {
c.JSON(http.StatusOK, books)
}
func createBookHandler(c *gin.Context) {
var book Book
if err := c.ShouldBindJSON(&book); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
books = append(books, book)
c.JSON(http.StatusCreated, book)
}
func deleteBookHandler(c *gin.Context) {
id := c.Param("id")
for i, a := range books {
if a.ID == id {
books = append(books[:i], books[i+1:]...)
break
}
}
c.Status(http.StatusNoContent)
}
译自:https://medium.com/@wattanai.tha/go-tutorial-series-ep-1-building-rest-api-with-gin-7c17c7ab1d5b
评论(0)