你好😀

在这篇简短的文章中,我将解释如何使用 Golang 编写 REST API 并尝试遵循一些最佳实践。

如果需要,要遵循的源代码是这里。

让我们这样做👍🏻

作为示例,我们将创建一个非常简单的 Post API。

构建项目

让我们跳转到我们的终端并为我们的项目创建一个新目录,然后初始化一个 go 模块。

mkdir postapi
cd postapi
go mod init postapi

进入全屏模式 退出全屏模式

注意: 模块命名的最佳实践是使用<domain>/<nameOfApp>

现在我们有了这个,让我们在我们最喜欢的代码编辑器中打开我们的项目,并创建一个main.go文件,其中包含 main.js 包。这将是我们应用程序的入口点。

接下来,创建一个包含app.go文件的包 app

[屏幕截图 2021-04-17 19.01.50](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

让我们将它添加到我们的结构中

[屏幕截图 2021-04-17 19.13.02](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() 函数,它将负责根据我们的结构返回实际的应用程序。

[屏幕截图 2021-04-17 19.15.15](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文件中,我们可以调用这个函数并创建一个新的应用程序。

[屏幕截图 2021-04-17 19.19.39](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的入口点已经结构化,我们继续路由。

路由

让我们使用我们之前定义的 AppRouter 来实现基本路由。

在我们的app.go文件中,我们将创建一个 InitRoutes 函数,当我们创建应用程序时,它将在我们的 New() 函数内部调用。此函数将成为我们的App 的接收者。实际上,从现在开始,我们的很多方法都将成为我们应用程序的接收者。由于我们的结构 App 中的 Router 字段的类型为gorilla/muxrouter 我们可以访问它的方法。

[屏幕截图 2021-04-17 19.26.38](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”。

[屏幕截图 2021-04-17 20.29.51](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)

现在让我们在路由中调用这个处理程序。

[屏幕截图 2021-04-17 20.32.30](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 路由器。我还做了一个检查功能来打印错误处理帮助。

[屏幕截图 2021-04-17 20.37.45](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

[屏幕截图 2021-04-17 at 21.51.19](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* 的超集。让我们为 sqlxlib/pq(postgres sql 驱动程序)运行一个 go get,然后实现它。

go get -u github.com/jmoiron/sqlx

go get -u github.com/lib/pq

[屏幕截图 2021-04-17 21.13.57](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 🙂

[屏幕截图 2021-04-17 21.17.00](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 数据库的新连接。

[屏幕截图 2021-04-17 21.24.52](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的单独文件中构建此字符串。

[屏幕截图 2021-04-17 21.27.58](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)

[屏幕截图 2021-04-17 21.28.38](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文件中。

[屏幕截图 2021-04-17 23.00.14](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)

[屏幕截图 2021-04-17 21.33.21](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 方法

[屏幕截图 2021-04-17 21.35.26](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文件并初始化与数据库的连接。

[屏幕截图 2021-04-17 21.48.00](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

[屏幕截图 2021-04-17 21.50.25](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 结构。

[屏幕截图 2021-04-17 at 22.02.41](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 结构。

[屏幕截图 2021-04-17 22.06.21](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 结构。

[屏幕截图 2021-04-17 at 22.12.10](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 个方法,GetPostsCreatePost

注意: 我不会仅通过简单的 GET 和 POST 来实现所有 REST 动词端点(尽量保持简短)。

[屏幕截图 2021-04-17 22.17.10](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

[屏幕截图 2021-04-17 22.58.42](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文件并编写我们的方法。

[屏幕截图 2021-04-17 at 22.22.10](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文件中。

[屏幕截图 2021-04-17 at 22.32.16](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)

[屏幕截图 2021-04-17 22.34.00](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 响应。

[屏幕截图 2021-04-17 22.38.57](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)

[屏幕截图 2021-04-17 22.39.33](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) 来将数据映射到该结构。

[屏幕截图 2021-04-17 22.45.26](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,现在我们已经解析了请求正文,我们需要使用该数据创建一个新帖子并将其保存在我们的数据库中。

[屏幕截图 2021-04-17 at 22.56.09](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文件创建一个新路由并调用我们的处理程序。

[屏幕截图 2021-04-17 22.53.01](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上进行测试。

[屏幕截图 2021-04-17 23.04.22](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

[屏幕截图 2021-04-17 23.14.36](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)

[屏幕截图 2021-04-17 23.17.28](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)

重新启动应用程序

[屏幕截图 2021-04-17 23.18.28](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)

失眠测试

[屏幕截图 2021-04-17 23.19.25](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.godb_test.go

[屏幕截图 2021-04-17 23.23.39](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 存储库。

再见! 👋🏻

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐