Fiber 是一个新的基于 Go 的 Web 框架,它已经爆发并引起了编程社区的极大兴趣。 该框架的存储库一直位于 Go 编程语言的 GitHub 趋势页面上,因此,我想我会打开旧的 VS Code 并尝试构建一个简单的 REST API。
因此,在本教程中,我们将介绍如何使用这个新的 Fiber 框架开始在 Go 中构建自己的 REST API 系统!
在本教程结束时,我们将介绍:
- 项目设置
- 为图书管理系统构建 Simle CRUD REST API
- 使用附加包将项目分解为更可扩展的格式。
让我们潜入!
视频教程
本教程还提供视频格式:
https://youtu.be/Iq2qT0fRhAA
为什么是fiber?
如果您来自另一种语言并尝试开发 Go 应用程序,那么 Fiber 是一个非常容易上手的框架。它为以前使用 Express.js 构建系统的 Node.js 开发人员提供了一种熟悉的感觉。它还建立在 Fasthttp 之上,这是一个为 Go 构建的令人难以置信的高性能和最小的 HTTP 引擎。
如果我们查看项目 README.md 中的快速启动代码,我们可以看到我们可以多么快速和简单地获得一个简单的基于 HTTP GET 的端点返回一个 Hello, World!:
main.go
package main
import "github.com/gofiber/fiber"
func main() {
app := fiber.New()
app.Get("/", func(c *fiber.Ctx) {
c.Send("Hello, World!")
})
app.Listen(3000)
}
然后我们可以运行它并在 http://localhost:3000 上启动我们的服务器,首先使用 go mod init 初始化我们的项目,然后运行 go run main.go,它将在启动服务器之前下载所有 Fiber 的依赖项:
$ go mod init github.com/tutorialedge/go-fiber-tutorial
$ go run main.go
Fiber v1.9.1 listening on :3000
太棒了,我们现在有了可以开始构建更复杂系统的基础! 😎
介绍
让我们从修改快速启动代码并使其更具可扩展性开始:
main.go
package main
import (
"github.com/gofiber/fiber"
)
func helloWorld(c *fiber.Ctx) {
c.Send("Hello, World!")
}
func setupRoutes(app *fiber.App) {
app.Get("/", helloWorld)
}
func main() {
app := fiber.New()
setupRoutes(app)
app.Listen(3000)
}
让我们分解一下我们在这里所做的事情。
- 我们创建了一个名为 setupRoutes 的新函数,我们将指针传递给我们的应用程序。在这个 setupRoutes 函数中,我们将端点映射到命名函数。此更改允许我们将路由逻辑从应用程序初始化逻辑中移出,如果我们要编写更复杂的应用程序,这很重要。
- 我们已经创建了命名函数 helloWorld,我们现在已经将 / 端点映射到该函数。这种变化允许我们编写更复杂的端点函数。
构建我们的 REST API 端点
所以,有了这些新的变化,现在让我们看看扩展我们的应用程序的功能并创建一些额外的端点,我们可以从中服务请求。我们将构建一个图书管理系统,该系统将在内存中存储我们在疫情封锁期间一直在阅读的图书!
我们将要创建以下端点:
- /api/v1/book - 一个 HTTP GET 端点,它将返回您在锁定期间阅读的所有书籍。
- /api/v1/book/:id - 一个 HTTP GET 端点,它接受书籍 ID 的路径参数并只返回一本单独的书
- /api/v1/book - 一个 HTTP POST 端点,允许我们将新书添加到列表中
- /api/v1/book/:id - 一个 HTTP DELETE 端点,它允许我们从列表中删除一本书,以防我们错误地添加任何书籍?
挑战 - 添加 HTTP PUT 端点以更新列表中的书籍。
让我们看看我们现在如何开始构建它。
Book Package
main.go 文件中没有足够的介绍性教程,我过去一直对此感到内疚。因此,让我们打破这个循环并建立一些可以轻松扩展的坚实基础,如果您希望使用本教程中的代码构建更复杂的应用程序。
我们将从在 Go 项目中创建一个新包开始。这将包含我们书籍端点的所有逻辑:
$ mkdir -p book
$ cd book
$ touch book.go
在这个新创建的 book.go 文件中,让我们开始为我们将映射到上述端点的函数定义存根:
book/book.go
package book
import (
"github.com/gofiber/fiber"
)
func GetBooks(c *fiber.Ctx) {
c.Send("All Books")
}
func GetBook(c *fiber.Ctx) {
c.Send("Single Book")
}
func NewBook(c *fiber.Ctx) {
c.Send("New Book")
}
func DeleteBook(c *fiber.Ctx) {
c.Send("Delete Book")
}
有了这个,我们就可以返回到 main.go 文件,在我们的 setupRoutes 函数中,我们可以将端点映射到这些新函数,如下所示:
main.go
package main
import (
"github.com/elliotforbes/go-fiber-tutorial/book"
"github.com/gofiber/fiber"
)
func helloWorld(c *fiber.Ctx) {
c.Send("Hello, World!")
}
func setupRoutes(app *fiber.App) {
app.Get("/", helloWorld)
app.Get("/api/v1/book", book.GetBooks)
app.Get("/api/v1/book/:id", book.GetBook)
app.Post("/api/v1/book", book.NewBook)
app.Delete("/api/v1/book/:id", book.DeleteBook)
}
func main() {
app := fiber.New()
setupRoutes(app)
app.Listen(3000)
}
很酷!我们现在已经导入了我们的新书包,并将我们想要的端点映射到这 4 个新函数。
让我们尝试使用一些 curl 命令来访问这些端点,看看它们是否以我们期望的方式响应:
$ curl http://localhost:3000/api/v1/book
All Books
$ curl http://localhost:3000/api/v1/book/1
Single Book
$ curl -X POST http://localhost:3000/api/v1/book
New Book
$ curl -X DELETE http://localhost:3000/api/v1/book/1
Delete Book
太棒了,所有 4 个端点都为各自的 HTTP 请求返回了正确的响应!
添加数据库
现在我们已经定义了各自的端点并按预期工作,让我们看看设置一个简单的数据库,我们将使用 gorm 与之交互,这简化了我们与数据库对话的生活!
在项目目录的根目录中,运行以下命令以创建一个名为 database/ 的新文件夹和一个名为 database.go 的新文件:
$ mkdir -p database
$ cd database
$ touch database.go
在这个新的 database.go 文件中,我们将要定义一个全局 DBConn 变量,该变量将是一个指向数据库连接的指针,我们的端点将使用它与本地 sqlite 数据库进行交互:
database/database.go
package database
import (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)
var (
DBConn *gorm.DB
)
有了这个,我们需要更新我们的 main.go 文件,通过创建一个新的 initDatabase() 函数来打开到这个 sqlite 数据库的连接。
main.go
package main
import (
"fmt"
"github.com/elliotforbes/go-fiber-tutorial/database"
"github.com/gofiber/fiber"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)
func setupRoutes(app *fiber.App) {
app.Get("/api/v1/book", book.GetBooks)
app.Get("/api/v1/book/:id", book.GetBook)
app.Post("/api/v1/book", book.NewBook)
app.Delete("/api/v1/book/:id", book.DeleteBook)
}
func initDatabase() {
var err error
database.DBConn, err = gorm.Open("sqlite3", "books.db")
if err != nil {
panic("failed to connect database")
}
fmt.Println("Connection Opened to Database")
}
func main() {
app := fiber.New()
initDatabase()
setupRoutes(app)
app.Listen(3000)
defer database.DBConn.Close()
}
接下来,我们必须更新 book/book.go 代码,以便我们定义一个 Book 结构,我们将使用它来创建数据库表。
package book
import (
"fmt"
"github.com/elliotforbes/go-fiber-tutorial/database"
"github.com/gofiber/fiber"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)
type Book struct {
gorm.Model
Title string `json:"name"`
Author string `json:"author"`
Rating int `json:"rating"`
}
更新我们的端点
接下来,我们需要更新映射到每个端点的函数。让我们从更新 GetBooks 以返回所有书籍开始:
func GetBooks(c *fiber.Ctx) {
db := database.DBConn
var books []Book
db.Find(&books)
c.JSON(books)
}
使用 Fiber 提供给我们的 c.JSON 方法,我们可以快速轻松地将 books 数组序列化为 JSON 字符串并在响应中返回!
接下来让我们更新我们的单书端点:
func GetBook(c *fiber.Ctx) {
id := c.Params("id")
db := database.DBConn
var book Book
db.Find(&book, id)
c.JSON(book)
}
在这里,我们使用了 c.Params("id") 函数来检索表示我们要检索的书的 ID 的路径参数。我们可以再次使用 c.JSON 函数返回这本书。
注意 - 我没有费心为这个特定的端点添加错误处理,它总是假设这本书存在。我将把它留给读者来处理这个案例。
添加和删除书籍
到目前为止,我们刚刚处理了从数据库中检索书籍,让我们看看如何通过更新 NewBook 和 DeleteBook 函数来开始添加和删除书籍。
在 NewBook 函数中,让我们对我们现在要填充的书进行硬编码,以便我们可以增量测试我们的 API。这将调用 db.Create 以便为我们将新书推送到数据库中,然后我们将返回该书的 JSON:
func NewBook(c *fiber.Ctx) {
db := database.DBConn
var book Book
book.Title = "1984"
book.Author = "George Orwell"
book.Rating = 5
db.Create(&book)
c.JSON(book)
}
完美,现在终于让我们更新 DeleteBook 功能。在这里,我们将实际执行一些错误处理并检查该书是否首先存在于数据库中,然后再尝试删除该书并返回一条确认删除的简单消息:
func DeleteBook(c *fiber.Ctx) {
id := c.Params("id")
db := database.DBConn
var book Book
db.First(&book, id)
if book.Title == "" {
c.Status(500).Send("No Book Found with ID")
return
}
db.Delete(&book)
c.Send("Book Successfully deleted")
}
迁移我们的数据库
幸运的是,Gorm 为我们处理了表的创建和任何更新,因此设置所有这些的复杂性是最小的。我们需要添加对 AutoMigrate 的调用,传入我们想要基于以下内容生成表的结构:
main.go
func initDatabase() {
var err error
database.DBConn, err = gorm.Open("sqlite3", "books.db")
if err != nil {
panic("failed to connect database")
}
fmt.Println("Connection Opened to Database")
database.DBConn.AutoMigrate(&book.Book{})
fmt.Println("Database Migrated")
}
当我们下次启动 API 时,它会在我们的 sqlite 数据库中自动为我们生成表。
测试我们的端点:
现在我们已经定义了端点并与数据库通信,下一步是手动测试它们以验证它们是否按预期工作:
$ curl http://localhost:3000/api/v1/book
[{"ID":3,"CreatedAt":"2020-04-24T09:20:37.622829+01:00","UpdatedAt":"2020-04-24T09:20:37.622829+01:00","DeletedAt":null,"name":"1984","author":"George Orwell","rating":5},{"ID":4,"CreatedAt":"2020-04-24T09:29:47.573672+01:00","UpdatedAt":"2020-04-24T09:29:47.573672+01:00","DeletedAt":null,"name":"1984","author":"George Orwell","rating":5}]
$ curl http://localhost:3000/api/v1/book/1
{"ID":3,"CreatedAt":"2020-04-24T09:20:37.622829+01:00","UpdatedAt":"2020-04-24T09:20:37.622829+01:00","DeletedAt":null,"name":"1984","author":"George Orwell","rating":5}
$ curl -X POST http://localhost:3000/api/v1/book
{"ID":5,"CreatedAt":"2020-04-24T09:49:16.405426+01:00","UpdatedAt":"2020-04-24T09:49:16.405426+01:00","DeletedAt":null,"name":"1984","author":"George Orwell","rating":5}
$ curl -X DELETE http://localhost:3000/api/v1/book/1
Book Successfully Deleted
所有这些都按照我们的预期工作了!我们现在有一个主要功能的 REST API,我们可以与之交互并在其上抛出一个前端!
读取 JSON 请求数据
我想在本教程中介绍的最后一件事是读取传入请求的主体并将其解析为 book 结构,以便我们可以将自定义数据填充到我们的数据库中。
值得庆幸的是,fiber 框架具有一个非常方便的 BodyParser 方法,它可以读取请求正文,然后为我们填充一个结构,如下所示:
func NewBook(c *fiber.Ctx) {
db := database.DBConn
book := new(Book)
if err := c.BodyParser(book); err != nil {
c.Status(503).Send(err)
return
}
db.Create(&book)
c.JSON(book)
}
有了这个新的变化,让我们重新运行我们的 API 并使用 curl 命令发送一个 HTTP POST 请求,我们将在一本新书中传递该命令:
$ curl -X POST -H "Content-Type: application/json" --data "{\"title\": \"Angels and Demons\", \"author\": \"Dan Brown\", \"rating\": 4}" http://localhost:3000/api/v1/book
{"ID":6,"CreatedAt":"2020-04-24T10:50:52.658811+01:00","UpdatedAt":"2020-04-24T10:50:52.658811+01:00","DeletedAt":null,"title":"Angels and Demons","author":"Dan Brown","rating":4}
一切都按预期工作!通过我们提供的信息,我们可以看到新书正在为我们添加到数据库中!
结论
🔥 太棒了,所以在本教程中,我们设法使用 Fiber 框架为 Go 中的图书管理系统构建了一个非常简单的 REST API!
我希望这对您有所帮助,并且您喜欢本教程! 😄 如果您喜欢它或有任何其他问题或意见,请在下面的评论部分告诉我!
- 登录 发表评论