如果您正在编写任何形式的 Web 应用程序,那么您很可能会与 1 个或多个 REST API 交互,以填充应用程序的动态部分并执行诸如更新或删除数据库中的数据之类的任务。
在本教程中,你将要建立一个完全成熟的REST API 暴露GET,POST,DELETE和PUT端点随后将允许你进行全方位的CRUD操作。
为了保持简单并专注于基本概念,我们不会与任何后端数据库技术交互来存储我们将使用的文章。但是,我们将以这样一种方式编写此书 REST API,以便可以轻松更新我们将定义的函数,以便它们对数据库进行后续调用以执行任何必要的CRUD操作。
如果您想了解有关如何使用 Go 与数据库交互的更多信息,可以查看以下文章:
- 转到 MySQL 教程
- 去 ORM 教程
源代码- 本文的完整源代码可以在这里找到: TutorialEdge/create-rest-api-in-go-tutorial
先决条件
- 您需要在开发机器上安装 Go 1.11+ 版本。
目标
在本教程结束时,您将知道如何在 Go 中创建自己的 REST-ful API,以处理所有方面的问题。你会知道如何创建您的项目中REST端点,可以处理POST,GET,PUT和 DELETEHTTP请求。
视频教程
REST 架构
如今,REST 无处不在,从网站到企业应用程序,RESTful 架构风格是一种在独立软件组件之间提供通信的强大方式。构建 REST API 可让您轻松分离消费者和生产者,并且通常设计为无状态。
注 -如果您想了解有关 REST API 基础知识的更多信息,请查看什么是 RESTful API?
JSON
出于本教程的目的,我将使用 JavaScript 对象表示法作为发送和接收所有信息的方式,幸运的是 Go 为使用标准库包 encoding/json 对这些格式的编码和解码提供了一些出色的支持。
注意 -有关 encoding/json 您的更多信息,请查看官方文档: encoding/json
编组
为了让我们更容易我们可以通过使用称为编组的东西轻松地将 GO 将数据结构转换为 JSON,它会生成一个字节切片,其中包含一个非常长的字符串,没有多余的空白。
基本 API 入门
首先,我们必须创建一个可以处理 HTTP 请求的是非常简单的服务器。为此,我们将创建一个名为main.go. 在这个 main.go文件中,我们需要定义 3 个不同的函数。一个homePage函数将处理对我们的根 URL 的所有请求,一个handleRequests将 URL 路径匹配与定义的函数匹配的函数以及一个main将启动我们的 API 的函数。
main.go
package main import ( "fmt" "log" "net/http" ) func homePage(w , r *){ (w, "Welcome to the HomePage!") ("Endpoint Hit: homePage") } func handleRequests() { ("/", homePage) log.Fatal(":10000", nil)) } func main() { handleRequests() }
如果我们现在在我们的机器上运行它,我们应该看到我们非常简单地 API 在端口 10000 上次启动,如果它没有被另一个进程占用。如果我们现在http://localhost:10000/在本地浏览器中导航到,我们应该会Welcome to the HomePage!在屏幕上看到 打印出来的内容。这意味着我们已经成功创建了我们将构建 REST API 的基础。
注意 -如果您想要更深入地关于如何创建基于 Go 的 Web 服务器的教程,请在此处查看本教程: Creating a Simple Web Server with Go(Lang)
我们的文章结构
我们将创建一个REST API,使我们能够CREATE,READ,UPDATE和 DELETE我们网站上的文章。当我们谈论CRUDAPI 时,我们指的是可以处理所有这些任务的 API:创建、读取、更新和删除。
在开始之前,我们必须定义我们的Article结构。Go 具有这种结构的概念,非常适合这种情况。让我们创建一个Article具有标题、描述 (desc) 和内容的 结构,如下所示:
type Article struct { Title string `json:"Title"` Desc string `json:"desc"` Content string `json:"content"` } // let's declare a global Articles array // that we can then populate in our main function // to simulate a database var Articles []Article
我们的 Struct 包含代表我们网站上所有文章所需的 3 个属性。为了使其工作,我们还必须将 "encoding/json"包导入到我们的导入列表中。
现在让我们更新我们的main函数,以便我们的Articles变量填充一些我们可以稍后检索和修改的虚拟数据。
func main() { Articles = []Article{ Article{Title: "Hello", Desc: "Article Description", Content: "Article Content"}, Article{Title: "Hello 2", Desc: "Article Description", Content: "Article Content"}, } handleRequests() }
完美,让我们现在继续创建我们的/articles端点,它将返回我们刚刚在这里定义的所有文章。
检索所有文章
在本教程的这一部分中,我们将创建一个新的 REST 端点,当收到HTTP GET请求时,它将返回我们站点的所有文章。
我们将首先创建一个名为 的新函数returnAllArticles,该函数将执行返回新填充的Articles以 JSON 格式编码的变量的简单任务:
main.go
func returnAllArticles(w , r *){ ("Endpoint Hit: returnAllArticles") j(w).Encode(Articles) }
调用将j(w).Encode(article)我们的文章数组编码为 JSON 字符串,然后作为响应的一部分写入。
在这生效之前,我们还需要向我们的handleRequests 函数添加一个新路由,它将所有调用映射http://localhost:10000/articles到我们新定义的函数。
func handleRequests() { ("/", homePage) // add our articles route and map it to our // returnAllArticles function like so ("/articles", returnAllArticles) log.Fatal(":10000", nil)) }
现在我们已经完成了这项工作,通过键入运行代码go run main.go,然后http://localhost:10000/articles在浏览器中打开,您应该会看到文章列表的 JSON 表示,如下所示:
http://localhost:10000/articles 响应
[ { Title: "Hello", desc: "Article Description", content: "Article Content" }, { Title: "Hello 2", desc: "Article Description", content: "Article Content" } ];
我们已经成功定义了我们的第一个 API 端点。
在本系列的下一部分中,您将更新 REST API 以使用gorilla/mux路由器而不是传统net/http路由器。
交换路由器将使您能够更轻松地执行任务,例如解析可能驻留在HTTP我们稍后需要的传入请求中的任何路径或查询参数 。
路由器入门
现在标准库足以提供让您自己的简单 REST API 启动和运行所需的一切,但是现在我们已经掌握了基本概念,我觉得是时候引入第三方路由器包了。最著名和使用率最高的是 gorilla/mux 路由器,它目前在 Github 上有 2,281 颗星。
构建我们的路由器
我们可以更新我们现有的main.go文件并交换一个gorilla/mux 基于HTTP路由器的路由器来代替之前存在的标准库。
修改您的handleRequests函数以创建一个新的路由器。
main.go
package main import ( "fmt" "log" "net/http" "encoding/json" "gi; ) … // Existing code from above func handleRequests() { // creates a new instance of a mux router myRouter := mux.NewRouter().StrictSlash(true) // replace with myRou myRou("/", homePage) myRou("/all", returnAllArticles) // finally, instead of passing in nil, we want // to pass in our newly created router as the second // argument log.Fatal(":10000", myRouter)) } func main() { ("Rest API v2.0 - Mux Routers") Articles = []Article{ Article{Title: "Hello", Desc: "Article Description", Content: "Article Content"}, Article{Title: "Hello 2", Desc: "Article Description", Content: "Article Content"}, } handleRequests() }
当您现在运行它时,您不会看到我们系统的工作方式发生真正的变化。它仍然会在同一个端口上启动并返回相同的结果,具体取决于您点击的端点。
唯一真正的区别是我们现在有一个 gorilla/mux 路由器,它允许我们在本教程后面轻松地执行诸如检索路径和查询参数之类的操作。
$ 去运行 main.go
Rest API v2.0 - Mux Routers
路径变量
到目前为止一切顺利,我们已经创建了一个非常简单的 REST API,它返回一个主页和我们所有的文章。但是如果我们只想查看一篇文章会发生什么?
好吧,多亏了 gorilla mux 路由器,我们可以将变量添加到我们的路径中,然后根据这些变量选择我们想要返回的文章。在您的handleRequests()功能中创建一条新路线,就在我们的/articles路线下方:
myRou("/article/{id}", returnSingleArticle)
请注意,我们已经添加{id}到我们的路径中。这将代表我们的 id 变量,当我们希望仅返回具有该确切键的文章时,我们将能够使用该变量。目前,我们的Article结构没有 Id 属性。现在让我们补充一下:
type Article struct { Id string `json:"Id"` Title string `json:"Title"` Desc string `json:"desc"` Content string `json:"content"` }
然后我们可以更新我们的main函数以填充数组中的Id值 Articles:
func main() { Articles = []Article{ Article{Id: "1", Title: "Hello", Desc: "Article Description", Content: "Article Content"}, Article{Id: "2", Title: "Hello 2", Desc: "Article Description", Content: "Article Content"}, } handleRequests() }
现在我们已经完成了,在我们的returnSingleArticle函数中,我们可以{id}从我们的 URL 中获取这个值,我们可以返回符合这个条件的文章。由于我们没有将数据存储在任何地方,因此我们只会返回传递给浏览器的 Id。
func returnSingleArticle(w , r *){ vars := mux.Vars(r) key := vars["id"] (w, "Key: " + key) }
如果我们http://localhost:1000/article/1在现在运行之后导航到,您应该会看到Key: 1在浏览器中打印出来。
让我们使用此key值返回与该键匹配的特定文章。
func returnSingleArticle(w , r *) { vars := mux.Vars(r) key := vars["id"] // Loop over all of our Articles // if the ar equals the key we pass in // return the article encoded as JSON for _, article := range Articles { if ar == key { j(w).Encode(article) } } }
通过调用运行它go run main.go,然后http://localhost:10000/article/1 在浏览器中打开:
http://localhost:10000/article/1 响应
{ Id: "1", Title: "Hello", desc: "Article Description", content: "Article Content" }
您现在将看到与1作为 JSON 返回的键匹配的文章。
创建和更新文章
在教程的这一部分,你将要打造的Create,Update和 DELETE一个部分CRUDREST API。我们已经涵盖了R阅读单个文章和所有文章的能力。
创建新文章
再一次,您将需要创建一个新函数来完成创建这篇新文章的工作。
让我们从createNewArticle()在我们的main.go文件中创建一个函数开始。
func createNewArticle(w , r *) { // get the body of our POST request // return the string response containing the request body reqBody, _ := iou) (w, "%+v", string(reqBody)) }
定义此函数后,您现在可以将路由添加到函数内定义的路由列表中handleRequests。但是,这一次,我们将添加.Methods("POST") 到路由的末尾以指定我们只想在传入请求是HTTP POST请求时调用此函数:
func handleRequests() { myRouter := mux.NewRouter().StrictSlash(true) myRou("/", homePage) myRou("/articles", returnAllArticles) // NOTE: Ordering is important here! This has to be defined before // the other `/article` endpoint. myRou("/article", createNewArticle).Methods("POST") myRou("/article/{id}", returnSingleArticle) log.Fatal(":10000", myRouter)) }
再次尝试运行它,然后尝试提交HTTP POST包含以下POST正文的请求:
{ "Id": "3", "Title": "Newly Created Post", "desc": "The description for my new post", "content": "my articles content" }
我们的端点将触发并随后回显请求正文中的任何值。
现在您已经验证了新端点是否正常工作,让我们更新我们的createNewArticle函数,以便它将请求正文中的 JSON 解组为一个新的Article结构体,该结构体随后可以附加到我们的Articles数组中:
func createNewArticle(w , r *) { // get the body of our POST request // unmarshal this into a new Article struct // append this to our Articles array. reqBody, _ := iou) var article Article j(reqBody, &article) // update our global Articles array to include // our new Article Articles = append(Articles, article) j(w).Encode(article) }
惊人的!如果您现在运行它POST并向您的应用程序发送相同的请求,您将看到它返回与以前相同的 JSON 格式,但它还会将新文章附加到您的Articles数组中。
现在通过点击来验证http://localhost:10000/articles:
http://localhost:10000/articles 响应
[ { "Id": "1", "Title": "Hello", "desc": "Article Description", "content": "Article Content" }, { "Id": "2", "Title": "Hello 2", "desc": "Article Description", "content": "Article Content" }, { "Id": "3", "Title": "Newly Created Post", "desc": "The description for my new post", "content": "my articles content" } ]
您现在已经成功地向Create新的 REST API添加了一个函数!
在本教程的下一部分中,您将了解如何添加新的 API 端点以允许您删除文章。
删除文章
有时您可能需要删除 REST API 公开的数据。为此,您需要DELETE在 API 中公开一个端点,该端点将接收一个标识符并删除与该标识符关联的任何内容。
在本教程的这一部分中,您将创建另一个端点,该端点接收HTTP DELETE请求并删除与给定Id路径参数匹配的文章 。
向您的main.go文件添加一个新函数,我们将调用该函数deleteArticle:
func deleteArticle(w , r *) { // once again, we will need to parse the path parameters vars := mux.Vars(r) // we will need to extract the `id` of the article we // wish to delete id := vars["id"] // we then need to loop through all our articles for index, article := range Articles { // if our id path parameter matches one of our // articles if ar == id { // updates our Articles array to remove the // article Articles = append(Articles[:index], Articles[index+1:]...) } } }
再一次,您需要向handleRequests映射到这个新deleteArticle函数的函数添加一个路由:
func handleRequests() { myRouter := mux.NewRouter().StrictSlash(true) myRou("/", homePage) myRou("/articles", returnAllArticles) myRou("/article", createNewArticle).Methods("POST") // add our new DELETE endpoint here myRou("/article/{id}", deleteArticle).Methods("DELETE") myRou("/article/{id}", returnSingleArticle) log.Fatal(":10000", myRouter)) }
尝试向 发送新HTTP DELETE请求http://localhost:10000/article/2。这将删除您的文章数组中的第二篇文章,当您随后http://localhost:10000/articles发出HTTP GET请求时,您应该看到它现在只包含一个Article.
注意- 为简单起见,我们正在更新一个全局变量。但是,我们不会进行任何检查以确保我们的代码没有竞争条件。为了使这段代码线程安全,我建议查看我关于Go Mutexes 的其他教程
更新文章端点
您需要实现的最后一个端点是 Update 端点。这个端点将是一个HTTP PUT基于端点,需要接受一个Id路径参数,就像我们对我们的HTTP DELETE端点所做的一样,以及一个 JSON 请求正文。
传入HTTP PUT请求正文中的此 JSON将包含我们要更新的文章的较新版本。
挑战
尝试在updateArticle函数中创建一个函数和相应的路由 handleRequests。这将与PUT请求匹配。完成此操作后,请使用您在函数中使用的相同代码实现该updateArticle函数,以便它解析HTTP请求正文createNewArticle。
最后,您必须遍历Articles数组中的文章并匹配并随后更新文章。
结论
这个例子代表了一个使用 Go 编写的非常简单的 RESTful API。在实际项目中,我们通常会将其与数据库联系起来,以便我们返回真实值。
源代码- 本教程的完整源代码可以在这里找到: TutorialEdge/create-rest-api-in-go
进一步阅读
如果您喜欢这篇文章,那么您可能还会喜欢以下教程:
- 设计生产就绪的 REST API
- 转到 MySQL 教程