菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
86
0

使用 Golang, chi 和 MySQL 来构建 RESTful API

原创
05/13 14:22
阅读数 463

在我目前的公司,我正在使用微服务架构,而 Golang 是创建微服务方面最受欢迎的语言。 今天,我将为博客文章构建一个简单的 CRUD API Web 服务。 撰写本文的原因是为了帮助初学者构建自己的 REST API。

在此 API 中,我们使用 dep 作为包管理(依赖管理)工具,使用 MySQL 数据库,使用 chi 作为路由。 在项目中使用包管理器是一个好习惯。由于中间件和一些其他优秀的功能,我们选择 chi 包进行路由的管理,你可以浏览它的 文档

在一个通用 CRUD 应用中,API 如下:

  1. POST /posts
  2. GET /posts
  3. GET /posts/{id}
  4. PUT /posts/{id}
  5. DELETE /posts/{id}

*在开始之前,请确保你的 $GOPATH 环境变量正确。然后在 $GOPATH/src 文件夹下新建一个 wiki 文件夹。如果你还未安装 dep 则利用下面的命令安装它。

$ go get -u github.com/golang/dep/cmd/dep

在成功安装了 dep 以后,进入刚刚新建的 wiki 文件夹然后初始化 dep,并且在这里安装一些其他的我们需要的包。

$ dep init
$ dep ensure -add github.com/go-chi/chi github.com/go-sql-driver/mysql

现在在 wiki 文件夹下新建 main.go 文件。然后按照如下代码在 main.go 文件中 导入 chi 和 SQL 包创建所有的路由并配置数据库连接凭据。下面我列出了我自己的代码,你可以根据他修改成你自己所需要的。

go-sql-route.go

package main

import (
    "github.com/go-chi/chi"
    "github.com/go-chi/chi/middleware"
    _ "github.com/go-sql-driver/mysql"
)

var router *chi.Mux
var db *sql.DB

const (
    dbName = "go-mysql-crud"
    dbPass = "12345"
    dbHost = "localhost"
    dbPort = "33066"
)

func routers() *chi.Mux {
    router.Get("/posts", AllPosts)
    router.Get("/posts/{id}", DetailPost)
    router.Post("/posts", CreatePost)
    router.Put("/posts/{id}", UpdatePost)
    router.Delete("/posts/{id}", DeletePost)

    return router
}

很好,让我们建立与数据库的连接并初始化路由器和中间件。 在这里,我们将使用 recover 中间件。 因为这可以保证任何请求失败应用程序都不会死亡,因此用户可以在你不重新启动应用的情况下再次请求。

go-sql-2.go

func init() { 
    router = chi.NewRouter() 
    router.Use(middleware.Recoverer)  

    dbSource := fmt.Sprintf("root:%s@tcp(%s:%s)/%s?charset=utf8",  dbPass, dbHost, dbPort, dbName)

    var err error
    db, err = sql.Open("mysql", dbSource) 

    catch(err)
}

不要纠结上面出现的 catch 函数,我将在之后定义此函数。 dbSource 的完整格式为 username:password@protocol(address)/dbname?param=value

现在我们将创建一个基本的 POST 模型,该模型用于发送和检索数据。 在 Go 语言中,模型通常用 struct 来定义。

go-sql-3.go

type Post struct {
    ID      int    `json: "id"`
    Title   string `json: "title"`
    Content string `json: "content"`
}

通过添加 json 标签,我们可以将 struct 转换为 JSON 格式。 我们将通过终端或其他第三方应用在 MySQL 数据库中创建 go-mysql-crudposts 表。 我主要用的软件是 Sequel Pro。

post.sql

CREATE DATABASE  IF NOT EXISTS `go-mysql-crud` /*!40100 DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci */;
USE `go-mysql-crud`;

/* Create table for `posts` */

DROP TABLE IF EXISTS `posts`;
CREATE TABLE `posts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
  `content` longtext COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
);

还记得我们之前创建的路由和处理函数吗? 现在是实现这些的时候了。 当有人通过 POST 方法并利用 json 格式携带 title 和 content 信息请求我们的 /post 路由时我们的代码将执行 CreatePost 函数。

go-sql-4.go

// CreatePost create a new post
func CreatePost(w http.ResponseWriter, r *http.Request) { 
    var post Post 
    json.NewDecoder(r.Body).Decode(&post)  

    query, err := db.Prepare("Insert posts SET title=?, content=?")
    catch(err) 

    _, er := query.Exec(post.Title, post.Content) 
    catch(er) 
    defer query.Close()  

    respondwithJSON(w, http.StatusCreated, map[string]string{"message": "successfully created"})
}

在上面的代码中,我们使用了 json.NewDecoder 来读取参数 r 中的 POST 数据 并将其解码为我们的 Post struct 模型。 然后准备我们的插入查询并随后执行。

title=? 表示我们动态的标题数据要执行,此数据来源于我们函数所接收到的 post 请求体。 在查询结束以后不要忘记关闭查询。 最后,我们使用 respondwithJSON 函数将消息以 JSON 格式发送给客户端。 稍后我们就实现这个函数。

让我们实现 UpdatePost 和 DeletePost 函数。

go-sql-5.go

// UpdatePost update a  spesific post
func UpdatePost(w http.ResponseWriter, r *http.Request) {
    var post Post
    id := chi.URLParam(r, "id")
    json.NewDecoder(r.Body).Decode(&post)

    query, err := db.Prepare("Update posts set title=?, content=? where id=?")
    catch(err)
    _, er := query.Exec(post.Title, post.Content, id)
    catch(er)

    defer query.Close()

    respondwithJSON(w, http.StatusOK, map[string]string{"message": "update successfully"})

}

// DeletePost remove a spesific post
func DeletePost(w http.ResponseWriter, r *http.Request) {
    id := chi.URLParam(r, "id")

    query, err := db.Prepare("delete from posts where id=?")
    catch(err)
    _, er := query.Exec(id)
    catch(er)
    query.Close()

    respondwithJSON(w, http.StatusOK, map[string]string{"message": "successfully deleted"})
}

要捕获 ID,我使用 chi.URLParam。 在你尝试实现 AllPostDetailPost 方法遇到困难时可以参考我的 Github Repo,这里面包含完整的代码。

Now it’s time to write main function and some helper function.

go-sql-7.go

func main() {
    routers()
    http.ListenAndServe(":8005", Logger())
}

Running this example will spin up a server on port 8005 and can be accessed at http://localhost:8005. (Logger function print the log in your every request on the terminal and it is implemented on helper.go file below.)

We will split main.go to helper.go for including our helper function. You can also split the router and move the router function to router.go.

helper.go

package main

import ( 
    "encoding/json" 
    "fmt" 
    "net/http" 
    "time"
)

func catch(err error) {
    if err != nil {
        panic(err)
    }
}

// respondwithError return error message
func respondWithError(w http.ResponseWriter, code int, msg string) {
    respondwithJSON(w, code, map[string]string{"message": msg})
}

// respondwithJSON write json response format
func respondwithJSON(w http.ResponseWriter, code int, payload interface{}) {
    response, _ := json.Marshal(payload)
    fmt.Println(payload)
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(code)
    w.Write(response)
}

// Logger return log message
func Logger() http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Println(time.Now(), r.Method, r.URL)
        router.ServeHTTP(w, r) // dispatch the request
    })
}

I think you are pretty smart to understand the above code ;) .

Okay, Can we run our code now? In my github repo I’ve included a makefile which can help us to save our time for every time building the app.

If you include makefile in your code directory then run

make run

If you don’t then build the main.go

go build .

It’ll create a binary file in your directory and to run this command $./wiki as our directory name is wiki. So far, Our app is running and serve 8005 port.

To test the API open postman and run those route sequentially. For instance -

Yee! We’ve finished our mundane tasks. Again, you can find full source code from github repo. I also added docker and docker-compose file over there.

本文章首发在 猿圈.com 网站上。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。


在我目前的公司,我正在使用微服务架构,而 Golang 是创建微服务方面最受欢迎的语言。 今天,我将为博客文章构建一个简单的 CRUD API Web 服务。 撰写本文的原因是为了帮助初学者构建自己的 REST API。

在此 API 中,我们使用 dep 作为包管理(依赖管理)工具,使用 MySQL 数据库,使用 chi 作为路由。 在项目中使用包管理器是一个好习惯。由于中间件和一些其他优秀的功能,我们选择 chi 包进行路由的管理,你可以浏览它的 文档

在一个通用 CRUD 应用中,API 如下:

  1. POST /posts
  2. GET /posts
  3. GET /posts/{id}
  4. PUT /posts/{id}
  5. DELETE /posts/{id}

*在开始之前,请确保你的 $GOPATH 环境变量正确。然后在 $GOPATH/src 文件夹下新建一个 wiki 文件夹。如果你还未安装 dep 则利用下面的命令安装它。

$ go get -u github.com/golang/dep/cmd/dep

在成功安装了 dep 以后,进入刚刚新建的 wiki 文件夹然后初始化 dep,并且在这里安装一些其他的我们需要的包。

$ dep init
$ dep ensure -add github.com/go-chi/chi github.com/go-sql-driver/mysql

现在在 wiki 文件夹下新建 main.go 文件。然后按照如下代码在 main.go 文件中 导入 chi 和 SQL 包创建所有的路由并配置数据库连接凭据。下面我列出了我自己的代码,你可以根据他修改成你自己所需要的。

go-sql-route.go

package main

import (
    "github.com/go-chi/chi"
    "github.com/go-chi/chi/middleware"
    _ "github.com/go-sql-driver/mysql"
)

var router *chi.Mux
var db *sql.DB

const (
    dbName = "go-mysql-crud"
    dbPass = "12345"
    dbHost = "localhost"
    dbPort = "33066"
)

func routers() *chi.Mux {
    router.Get("/posts", AllPosts)
    router.Get("/posts/{id}", DetailPost)
    router.Post("/posts", CreatePost)
    router.Put("/posts/{id}", UpdatePost)
    router.Delete("/posts/{id}", DeletePost)

    return router
}

很好,让我们建立与数据库的连接并初始化路由器和中间件。 在这里,我们将使用 recover 中间件。 因为这可以保证任何请求失败应用程序都不会死亡,因此用户可以在你不重新启动应用的情况下再次请求。

go-sql-2.go

func init() { 
    router = chi.NewRouter() 
    router.Use(middleware.Recoverer)  

    dbSource := fmt.Sprintf("root:%s@tcp(%s:%s)/%s?charset=utf8",  dbPass, dbHost, dbPort, dbName)

    var err error
    db, err = sql.Open("mysql", dbSource) 

    catch(err)
}

不要纠结上面出现的 catch 函数,我将在之后定义此函数。 dbSource 的完整格式为 username:password@protocol(address)/dbname?param=value

现在我们将创建一个基本的 POST 模型,该模型用于发送和检索数据。 在 Go 语言中,模型通常用 struct 来定义。

go-sql-3.go

type Post struct {
    ID      int    `json: "id"`
    Title   string `json: "title"`
    Content string `json: "content"`
}

通过添加 json 标签,我们可以将 struct 转换为 JSON 格式。 我们将通过终端或其他第三方应用在 MySQL 数据库中创建 go-mysql-crudposts 表。 我主要用的软件是 Sequel Pro。

post.sql

CREATE DATABASE  IF NOT EXISTS `go-mysql-crud` /*!40100 DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci */;
USE `go-mysql-crud`;

/* Create table for `posts` */

DROP TABLE IF EXISTS `posts`;
CREATE TABLE `posts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
  `content` longtext COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
);

还记得我们之前创建的路由和处理函数吗? 现在是实现这些的时候了。 当有人通过 POST 方法并利用 json 格式携带 title 和 content 信息请求我们的 /post 路由时我们的代码将执行 CreatePost 函数。

go-sql-4.go

// CreatePost create a new post
func CreatePost(w http.ResponseWriter, r *http.Request) { 
    var post Post 
    json.NewDecoder(r.Body).Decode(&post)  

    query, err := db.Prepare("Insert posts SET title=?, content=?")
    catch(err) 

    _, er := query.Exec(post.Title, post.Content) 
    catch(er) 
    defer query.Close()  

    respondwithJSON(w, http.StatusCreated, map[string]string{"message": "successfully created"})
}

在上面的代码中,我们使用了 json.NewDecoder 来读取参数 r 中的 POST 数据 并将其解码为我们的 Post struct 模型。 然后准备我们的插入查询并随后执行。

title=? 表示我们动态的标题数据要执行,此数据来源于我们函数所接收到的 post 请求体。 在查询结束以后不要忘记关闭查询。 最后,我们使用 respondwithJSON 函数将消息以 JSON 格式发送给客户端。 稍后我们就实现这个函数。

让我们实现 UpdatePost 和 DeletePost 函数。

go-sql-5.go

// UpdatePost update a  spesific post
func UpdatePost(w http.ResponseWriter, r *http.Request) {
    var post Post
    id := chi.URLParam(r, "id")
    json.NewDecoder(r.Body).Decode(&post)

    query, err := db.Prepare("Update posts set title=?, content=? where id=?")
    catch(err)
    _, er := query.Exec(post.Title, post.Content, id)
    catch(er)

    defer query.Close()

    respondwithJSON(w, http.StatusOK, map[string]string{"message": "update successfully"})

}

// DeletePost remove a spesific post
func DeletePost(w http.ResponseWriter, r *http.Request) {
    id := chi.URLParam(r, "id")

    query, err := db.Prepare("delete from posts where id=?")
    catch(err)
    _, er := query.Exec(id)
    catch(er)
    query.Close()

    respondwithJSON(w, http.StatusOK, map[string]string{"message": "successfully deleted"})
}

要捕获 ID,我使用 chi.URLParam。 在你尝试实现 AllPostDetailPost 方法遇到困难时可以参考我的 Github Repo,这里面包含完整的代码。

Now it’s time to write main function and some helper function.

go-sql-7.go

func main() {
    routers()
    http.ListenAndServe(":8005", Logger())
}

Running this example will spin up a server on port 8005 and can be accessed at http://localhost:8005. (Logger function print the log in your every request on the terminal and it is implemented on helper.go file below.)

We will split main.go to helper.go for including our helper function. You can also split the router and move the router function to router.go.

helper.go

package main

import ( 
    "encoding/json" 
    "fmt" 
    "net/http" 
    "time"
)

func catch(err error) {
    if err != nil {
        panic(err)
    }
}

// respondwithError return error message
func respondWithError(w http.ResponseWriter, code int, msg string) {
    respondwithJSON(w, code, map[string]string{"message": msg})
}

// respondwithJSON write json response format
func respondwithJSON(w http.ResponseWriter, code int, payload interface{}) {
    response, _ := json.Marshal(payload)
    fmt.Println(payload)
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(code)
    w.Write(response)
}

// Logger return log message
func Logger() http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Println(time.Now(), r.Method, r.URL)
        router.ServeHTTP(w, r) // dispatch the request
    })
}

I think you are pretty smart to understand the above code ;) .

Okay, Can we run our code now? In my github repo I’ve included a makefile which can help us to save our time for every time building the app.

If you include makefile in your code directory then run

make run

If you don’t then build the main.go

go build .

It’ll create a binary file in your directory and to run this command $./wiki as our directory name is wiki. So far, Our app is running and serve 8005 port.

To test the API open postman and run those route sequentially. For instance -

Yee! We’ve finished our mundane tasks. Again, you can find full source code from github repo. I also added docker and docker-compose file over there.

本文章首发在 猿圈.com 网站上。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

发表评论

0/200
86 点赞
0 评论
收藏