如何使用 Mux、Go、PostgreSQL 和 GORM 构建 API
我看到很多关于 Go 速度的讨论。根据Benchmark Game 的测试,Go 比 Node 快得多,比 Java 稍快一些,甚至比 Python 和 Ruby 还要快。尽管性能如此,Go 仍然拥有相对不错的开发者体验。分号是隐式的,部分类型是推断出来的,而且非面向对象的特性使其更加灵活。这还没算上它内置的并发功能!我决定用 Go 构建一些东西,看看它未来是否能成为我应用程序的可行编程语言。
设置
我首先需要将 Go 下载到我的电脑上。通过 Go 官网下载在我的电脑上无法使用——它一直挂起并冻结。我最终尝试通过 Homebrew 安装它。安装 Go 时,还需要$GOPATH
在电脑上设置一个目录,用于声明创建 Go 项目的工作区。我遇到了一些问题,导致无法正常工作。最终,我添加了以下内容,.zshrc
最终成功了。
export GOPATH=$HOME/go
export GOROOT=/usr/local/opt/go/libexec
export PATH=$PATH:$GOPATH/bin
export PATH=$PATH:$GOROOT/bin
然后,我按照Golang 网站上的“如何编写 Go 代码”$HOME/go/src/github.com/user/rest-api
教程中的建议编写了我的代码。
入门
我一开始使用的是 Go 网站上的“尝试 Go”教程。这是一个很好的入门教程,而且我喜欢它的互动性。在简单的介绍之后,我感觉 Go 已经相当熟练了。对我来说,Go 就像是 C++、Python 和 JavaScript 的混合体。它不像其他语言那样让我感到陌生!
最终项目
我感觉很适合继续学习更高级的概念——这次我想构建一个 API。过去一年左右,我一直非常倾向于微服务应用,由于我现在专注于 Web 应用,所以我想构建一些基于 Web 的应用。我很难追踪那些优秀的编程文章并发送给别人,所以我想构建一个工具,让人们可以追踪并分享他们发现的优秀文章。
我承认,在阅读了工具文档后,我就直接进入了最终项目,而不是像平常一样学习教程——Go 对我来说感觉很舒服,尽管我确信专家会为我提供很多改进!
我最初使用了一个只有几个项目的硬编码 API——类似于Francis Sunday 的精彩教程。通过那篇文章,我发现了 Gorilla Mux,它可以辅助 Go 中的路由功能。Go 语言内置了服务器,因此我无需为该功能添加太多代码。
然后我想添加一个数据库。我几乎所有东西都用 PostgreSQL。我非常依赖它的 JSON 和数组字段,但我更喜欢关系型数据库。我也更喜欢在我的应用中使用 ORM,因为它们通常能让查询在语法上更优雅。我发现了 GORM,用起来非常棒。它没有内置 Postgres 的所有功能,但我发现只需使用“pq”Go 包就能轻松实现我想要的功能。
由于在 Go 中没有大量关于使用此堆栈创建 API 的资源,因此我想比平时更多地了解一下我的代码。
导入依赖项后,我定义了一个struct
。结构体是“字段的集合”。虽然 Go 不是面向对象的,但对我来说,结构体有点像类。你定义一个蓝图,然后在代码中创建它的实例。我希望我的 API 中包含几个字段:资源的链接、名称、作者、描述以及与之关联的标签。GORM 和 Postgres 在输出端也添加了created_at
、updated_at
、deleted_at
和id
字段。唯一比较棘手的字段是标签——我最终使用了 pq 中的 StringArray,因为据我所知,它并没有内置在 GORM 中。
type Resource struct {
gorm.Model
Link string
Name string
Author string
Description string
Tags pq.StringArray `gorm:"type:varchar(64)[]"`
}
接下来,我编写了我的main
函数。这个函数会在程序运行时自动运行,并启动程序中的其他操作。我首先创建了 Mux 路由器,这将简化应用程序中的 URL 路由。然后,我为数据库连接设置了一个全局变量,以便我可以在整个应用程序中使用它。我还做了一些错误处理,以防无法连接到数据库。我曾经os.Getenv
与文件中设置的环境变量进行交互.env
。这也让我最终能够非常轻松地部署我的应用程序!我还使用 GORM 迁移了我的数据库,这样无论我使用哪个数据库,启动应用程序时,架构都是正确的。
然后,我为我的应用实现了路由。我最初只想创建四个路由——GET all、GET one、POST 和 DELETE。我可能还会添加一个用于更新的 PUT 路由。我喜欢 Mux 提供的路由,它简洁明了。
最后,我在函数的最后一行启动了服务器main
——它只指定了要使用的端口和路由器。它还指定在关闭服务器之前记录错误。
var db *gorm.DB
var err error
func main() {
router := mux.NewRouter()
db, err = gorm.Open(
"postgres",
"host="+os.Getenv("HOST")+" user="+os.Getenv("USER")+
" dbname="+os.Getenv("DBNAME")+" sslmode=disable password="+
os.Getenv("PASSWORD"))
if err != nil {
panic("failed to connect database")
}
defer db.Close()
db.AutoMigrate(&Resource{})
router.HandleFunc("/resources", GetResources).Methods("GET")
router.HandleFunc("/resources/{id}", GetResource).Methods("GET")
router.HandleFunc("/resources", CreateResource).Methods("POST")
router.HandleFunc("/resources/{id}", DeleteResource).Methods("DELETE")
log.Fatal(http.ListenAndServe(":"+os.Getenv("PORT"), router))
}
然后,我编写了路由处理函数。每个函数都接收 HTTP 响应和请求作为参数——类似于许多 Web 框架。首先,我编写了获取所有资源的路由。我首先创建一个资源数组,然后查询数据库中的所有资源,并将结果设置为资源数组。然后,我发送响应,该响应是一个包含资源的 JSON 格式的数据。
func GetResources(w http.ResponseWriter, r *http.Request) {
var resources []Resource
db.Find(&resources)
json.NewEncoder(w).Encode(&resources)
}
获取一个资源的路线是类似的——唯一的区别是首先必须检索请求参数以用于查询一个资源。
func GetResource(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
var resource Resource
db.First(&resource, params["id"])
json.NewEncoder(w).Encode(&resource)
}
创建函数与之前的路由非常相似。这背后有一个有趣的故事——我一度搞不清楚为什么这个函数无法正常工作——虽然没有报错,但当我用 Postman 测试它时,字段都填成了空白。后来我换用了 CURl,它就完全正常工作了!其实并没有 bug,只是我使用接口的能力下降了!
func CreateResource(w http.ResponseWriter, r *http.Request) {
var resource Resource
json.NewDecoder(r.Body).Decode(&resource)
db.Create(&resource)
json.NewEncoder(w).Encode(&resource)
}
最后,我创建了删除路由。这个路由和之前的类似,只是在删除指定的资源后返回了所有资源。
func DeleteResource(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
var resource Resource
db.First(&resource, params["id"])
db.Delete(&resource)
var resources []Resource
db.Find(&resources)
json.NewEncoder(w).Encode(&resources)
}
在开发过程中,每写一段新代码,我都会运行一下go run
代码来检查 API。我并没有遇到太多问题——如果代码编译不通过,错误信息也很清晰,切中要点。我几乎没有注意到这个额外的步骤。最后,我编译了我的应用,之后就可以在电脑上运行可执行文件了。由于我最终用了不少库,所以go install
我也用它来进行依赖管理!godep
我所有代码都高度依赖 GORM 文档!它太棒了——示例清晰,通俗易懂!我强烈推荐我在这个应用中用到的所有库。
代码编写完成后,我还运行了内置的 linter gofmt
。我最初编写的格式与 JavaScript 代码类似,但 linter 清理了代码。我确实喜欢额外的空格,但我也很高兴我的代码能够如此轻松地更好地适应 Go 代码风格指南!
我觉得代码非常直观易读!我用 Go 语言做这个项目非常开心。最终项目已上传至GitHub,并已在线部署。
部署
考虑到我之前没有基于 Web 的编译语言开发经验,不知为何,我对部署这个应用感到非常紧张。事实上,我只用编译语言为学校项目写过代码!最终我按照Heroku 网站上部署 Go 应用的步骤操作。虽然我不得不更改环境变量的存储方式,但除此之外,这些步骤都相当有效!毕竟我不需要做任何疯狂的事情!
后续步骤
我非常喜欢用 Go 写代码。我肯定会再次在项目中使用它,尤其是在性能对项目至关重要的情况下。我也很乐意为这个项目增添新的功能。我可能会在 API 端添加标签过滤、授权和更新路由。我还可能会为这个应用添加一个前端,以便更轻松地与 API 交互。这个项目非常有趣,总而言之,我只花了大约四个小时就从电脑上没有 Go 到最终成品!我发现它的语法易于理解和实现,我甚至不介意处理指针、静态类型或编译!我完全赞同 Go,我会优先使用它,而不是我过去用过的很多语言!
我的“学习新事物”系列的一部分
文章来源:https://dev.to/aspittel/how-i-built-an-api-with-mux-go-postgresql-and-gorm-5ah8