Fiber是一个新的基于 Go 的 Web 框架,它已经爆发并引起了编程社区的极大兴趣。该框架的存储库一直位于 Go 编程语言的 GitHub 趋势页面上,因此,我想我会打开旧的 VS Code 并尝试构建一个简单的 REST API。
因此,在本教程中,我们将介绍如何使用这个新的 Fiber 框架开始在 Go 中构建自己的 REST API 系统!
在本教程结束时,我们将介绍:
- 项目设置
- 为图书管理系统构建 Simle CRUD REST API
- 使用附加包将项目分解为更可扩展的格式。
让我们潜入水中!
为什么是Fiber?
如果您来自另一种语言并尝试开发 Go 应用程序,那么 Fiber 是一个非常容易上手的框架。它为以前使用 Express.js 构建系统的 Node.js 开发人员提供了一种熟悉的感觉。它还建立在它之上,Fasthttp
它是为 Go 构建的令人难以置信的高性能和最小的 HTTP 引擎。
如果我们看一下项目中的快速启动代码,README.md
我们可以看到我们可以多么快速和简单地获得一个基于简单HTTP GET
的端点返回 a Hello, World!
:
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
太棒了,我们现在有了可以开始构建更复杂系统的基础!😎
介绍
让我们从修改快速启动代码并使其更具可扩展性开始:
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
,我们将指针传递给我们的app
. 在这个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
用于更新列表中书籍的端点。
让我们看看我们现在如何开始构建它。
书包
文件中没有足够的介绍性教程main.go
,我过去一直对此感到内疚。因此,让我们打破这个循环并建立一些可以轻松扩展的坚实基础,如果您希望使用本教程中的代码构建更复杂的应用程序。
我们将从在 Go 项目中创建一个新包开始。这将包含我们书籍端点的所有逻辑:
$ mkdir -p book
$ cd book
$ touch book.go
在这个新创建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
函数中,我们可以将端点映射到这些新函数,如下所示:
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)
}
很酷!我们现在已经导入了我们的新book
包并将我们想要的端点映射到这 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 数据库进行交互:
package database
import (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)
var (
DBConn *gorm.DB
)
有了这个,我们将要更新我们的文件,通过创建一个新函数main.go
来打开到这个 sqlite 数据库的连接。initDatabase()
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
代码,以便我们定义一个struct
用于创建数据库表的 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)
}
使用c.JSON
Fiber 提供给我们的方法,我们可以快速轻松地将 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
传入我们想要基于以下内容生成表的结构:
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 请求数据
我想在本教程中介绍的最后一件事是读取传入请求的正文并将其解析为 a book struct
,以便我们可以将自定义数据填充到我们的数据库中。
值得庆幸的是,该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!🔥