用Golang写一个简单的REST API
你好😀
在这篇简短的文章中,我将解释如何使用 Golang 编写 REST API 并尝试遵循一些最佳实践。
如果需要,要遵循的源代码是这里。
让我们这样做👍🏻
作为示例,我们将创建一个非常简单的 Post API。
构建项目
让我们跳转到我们的终端并为我们的项目创建一个新目录,然后初始化一个 go 模块。
mkdir postapi
cd postapi
go mod init postapi
进入全屏模式 退出全屏模式
注意: 模块命名的最佳实践是使用<domain>/<nameOfApp>
现在我们有了这个,让我们在我们最喜欢的代码编辑器中打开我们的项目,并创建一个main.go
文件,其中包含 main.js 包。这将是我们应用程序的入口点。
接下来,创建一个包含app.go
文件的包 app
[](https://res.cloudinary.com/practicaldev/image/fetch/s--DnLCG4bz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dpv5efmlpn37hhb19koz.png)
在其中,我们将创建一个 ** App struct** 来表示我们的应用程序的结构。这个结构体上有两个字段,我们的应用程序将有一个 DB 和一个 Router。
路由器将是gorilla/mux路由器,所以让我们继续导入它:
go get -u github.com/gorilla/mux
让我们将它添加到我们的结构中
[](https://res.cloudinary.com/practicaldev/image/fetch/s--J923pJFa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i8n44k0eiffess0qw7kl.png)
现在我们将创建一个 New() 函数,它将负责根据我们的结构返回实际的应用程序。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--uI1fY97S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vzd53ctkxru8l07sbfc6.png)
现在在我们的main.go
文件中,我们可以调用这个函数并创建一个新的应用程序。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--I_p1EilQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jjcsandt5z6ftfck3my7.png)
好了,app的入口点已经结构化,我们继续路由。
路由
让我们使用我们之前定义的 App 的 Router 来实现基本路由。
在我们的app.go
文件中,我们将创建一个 InitRoutes 函数,当我们创建应用程序时,它将在我们的 New() 函数内部调用。此函数将成为我们的App 的接收者。实际上,从现在开始,我们的很多方法都将成为我们应用程序的接收者。由于我们的结构 App 中的 Router 字段的类型为gorilla/muxrouter 我们可以访问它的方法。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--lsQtyQ_b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/35pw6fibi838duf996ih.png)
让我们很快用索引路由的简单处理程序替换 nil。为此,在我们的包应用程序中创建一个handlers.go
文件。这是我们要为我们的路线存储处理程序的地方。我将创建一个 IndexHandler() 处理程序,它将返回一个 http.HandlerFunc 打印响应“Welcome to Post API”。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--s0qDl4SQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8si7n2h7qcshwzz0z2sk.png)
现在让我们在路由中调用这个处理程序。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--XIoYXDzX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/db260nodu9qccnqg6nlp.png)
完美,为了测试这一点,我们需要返回main.go
文件,在我们选择的端口上为应用程序提供服务,并将其重定向到我们的 app 路由器。我还做了一个检查功能来打印错误处理帮助。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--wmUMyl9y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ri0h3g9jp4suxxzv2fz2.png)
好的,现在我们可以使用go run main.go
运行我们的应用程序并 curl 端点以查看是否得到响应。
curl http://localhost:9000
[](https://res.cloudinary.com/practicaldev/image/fetch/s--J82aWNrq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tyawg7ld5cuwy69c9pd3.png)
酷,路由似乎工作!
数据库
是时候设置我们的数据库了,我选择使用 PostgreSQL,为了避免本地设置,我将在 Docker 容器上运行我的数据库。
我将使用以下命令:
docker run --name postapidb --env POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres
进入全屏模式 退出全屏模式
注意: 要停止,您只需运行docker stop postapidb
并将其删除docker rm postapidb
好的,现在我们有一个正在运行的数据库,让我们跳转到代码并在我们的 app 包中创建一个新包 database。
让我们创建一个db.go
文件,在里面我们将有一个接口。我将把它命名为PostDB
,这个接口就像一个合约(实现的方法),如果我们希望我们的数据库成为一个 PostDB 数据库,我们必须尊重它。让我们首先说一个 PostDB 应该实现一个 Open() 和一个可以返回错误的 Close 方法。
我们还将有一个 DB 结构,就像我们的应用程序一样,它将有一个类型为sqlx DB的单个字段 db,就像 Go 标准库中的 database/sql* 的超集。让我们为 sqlx 和 lib/pq(postgres sql 驱动程序)运行一个 go get,然后实现它。
go get -u github.com/jmoiron/sqlx
go get -u github.com/lib/pq
[](https://res.cloudinary.com/practicaldev/image/fetch/s--sHNCKwnp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/31xyjyi2uctc8zvkarxy.png)
现在,在编写我们的 Open() 和 Close() 的主体之前,我们可以在app.go
中向我们的 App 结构中添加一个数据库字段,说明我们在这个应用程序中使用了一个 PostDB 🙂
[](https://res.cloudinary.com/practicaldev/image/fetch/s--V8JijwYU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rki3arnu80ur6f1wtr91.png)
继续,从我们的 Open 方法开始,让我们打开一个到我们的 postgres 数据库的新连接。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--so2KjRhf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zecha3q8ha31s8ha19ce.png)
为了建立这个连接,sqlx.Open() 方法的第二个参数是一个 postgres 连接字符串。让我们在 database 包中名为config.go
的单独文件中构建此字符串。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--U5SLr1un--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w1ka5tankvtdv8y73zfo.png)
[](https://res.cloudinary.com/practicaldev/image/fetch/s--yBY98Mwx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tcvounayf7an21c0w9xh.png)
我想做的另一件事是创建一个将在连接后运行的 SQL 模式。这个模式只会在我们的数据库中创建一个表,以防万一。让我们将其添加到schemas.go
文件中。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--nAI7yUp0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/36q697vxqjz8r3ew1tck.png)
[](https://res.cloudinary.com/practicaldev/image/fetch/s--e_uTzvzW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5a8bis3wvgy0d2b37ibc.png)
我们的 Close 方法会简单很多。我们只需要从 sqlx 调用 Close 方法
[](https://res.cloudinary.com/practicaldev/image/fetch/s--KT436CYZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q4pdx4l29pttdzsewzs8.png)
在为我们的数据编写一些方法之前,让我们跳转到main.go
文件并初始化与数据库的连接。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--cNk0bgvy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/41ddpn562lk7ssqn0ct7.png)
让我们运行go run main.go
[](https://res.cloudinary.com/practicaldev/image/fetch/s--Qpvjtkwx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/da9xd0pj9816sv09zeqq.png)
数据库已配置但尚未完成,因为我想实现一些方法(如 CreatePost 和 GetPosts),但为此我们首先需要一个 Post 模型。
型号
继续在 app 包内创建一个新包 models 并创建一个post.go
文件。在其中,我们将有一个带有 4 个字段的 Post 结构。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--SmAbmFdX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xjzu5byz4g84qsl6au6x.png)
由于这是一个 REST API,我们稍后会将我们的响应映射到 JSON,有时我们数据库中的字段可能与我们的 json 字段不对应,或者我们可能希望灵活地添加新字段或删除字段。
为此,我们已经为我们的文件创建了一个 JsonPost 结构。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--C9cFKW0N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fg7gekqhu019ie3tlua0.png)
我要添加到此文件的最后一件事将在几秒钟内有用。当我们向数据库添加帖子时,ID 会自动递增,这意味着我们在请求创建新帖子时不必传递 ID。因此,让我们为此创建一个 PostRequest 结构。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--8_QqrgDD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ezhr6vq5budo850w7zzd.png)
回到我们的db.go
文件中的数据库,我想在我的 PostDB 接口中添加 2 个方法,GetPosts 和 CreatePost。
注意: 我不会仅通过简单的 GET 和 POST 来实现所有 REST 动词端点(尽量保持简短)。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--E4yrFDfE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/irsd0apuo78dfnzoboeu.png)
在我们的schemas.go
文件中,我想为我的 CreatePost 方法添加一个简单的 insertPostSchema。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--phz8T7f5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jwmr7t2u6k3m6qcpmo25.png)
让我们创建一个methods.go
文件并编写我们的方法。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--T0_YmacU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nwa5ua1bfzn7y0gd6kg2.png)
Http 处理程序
现在我们已经拥有了与数据库交互的所有方法,让我们像之前为 IndexHandler 所做的那样编写我们的 http 处理程序。在我们的handlers.go
文件中,让我们从 CreatePostHandler 开始。
我们要做的是初始化一个空的 PostRequest 结构,解析用户输入到该结构的请求正文。为了解析或映射请求正文,我将编写一个简单命名为 parse 的辅助函数,我将把它放入一个helpers.go
文件中。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--k64uHrsk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rvj2x42fwugw2mtucjiz.png)
[](https://res.cloudinary.com/practicaldev/image/fetch/s--MlBbYZP_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o4i3cwy522duuede0xaq.png)
再说一次,这是一个 REST API,所以最好发送一个 http 状态。我们将发送大量带有 http 状态的响应,因此我将在helpers.go
文件中为此编写另一个帮助函数,用于发送带有状态的 JSON 响应。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--vZN6e0QG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/33tyvig5uj1zjha660li.png)
[](https://res.cloudinary.com/practicaldev/image/fetch/s--37OLtMEy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1m80tw737fytv4s1erd0.png)
顺便说一句,还记得我们有一个 JsonPost 结构以防我们想要那种灵活性吗?让我们添加最后一个辅助函数 (helpers.go
) 来将数据映射到该结构。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--qAkErL3G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/co46g0inerk3xbwnguep.png)
继续CreatePostHandler,现在我们已经解析了请求正文,我们需要使用该数据创建一个新帖子并将其保存在我们的数据库中。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--9swGDPcv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jooeqjim7hrrthxkfcbo.png)
现在要完成 CreatePostHandler,我们只需在 func initRoutes 中从我们的app.go
文件创建一个新路由并调用我们的处理程序。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--EmWdHL7i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mxd0qjsr3v20zvrjqjmh.png)
再次运行go run main.go
,让我们在Insomnia上进行测试。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--slgONTHz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yvhjvwr3z11f1mljchr9.png)
好像有效果👍🏻
在继续测试之前,让我们实现应该更简单的 GetPostHandler。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--og4hfGMN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z5zdtl4uphqh597hys0q.png)
[](https://res.cloudinary.com/practicaldev/image/fetch/s--5-wCdsb7--/c_limit%2Cf_auto%2Cfl_progressive %2Cq_auto% 2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4szi96lewtnvrkf88ac8.png)
重新启动应用程序
[](https://res.cloudinary.com/practicaldev/image/fetch/s--Azrpzxgv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/77rcfy4anh6efupjwtgr.png)
失眠测试
[](https://res.cloudinary.com/practicaldev/image/fetch/s--i2M9-ock--/c_limit%2Cf_auto%2Cfl_progressive %2Cq_auto% 2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bybknrhi2g43v68dza6p.png)
完美,这是有效的🙂
测试
测试我们的代码也是非常好的做法,这就是为什么我们需要尝试为我们的应用程序添加测试覆盖率。为此,您可以创建一个 test 包并为要测试的内容添加一个测试文件,例如handler_test.go
或db_test.go
。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--NZAFxYc2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto %2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iaut5vm2hq4j7j5bc9ux.png)
我实际上制作了一个视频并写了一篇关于在 Golang 中实现测试的文章所以你可以检查一下🙂
结论
而已!我将在这里停止这篇文章,我知道还有改进的余地,但我想保持简短,只谈谈基础。
一如既往地毫不犹豫地给我反馈,今天开发人员的角色是不断改进,这就是我想做的 🙂
不要犹豫,查看我的Youtube Channel,您也可以通过我的twitter 帐户与我联系,当然我会为本文链接github 存储库。
再见! 👋🏻
更多推荐
所有评论(0)