使用 Golang 和 MongoDB 构建 REST API - Gin-gonic 版本
先决条件
让我们开始编程吧
结论
表述性状态转移(REST)是一种架构模式,它指导应用程序编程接口(API)的设计和开发。REST API 已成为产品服务器端与其客户端之间通信的标准,以提高性能、可扩展性、简洁性、可修改性、可见性、可移植性和可靠性。
本文将探讨如何使用 Golang 、Gin-gonic框架和MongoDB构建用户管理应用程序。在本教程的最后,我们将学习如何构建 Gin-gonic 应用程序、创建 REST API 以及使用 MongoDB 持久化数据。
Gin-gonic,通常简称为Gin,是一个用 Golang 编写的 HTTP Web 框架,专注于提升性能和生产力。Gin 使用定制版的HttpRouter,这是一个轻量级、高性能的 HTTP 请求路由器,其 API 路由的路由速度比大多数框架都要快。
MongoDB 是一款基于文档的数据库管理程序,可作为关系型数据库的替代方案。MongoDB 支持处理大型分布式数据集,并提供无缝存储和检索信息的选项。
您可以在此存储库中找到完整的源代码。
先决条件
本文后续步骤需要具备 Golang 编程经验。虽然不要求具备 MongoDB 经验,但有相关经验会更好。
我们还需要以下物品:
- 用于托管数据库的MongoDB 账户。注册 完全免费。
- Postman或您选择的任何 API 测试应用程序
让我们开始编程吧
入门
首先,我们需要导航到目标目录,然后在终端中运行以下命令。
mkdir gin-mongo-api && cd gin-mongo-api
该命令会创建一个gin-mongo-api文件夹并导航到项目目录。
接下来,我们需要初始化一个 Go 模块来管理项目依赖项,运行以下命令:
go mod init gin-mongo-api
此命令将创建一个go.mod用于跟踪项目依赖关系的文件。
接下来,我们使用以下命令安装所需的依赖项:
go get -u github.com/gin-gonic/gin go.mongodb.org/mongo-driver/mongo github.com/joho/godotenv github.com/go-playground/validator/v10
github.com/gin-gonic/gin是一个用于构建 Web 应用程序的框架。
go.mongodb.org/mongo-driver/mongo是用于连接 MongoDB 的驱动程序。
github.com/joho/godotenv是一个用于管理环境变量的库。
github.com/go-playground/validator/v10是一个用于验证结构体和字段的库。
应用程序入口点
项目依赖项安装完毕后,我们需要main.go在根目录下创建一个文件,并添加以下代码片段:
package main
import "github.com/gin-gonic/gin"
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"data": "Hello from Gin-gonic & mongoDB",
})
})
router.Run("localhost:6000")
}
上面的代码片段执行以下操作:
- 导入所需的依赖项。
- 使用配置初始化 Gin 路由器
Default。该Default函数会使用默认中间件(日志记录器和恢复器)配置 Gin 路由器。 - 使用该
Get函数路由到/路径和返回 JSON 的处理函数Hello from Gin-gonic & mongoDB。 - 使用该
Run函数将服务器连接router到http.Server,并开始监听和处理 HTTP 请求localhost:6000。
接下来,我们可以通过在终端中运行以下命令来启动开发服务器,从而测试我们的应用程序。
go run main.go
Golang 中的模块化
良好的项目文件夹结构至关重要。合理的项目结构可以简化我们管理应用程序依赖项的方式,并使我们自己和其他人更容易阅读代码库。
为此,我们需要在项目目录中创建 `<module> configs`、 `<module> ` controllers、 `<module> ` 和`<module>` 文件夹。modelsresponsesroutes
PS:该 go.sum 文件包含所有依赖项校验和,并由 go 工具自动管理,我们无需担心。
configs用于模块化项目配置文件
controllers是用于模块化应用程序逻辑。
models是用于数据和数据库逻辑的模块化。
responses是用来模块化描述我们希望 API 返回的响应的文件。这一点稍后会更清楚。
routes用于模块化 URL 模式和处理程序信息。
设置 MongoDB
完成上述步骤后,我们需要登录或注册MongoDB帐户。点击项目下拉菜单,然后点击“新建项目”按钮。
输入golang-api项目名称,点击“下一步”,然后点击“创建项目”。
点击“构建数据库”
选择“共享”作为数据库类型。
点击“创建”按钮设置集群。设置过程可能需要一些时间。
接下来,我们需要创建一个用户,以便从外部访问数据库。输入用户名和密码,然后点击“创建用户”。我们还需要添加自己的 IP 地址,以便安全地连接到数据库。点击“添加我的当前 IP 地址”按钮即可。然后点击“完成并关闭”保存更改。
保存更改后,应该会看到如下所示的“数据库部署”屏幕:
将我们的应用程序连接到 MongoDB
配置完成后,我们需要将应用程序连接到已创建的数据库。为此,请单击“连接”按钮。
点击“连接您的应用程序”,将驱动程序和Go版本更改为如下所示的内容。然后点击复制图标复制连接字符串。
设置环境变量
接下来,我们需要修改复制的连接字符串,使其包含我们之前创建的用户密码,并更改数据库名称。为此,首先需要.env在根目录下创建一个文件,并在该文件中添加以下代码片段:
MONGOURI=mongodb+srv://<YOUR USERNAME HERE>:<YOUR PASSWORD HERE>@cluster0.e5akf.mongodb.net/myFirstDatabese?retryWrites=true&w=majority
以下是一个正确填写的连接字符串示例:
MONGOURI=mongodb+srv://malomz:malomzPassword@cluster0.e5akf.mongodb.net/golangDB?retryWrites=true&w=majority
加载环境变量
完成上述步骤后,我们需要创建一个辅助函数,使用github.com/joho/godotenv之前安装的库来加载环境变量。为此,我们需要导航到该configs文件夹,并在该文件夹中创建一个env.go文件,然后添加以下代码片段:
package configs
import (
"log"
"os"
"github.com/joho/godotenv"
)
func EnvMongoURI() string {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
return os.Getenv("MONGOURI")
}
上面的代码片段执行以下操作:
- 导入所需的依赖项。
- 创建一个
EnvMongoURI函数,检查环境变量是否已正确加载,并返回该环境变量。
连接到 MongoDB
要从我们的应用程序连接到 MongoDB 数据库,首先需要导航到该configs文件夹,并在该文件夹中创建一个setup.go文件,然后添加以下代码片段:
package configs
import (
"context"
"fmt"
"log"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func ConnectDB() *mongo.Client {
client, err := mongo.NewClient(options.Client().ApplyURI(EnvMongoURI()))
if err != nil {
log.Fatal(err)
}
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
err = client.Connect(ctx)
if err != nil {
log.Fatal(err)
}
//ping the database
err = client.Ping(ctx, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println("Connected to MongoDB")
return client
}
//Client instance
var DB *mongo.Client = ConnectDB()
//getting database collections
func GetCollection(client *mongo.Client, collectionName string) *mongo.Collection {
collection := client.Database("golangAPI").Collection(collectionName)
return collection
}
上面的代码片段执行以下操作:
- 导入所需的依赖项。
- 创建一个
ConnectDB函数,首先配置客户端使用正确的 URI 并检查错误。其次,我们定义了一个 10 秒的连接超时时间。第三,检查连接数据库时是否存在错误,如果连接时间超过 10 秒则取消连接。最后,我们 ping 了数据库以测试连接,并返回了实例client。 - 创建一个
DB变量实例ConnectDB。这在创建集合时会非常有用。 - 创建一个函数,用于在数据库中
GetCollection检索和创建数据。collections
接下来,我们需要在应用程序启动时连接到数据库。为此,我们需要main.go按如下所示进行修改:
package main
import (
"gin-mongo-api/configs" //add this
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
//run database
configs.ConnectDB()
router.Run("localhost:6000")
}
设置 API 路由处理程序和响应类型
路由处理程序
完成上述步骤后,我们需要在文件夹user_route.go内创建一个文件routes来管理应用程序中所有与用户相关的路由,如下所示:
package routes
import "github.com/gin-gonic/gin"
func UserRoute(router *gin.Engine) {
//All routes related to users comes here
}
接下来,我们需要将新创建的路由附加到http.Server,具体main.go操作如下所示:
package main
import (
"gin-mongo-api/configs"
"gin-mongo-api/routes" //add this
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
//run database
configs.ConnectDB()
//routes
routes.UserRoute(router) //add this
router.Run("localhost:6000")
}
接下来,我们需要创建一个可重用的响应类型描述struct符来描述 API 的响应。为此,请导航到该responses文件夹,并在其中创建一个user_response.go文件,然后添加以下代码片段:
package responses
type UserResponse struct {
Status int `json:"status"`
Message string `json:"message"`
Data map[string]interface{} `json:"data"`
}
上面的代码片段创建了一个UserResponse包含`<struct>` Status、Message`<property>` 和Data`<property>` 属性的结构体,用于表示 API 响应类型。
PS:json:"status"`< struct 标签>` json:"message"、`<struct 标签>` 和 ` json:"data" <struct 标签>` 被称为结构体标签。结构体标签允许我们将元信息附加到相应的结构体属性上。换句话说,我们使用它们来重新格式化 API 返回的 JSON 响应。
最后,创建 REST API
接下来,我们需要一个模型来表示我们的应用程序数据。为此,我们需要导航到该models文件夹,并在该文件夹中创建一个user_model.go文件,然后添加以下代码片段:
package models
import "go.mongodb.org/mongo-driver/bson/primitive"
type User struct {
Id primitive.ObjectID `json:"id,omitempty"`
Name string `json:"name,omitempty" validate:"required"`
Location string `json:"location,omitempty" validate:"required"`
Title string `json:"title,omitempty" validate:"required"`
}
上面的代码片段执行以下操作:
- 导入所需的依赖项。
- 创建一个
User包含必需属性的结构体。我们在结构体标签中添加了 `--no-text`omitempty和validate:"required"`--required`,分别告诉 Gin-gonic 忽略空字段并将空字段设为必需字段。
创建用户端点
模型设置完成后,我们现在可以创建一个创建用户的函数。为此,我们需要导航到该controllers文件夹,并在该文件夹中创建一个user_controller.go文件,然后添加以下代码片段:
package controllers
import (
"context"
"gin-mongo-api/configs"
"gin-mongo-api/models"
"gin-mongo-api/responses"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
)
var userCollection *mongo.Collection = configs.GetCollection(configs.DB, "users")
var validate = validator.New()
func CreateUser() gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
var user models.User
defer cancel()
//validate the request body
if err := c.BindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: map[string]interface{}{"data": err.Error()}})
return
}
//use the validator library to validate required fields
if validationErr := validate.Struct(&user); validationErr != nil {
c.JSON(http.StatusBadRequest, responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: map[string]interface{}{"data": validationErr.Error()}})
return
}
newUser := models.User{
Id: primitive.NewObjectID(),
Name: user.Name,
Location: user.Location,
Title: user.Title,
}
result, err := userCollection.InsertOne(ctx, newUser)
if err != nil {
c.JSON(http.StatusInternalServerError, responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}})
return
}
c.JSON(http.StatusCreated, responses.UserResponse{Status: http.StatusCreated, Message: "success", Data: map[string]interface{}{"data": result}})
}
}
上面的代码片段执行以下操作:
- 导入所需的依赖项。
- 分别使用我们之前安装的库创建集合和验证模型的变量
userCollection。validategithub.com/go-playground/validator/v10 - 创建一个
CreateUser返回 Gin-gonic 处理程序的函数。在返回的处理程序中,我们首先定义了将用户插入文档的超时时间为 10 秒,并使用验证器库验证请求体和必填字段。我们使用UserResponse之前创建的结构体返回相应的消息和状态码。其次,我们创建了一个newUser变量,使用该函数插入它userCollection.InsertOne,并检查是否存在错误。最后,如果插入成功,则返回正确的响应。
接下来,我们需要更新user_routes.go路由 API URL 和相应的控制器,如下所示:
package routes
import (
"gin-mongo-api/controllers" //add this
"github.com/gin-gonic/gin"
)
func UserRoute(router *gin.Engine) {
router.POST("/user", controllers.CreateUser()) //add this
}
获取用户端点
要获取用户详细信息,我们需要user_controller.go按如下所示进行修改:
package controllers
import (
//other import goes here
"go.mongodb.org/mongo-driver/bson" //add this
)
var userCollection *mongo.Collection = configs.GetCollection(configs.DB, "users")
var validate = validator.New()
func CreateUser() gin.HandlerFunc {
return func(c *gin.Context) {
//create user code goes here
}
}
func GetAUser() gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
userId := c.Param("userId")
var user models.User
defer cancel()
objId, _ := primitive.ObjectIDFromHex(userId)
err := userCollection.FindOne(ctx, bson.M{"id": objId}).Decode(&user)
if err != nil {
c.JSON(http.StatusInternalServerError, responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}})
return
}
c.JSON(http.StatusOK, responses.UserResponse{Status: http.StatusOK, Message: "success", Data: map[string]interface{}{"data": user}})
}
}
上面的代码片段执行以下操作:
- 导入所需的依赖项。
- 创建一个
GetAUser返回 Gin-gonic 处理程序的函数。在返回的处理程序中,我们首先定义了在文档中查找用户的超时时间(10 秒),一个用于从 URL 参数中userId获取用户ID的变量,以及一个用于将字符串user转换为MongoDB 使用的 BSON 类型的变量。其次,我们使用 URL 参数搜索用户,并将 URL作为过滤器传递,然后使用属性方法获取相应的对象。最后,我们返回解码后的响应。userIdprimitive.ObjectIDuserCollection.FindOneobjIdDecode
接下来,我们需要更新user_routes.go路由 API URL 和相应的控制器,如下所示:
package routes
import (
//import goes here
)
func UserRoute(router *gin.Engine) {
//other routes goes here
router.GET("/user/:userId", controllers.GetAUser()) //add this
}
PS: 我们还 userId 向 URL 路径传递了一个参数。该参数必须与我们在控制器中指定的参数一致。
编辑用户端点
要编辑用户,我们需要user_controller.go按如下所示进行修改:
package controllers
import (
//import goes here
)
var userCollection *mongo.Collection = configs.GetCollection(configs.DB, "users")
var validate = validator.New()
func CreateUser() gin.HandlerFunc {
return func(c *gin.Context) {
//create user code goes here
}
}
func GetAUser() gin.HandlerFunc {
return func(c *gin.Context) {
// get a user code goes here
}
}
func EditAUser() gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
userId := c.Param("userId")
var user models.User
defer cancel()
objId, _ := primitive.ObjectIDFromHex(userId)
//validate the request body
if err := c.BindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: map[string]interface{}{"data": err.Error()}})
return
}
//use the validator library to validate required fields
if validationErr := validate.Struct(&user); validationErr != nil {
c.JSON(http.StatusBadRequest, responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: map[string]interface{}{"data": validationErr.Error()}})
return
}
update := bson.M{"name": user.Name, "location": user.Location, "title": user.Title}
result, err := userCollection.UpdateOne(ctx, bson.M{"id": objId}, bson.M{"$set": update})
if err != nil {
c.JSON(http.StatusInternalServerError, responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}})
return
}
//get updated user details
var updatedUser models.User
if result.MatchedCount == 1 {
err := userCollection.FindOne(ctx, bson.M{"id": objId}).Decode(&updatedUser)
if err != nil {
c.JSON(http.StatusInternalServerError, responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}})
return
}
}
c.JSON(http.StatusOK, responses.UserResponse{Status: http.StatusOK, Message: "success", Data: map[string]interface{}{"data": updatedUser}})
}
}
上面的函数EditAUser与原函数功能相同CreateUser。但是,我们添加了一个update变量来获取更新后的字段,并使用该变量更新了集合userCollection.UpdateOne。最后,我们搜索了更新后的用户详细信息,并返回了解码后的响应。
接下来,我们需要更新user_routes.go路由 API URL 和相应的控制器,如下所示:
package routes
import (
//import goes here
)
func UserRoute(router *gin.Engine) {
//other routes goes here
router.PUT("/user/:userId", controllers.EditAUser()) //add this
}
删除用户端点
要删除用户,我们需要user_controller.go按如下所示进行修改:
package controllers
import (
//import goes here
)
var userCollection *mongo.Collection = configs.GetCollection(configs.DB, "users")
var validate = validator.New()
func CreateUser() gin.HandlerFunc {
return func(c *gin.Context) {
//create user code goes here
}
}
func GetAUser() gin.HandlerFunc {
return func(c *gin.Context) {
// get a user code goes here
}
}
func EditAUser() gin.HandlerFunc {
return func(c *gin.Context) {
//edit a user code goes here
}
}
func DeleteAUser() gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
userId := c.Param("userId")
defer cancel()
objId, _ := primitive.ObjectIDFromHex(userId)
result, err := userCollection.DeleteOne(ctx, bson.M{"id": objId})
if err != nil {
c.JSON(http.StatusInternalServerError, responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}})
return
}
if result.DeletedCount < 1 {
c.JSON(http.StatusNotFound,
responses.UserResponse{Status: http.StatusNotFound, Message: "error", Data: map[string]interface{}{"data": "User with specified ID not found!"}},
)
return
}
c.JSON(http.StatusOK,
responses.UserResponse{Status: http.StatusOK, Message: "success", Data: map[string]interface{}{"data": "User successfully deleted!"}},
)
}
}
该DeleteAUser函数遵循之前的步骤,使用删除匹配的记录userCollection.DeleteOne。我们还检查了项目是否已成功删除,并返回了相应的响应。
接下来,我们需要更新user_routes.go路由 API URL 和相应的控制器,如下所示:
package routes
import (
//import goes here
)
func UserRoute(router *gin.Engine) {
//other routes goes here
router.DELETE("/user/:userId", controllers.DeleteAUser()) //add this
}
获取用户列表端点
要获取用户列表,我们需要user_controller.go按如下所示进行修改:
package controllers
import (
//import goes here
)
var userCollection *mongo.Collection = configs.GetCollection(configs.DB, "users")
var validate = validator.New()
func CreateUser() gin.HandlerFunc {
return func(c *gin.Context) {
//create user code goes here
}
}
func GetAUser() gin.HandlerFunc {
return func(c *gin.Context) {
// get a user code goes here
}
}
func EditAUser() gin.HandlerFunc {
return func(c *gin.Context) {
//edit a user code goes here
}
}
func DeleteAUser() gin.HandlerFunc {
return func(c *gin.Context) {
//delete a user code goes here
}
}
func GetAllUsers() gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
var users []models.User
defer cancel()
results, err := userCollection.Find(ctx, bson.M{})
if err != nil {
c.JSON(http.StatusInternalServerError, responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}})
return
}
//reading from the db in an optimal way
defer results.Close(ctx)
for results.Next(ctx) {
var singleUser models.User
if err = results.Decode(&singleUser); err != nil {
c.JSON(http.StatusInternalServerError, responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}})
}
users = append(users, singleUser)
}
c.JSON(http.StatusOK,
responses.UserResponse{Status: http.StatusOK, Message: "success", Data: map[string]interface{}{"data": users}},
)
}
}
该GetAllUsers函数遵循之前的步骤,通过获取用户列表来实现。我们还使用属性方法遍历返回的用户列表,userCollection.Find从而以最优方式读取该列表。Next
接下来,我们需要更新user_routes.go路由 API URL 和相应的控制器,如下所示:
package routes
import (
//import goes here
)
func UserRoute(router *gin.Engine) {
//other routes goes here
router.GET("/users", controllers.GetAllUsers()) //add this
}
完成 user_controller.go
package controllers
import (
"context"
"gin-mongo-api/configs"
"gin-mongo-api/models"
"gin-mongo-api/responses"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
)
var userCollection *mongo.Collection = configs.GetCollection(configs.DB, "users")
var validate = validator.New()
func CreateUser() gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
var user models.User
defer cancel()
//validate the request body
if err := c.BindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: map[string]interface{}{"data": err.Error()}})
return
}
//use the validator library to validate required fields
if validationErr := validate.Struct(&user); validationErr != nil {
c.JSON(http.StatusBadRequest, responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: map[string]interface{}{"data": validationErr.Error()}})
return
}
newUser := models.User{
Id: primitive.NewObjectID(),
Name: user.Name,
Location: user.Location,
Title: user.Title,
}
result, err := userCollection.InsertOne(ctx, newUser)
if err != nil {
c.JSON(http.StatusInternalServerError, responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}})
return
}
c.JSON(http.StatusCreated, responses.UserResponse{Status: http.StatusCreated, Message: "success", Data: map[string]interface{}{"data": result}})
}
}
func GetAUser() gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
userId := c.Param("userId")
var user models.User
defer cancel()
objId, _ := primitive.ObjectIDFromHex(userId)
err := userCollection.FindOne(ctx, bson.M{"id": objId}).Decode(&user)
if err != nil {
c.JSON(http.StatusInternalServerError, responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}})
return
}
c.JSON(http.StatusOK, responses.UserResponse{Status: http.StatusOK, Message: "success", Data: map[string]interface{}{"data": user}})
}
}
func EditAUser() gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
userId := c.Param("userId")
var user models.User
defer cancel()
objId, _ := primitive.ObjectIDFromHex(userId)
//validate the request body
if err := c.BindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: map[string]interface{}{"data": err.Error()}})
return
}
//use the validator library to validate required fields
if validationErr := validate.Struct(&user); validationErr != nil {
c.JSON(http.StatusBadRequest, responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: map[string]interface{}{"data": validationErr.Error()}})
return
}
update := bson.M{"name": user.Name, "location": user.Location, "title": user.Title}
result, err := userCollection.UpdateOne(ctx, bson.M{"id": objId}, bson.M{"$set": update})
if err != nil {
c.JSON(http.StatusInternalServerError, responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}})
return
}
//get updated user details
var updatedUser models.User
if result.MatchedCount == 1 {
err := userCollection.FindOne(ctx, bson.M{"id": objId}).Decode(&updatedUser)
if err != nil {
c.JSON(http.StatusInternalServerError, responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}})
return
}
}
c.JSON(http.StatusOK, responses.UserResponse{Status: http.StatusOK, Message: "success", Data: map[string]interface{}{"data": updatedUser}})
}
}
func DeleteAUser() gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
userId := c.Param("userId")
defer cancel()
objId, _ := primitive.ObjectIDFromHex(userId)
result, err := userCollection.DeleteOne(ctx, bson.M{"id": objId})
if err != nil {
c.JSON(http.StatusInternalServerError, responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}})
return
}
if result.DeletedCount < 1 {
c.JSON(http.StatusNotFound,
responses.UserResponse{Status: http.StatusNotFound, Message: "error", Data: map[string]interface{}{"data": "User with specified ID not found!"}},
)
return
}
c.JSON(http.StatusOK,
responses.UserResponse{Status: http.StatusOK, Message: "success", Data: map[string]interface{}{"data": "User successfully deleted!"}},
)
}
}
func GetAllUsers() gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
var users []models.User
defer cancel()
results, err := userCollection.Find(ctx, bson.M{})
if err != nil {
c.JSON(http.StatusInternalServerError, responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}})
return
}
//reading from the db in an optimal way
defer results.Close(ctx)
for results.Next(ctx) {
var singleUser models.User
if err = results.Decode(&singleUser); err != nil {
c.JSON(http.StatusInternalServerError, responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}})
}
users = append(users, singleUser)
}
c.JSON(http.StatusOK,
responses.UserResponse{Status: http.StatusOK, Message: "success", Data: map[string]interface{}{"data": users}},
)
}
}
完成 user_route.go
package routes
import (
"gin-mongo-api/controllers"
"github.com/gin-gonic/gin"
)
func UserRoute(router *gin.Engine) {
router.POST("/user", controllers.CreateUser())
router.GET("/user/:userId", controllers.GetAUser())
router.PUT("/user/:userId", controllers.EditAUser())
router.DELETE("/user/:userId", controllers.DeleteAUser())
router.GET("/users", controllers.GetAllUsers())
}
完成上述步骤后,我们可以通过在终端中运行以下命令来启动开发服务器,从而测试我们的应用程序。
go run main.go
结论
本文讨论了如何构建 Gin-gonic 应用程序、构建 REST API 以及使用 MongoDB 持久化数据。
您可能会发现以下资源很有用:
文章来源:https://dev.to/hackmamba/build-a-rest-api-with-golang-and-mongodb-gin-gonic-version-269m





















