diff --git a/cmd/server/main.go b/cmd/server/main.go index 3a726dd..561f788 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -38,6 +38,7 @@ var ( AuthController controllers.AuthController AuthRouteController routes.AuthRouteController + // 👇 Add the Post Service, Controllers and Routes postService services.PostService PostController controllers.PostController postCollection *mongo.Collection @@ -92,6 +93,7 @@ func init() { UserController = controllers.NewUserController(userService) UserRouteController = routes.NewRouteUserController(UserController) + // 👇 Add the Post Service, Controllers and Routes postCollection = mongoclient.Database("golang_mongodb").Collection("posts") postService = services.NewPostService(postCollection, ctx) PostController = controllers.NewPostController(postService) @@ -164,6 +166,7 @@ func startGinServer(config config.Config) { AuthRouteController.AuthRoute(router, userService) UserRouteController.UserRoute(router, userService) + // 👇 Evoke the PostRoute PostRouteController.PostRoute(router) log.Fatal(server.Run(":" + config.Port)) } diff --git a/controllers/auth.controller.go b/controllers/auth.controller.go index ea3c8c2..34f77d7 100644 --- a/controllers/auth.controller.go +++ b/controllers/auth.controller.go @@ -63,7 +63,7 @@ func (ac *AuthController) SignUpUser(ctx *gin.Context) { verificationCode := utils.Encode(code) - updateData := &models.UpdateInput{ + updateData := &models.UserUpdateInput{ VerificationCode: verificationCode, } @@ -205,8 +205,6 @@ func (ac *AuthController) VerifyEmail(ctx *gin.Context) { return } - fmt.Println(result) - ctx.JSON(http.StatusOK, gin.H{"status": "success", "message": "Email verified successfully"}) } diff --git a/controllers/post.controller.go b/controllers/post.controller.go index 0ce8253..8362bce 100644 --- a/controllers/post.controller.go +++ b/controllers/post.controller.go @@ -69,6 +69,10 @@ func (pc *PostController) FindPostById(ctx *gin.Context) { post, err := pc.postService.FindPostById(postId) if err != nil { + if strings.Contains(err.Error(), "Id exists") { + ctx.JSON(http.StatusNotFound, gin.H{"status": "fail", "message": err.Error()}) + return + } ctx.JSON(http.StatusBadGateway, gin.H{"status": "fail", "message": err.Error()}) return } diff --git a/controllers/user.controller.go b/controllers/user.controller.go index f57fd3b..293a453 100644 --- a/controllers/user.controller.go +++ b/controllers/user.controller.go @@ -17,7 +17,7 @@ func NewUserController(userService services.UserService) UserController { } func (uc *UserController) GetMe(ctx *gin.Context) { - currentUser := ctx.MustGet("currentUser").(*models.DBResponse) + currentUser := ctx.MustGet("currentUser").(*models.UserDBResponse) - ctx.JSON(http.StatusOK, gin.H{"status": "success", "data": gin.H{"user": models.FilteredResponse(currentUser)}}) + ctx.JSON(http.StatusOK, gin.H{"status": "success", "data": gin.H{"user": models.FilteredUserResponse(currentUser)}}) } diff --git a/gapi/rpc_signup_user.go b/gapi/rpc_signup_user.go index 1682d7b..25c64f5 100644 --- a/gapi/rpc_signup_user.go +++ b/gapi/rpc_signup_user.go @@ -39,7 +39,7 @@ func (authServer *AuthServer) SignUpUser(ctx context.Context, req *pb.SignUpUser verificationCode := utils.Encode(code) - updateData := &models.UpdateInput{ + updateData := &models.UserUpdateInput{ VerificationCode: verificationCode, } diff --git a/models/user.model.go b/models/user.model.go index 690f287..709db99 100644 --- a/models/user.model.go +++ b/models/user.model.go @@ -27,8 +27,8 @@ type SignInInput struct { Password string `json:"password" bson:"password" binding:"required"` } -// 👈 DBResponse struct -type DBResponse struct { +// 👈 UserDBResponse struct +type UserDBResponse struct { ID primitive.ObjectID `json:"id" bson:"_id"` Name string `json:"name" bson:"name"` Email string `json:"email" bson:"email"` @@ -43,7 +43,7 @@ type DBResponse struct { UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` } -type UpdateInput struct { +type UserUpdateInput struct { Name string `json:"name,omitempty" bson:"name,omitempty"` Email string `json:"email,omitempty" bson:"email,omitempty"` Password string `json:"password,omitempty" bson:"password,omitempty"` @@ -77,7 +77,7 @@ type ResetPasswordInput struct { PasswordConfirm string `json:"passwordConfirm,omitempty" bson:"passwordConfirm,omitempty"` } -func FilteredResponse(user *DBResponse) UserResponse { +func FilteredUserResponse(user *UserDBResponse) UserResponse { return UserResponse{ ID: user.ID, Email: user.Email, diff --git a/readMe.md b/readMe.md index 56a6138..30863fe 100644 --- a/readMe.md +++ b/readMe.md @@ -1,4 +1,34 @@ -# API with Golang + MongoDB + Redis + Gin Gonic +# Build CRUD RESTful API Server with Golang, Gin, and MongoDB + +In this article, you'll learn how to build a CRUD RESTful API server with Golang, Gin Gonic, MongoDB-Go-driver, Docker, and Docker-compose. + +![Build CRUD RESTful API Server with Golang, Gin, and MongoDB](https://codevoweb.com/wp-content/uploads/2022/05/Build-CRUD-RESTful-API-Server-with-Golang-Gin-and-MongoDB.webp) + +## Topics Covered + +- Golang, Gin Gonic, MongoDB CRUD RESTful API Overview +- Create the Models with Structs +- Create the Service Interface +- Create Methods to Implement the Interface + - Initialize the Service Struct + - Define a Service to Create a Post + - Define a Service to Update Post + - Define a Service to Delete Post + - Define a Service to Get Single Post + - Define a Service to Get All Posts +- Create Controllers to Perform the CRUD Operations + - Initialize the Controller Struct + - Define a Controller to Create a Post + - Define a Controller to Update a Post + - Define a Controller to Delete a Post + - Define a Controller to Get a Single Post + - Define a Controller to Get All Posts +- Create the Routes for the Controllers +- Initialize the Constructors and Start the Gin Server + +Read the entire article here: [https://codevoweb.com/crud-restful-api-server-with-golang-and-mongodb](https://codevoweb.com/crud-restful-api-server-with-golang-and-mongodb) + +Articles in this series: ### 1. API with Golang + MongoDB + Redis + Gin Gonic: Project Setup @@ -19,3 +49,15 @@ ### 5. Build Golang gRPC Server and Client: SignUp User & Verify Email [Build Golang gRPC Server and Client: SignUp User & Verify Email](https://codevoweb.com/golang-grpc-server-and-client-signup-user-verify-email) + +### 6. Build Golang gRPC Server and Client: Access & Refresh Tokens + +[Build Golang gRPC Server and Client: Access & Refresh Tokens](https://codevoweb.com/golang-grpc-server-and-client-access-refresh-tokens) + +### 7. Build CRUD RESTful API Server with Golang, Gin, and MongoDB + +[Build CRUD RESTful API Server with Golang, Gin, and MongoDB](https://codevoweb.com/crud-restful-api-server-with-golang-and-mongodb) + +### 8. Build CRUD gRPC Server API & Client with Golang and MongoDB + +[Build CRUD gRPC Server API & Client with Golang and MongoDB](https://codevoweb.com/crud-grpc-server-api-client-with-golang-and-mongodb) diff --git a/services/auth.service.go b/services/auth.service.go index eaa9afe..42b39d3 100644 --- a/services/auth.service.go +++ b/services/auth.service.go @@ -3,6 +3,6 @@ package services import "github.com/wpcodevo/golang-mongodb/models" type AuthService interface { - SignUpUser(*models.SignUpInput) (*models.DBResponse, error) - SignInUser(*models.SignInInput) (*models.DBResponse, error) + SignUpUser(*models.SignUpInput) (*models.UserDBResponse, error) + SignInUser(*models.SignInInput) (*models.UserDBResponse, error) } diff --git a/services/auth.service.impl.go b/services/auth.service.impl.go index d144fb4..98e6078 100644 --- a/services/auth.service.impl.go +++ b/services/auth.service.impl.go @@ -22,7 +22,7 @@ func NewAuthService(collection *mongo.Collection, ctx context.Context) AuthServi return &AuthServiceImpl{collection, ctx} } -func (uc *AuthServiceImpl) SignUpUser(user *models.SignUpInput) (*models.DBResponse, error) { +func (uc *AuthServiceImpl) SignUpUser(user *models.SignUpInput) (*models.UserDBResponse, error) { user.CreatedAt = time.Now() user.UpdatedAt = user.CreatedAt user.Email = strings.ToLower(user.Email) @@ -50,7 +50,7 @@ func (uc *AuthServiceImpl) SignUpUser(user *models.SignUpInput) (*models.DBRespo return nil, errors.New("could not create index for email") } - var newUser *models.DBResponse + var newUser *models.UserDBResponse query := bson.M{"_id": res.InsertedID} err = uc.collection.FindOne(uc.ctx, query).Decode(&newUser) @@ -61,6 +61,6 @@ func (uc *AuthServiceImpl) SignUpUser(user *models.SignUpInput) (*models.DBRespo return newUser, nil } -func (uc *AuthServiceImpl) SignInUser(*models.SignInInput) (*models.DBResponse, error) { +func (uc *AuthServiceImpl) SignInUser(*models.SignInInput) (*models.UserDBResponse, error) { return nil, nil } diff --git a/services/post.service.impl.go b/services/post.service.impl.go index 47d1563..7881fd1 100644 --- a/services/post.service.impl.go +++ b/services/post.service.impl.go @@ -61,19 +61,12 @@ func (p *PostServiceImpl) UpdatePost(id string, data *models.UpdatePost) (*model obId, _ := primitive.ObjectIDFromHex(id) query := bson.D{{Key: "_id", Value: obId}} update := bson.D{{Key: "$set", Value: doc}} - res, err := p.postCollection.UpdateOne(p.ctx, query, update) - if err != nil { - return nil, err - } - - if res.MatchedCount == 0 { - return nil, errors.New("no post with that Id exists") - } + res := p.postCollection.FindOneAndUpdate(p.ctx, query, update, options.FindOneAndUpdate().SetReturnDocument(1)) var updatedPost *models.DBPost - if err = p.postCollection.FindOne(p.ctx, query).Decode(&updatedPost); err != nil { - return nil, err + if err := res.Decode(&updatedPost); err != nil { + return nil, errors.New("no post with that Id exists") } return updatedPost, nil @@ -98,6 +91,14 @@ func (p *PostServiceImpl) FindPostById(id string) (*models.DBPost, error) { } func (p *PostServiceImpl) FindPosts(page int, limit int) ([]*models.DBPost, error) { + if page == 0 { + page = 1 + } + + if limit == 0 { + limit = 10 + } + skip := (page - 1) * limit opt := options.FindOptions{} @@ -115,7 +116,7 @@ func (p *PostServiceImpl) FindPosts(page int, limit int) ([]*models.DBPost, erro var posts []*models.DBPost - if cursor.Next(p.ctx) { + for cursor.Next(p.ctx) { post := &models.DBPost{} err := cursor.Decode(post) diff --git a/services/user.service.go b/services/user.service.go index cc428ad..60e3189 100644 --- a/services/user.service.go +++ b/services/user.service.go @@ -3,7 +3,7 @@ package services import "github.com/wpcodevo/golang-mongodb/models" type UserService interface { - FindUserById(id string) (*models.DBResponse, error) - FindUserByEmail(email string) (*models.DBResponse, error) - UpdateUserById(id string, data *models.UpdateInput) (*models.DBResponse, error) + FindUserById(id string) (*models.UserDBResponse, error) + FindUserByEmail(email string) (*models.UserDBResponse, error) + UpdateUserById(id string, data *models.UserUpdateInput) (*models.UserDBResponse, error) } diff --git a/services/user.service.impl.go b/services/user.service.impl.go index 081035b..609416a 100644 --- a/services/user.service.impl.go +++ b/services/user.service.impl.go @@ -2,7 +2,7 @@ package services import ( "context" - "fmt" + "errors" "strings" "github.com/wpcodevo/golang-mongodb/models" @@ -10,6 +10,7 @@ import ( "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" ) type UserServiceImpl struct { @@ -21,17 +22,17 @@ func NewUserServiceImpl(collection *mongo.Collection, ctx context.Context) UserS return &UserServiceImpl{collection, ctx} } -func (us *UserServiceImpl) FindUserById(id string) (*models.DBResponse, error) { +func (us *UserServiceImpl) FindUserById(id string) (*models.UserDBResponse, error) { oid, _ := primitive.ObjectIDFromHex(id) - var user *models.DBResponse + var user *models.UserDBResponse query := bson.M{"_id": oid} err := us.collection.FindOne(us.ctx, query).Decode(&user) if err != nil { if err == mongo.ErrNoDocuments { - return &models.DBResponse{}, err + return &models.UserDBResponse{}, err } return nil, err } @@ -39,15 +40,15 @@ func (us *UserServiceImpl) FindUserById(id string) (*models.DBResponse, error) { return user, nil } -func (us *UserServiceImpl) FindUserByEmail(email string) (*models.DBResponse, error) { - var user *models.DBResponse +func (us *UserServiceImpl) FindUserByEmail(email string) (*models.UserDBResponse, error) { + var user *models.UserDBResponse query := bson.M{"email": strings.ToLower(email)} err := us.collection.FindOne(us.ctx, query).Decode(&user) if err != nil { if err == mongo.ErrNoDocuments { - return &models.DBResponse{}, err + return &models.UserDBResponse{}, err } return nil, err } @@ -55,24 +56,22 @@ func (us *UserServiceImpl) FindUserByEmail(email string) (*models.DBResponse, er return user, nil } -func (uc *UserServiceImpl) UpdateUserById(id string, data *models.UpdateInput) (*models.DBResponse, error) { +func (uc *UserServiceImpl) UpdateUserById(id string, data *models.UserUpdateInput) (*models.UserDBResponse, error) { doc, err := utils.ToDoc(data) if err != nil { - return &models.DBResponse{}, err + return &models.UserDBResponse{}, err } - fmt.Println(data) - obId, _ := primitive.ObjectIDFromHex(id) query := bson.D{{Key: "_id", Value: obId}} update := bson.D{{Key: "$set", Value: doc}} - result, err := uc.collection.UpdateOne(uc.ctx, query, update) + result := uc.collection.FindOneAndUpdate(uc.ctx, query, update, options.FindOneAndUpdate().SetReturnDocument(1)) - fmt.Print(result.ModifiedCount) - if err != nil { - return &models.DBResponse{}, err + var updatedUser *models.UserDBResponse + if err := result.Decode(&updatedUser); err != nil { + return nil, errors.New("no user with that id exists") } - return &models.DBResponse{}, nil + return updatedUser, nil } diff --git a/tmp/build-errors.log b/tmp/build-errors.log index 85600e2..8cac00d 100644 --- a/tmp/build-errors.log +++ b/tmp/build-errors.log @@ -1 +1 @@ -exit status 1exit status 0xc000013aexit status 1exit status 1exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 1exit status 1exit status 2exit status 1 \ No newline at end of file +exit status 1exit status 0xc000013aexit status 1exit status 1exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 1exit status 1exit status 2exit status 1exit status 0xc000013a \ No newline at end of file diff --git a/utils/email.go b/utils/email.go index 4fabae8..d20002d 100644 --- a/utils/email.go +++ b/utils/email.go @@ -44,7 +44,7 @@ func ParseTemplateDir(dir string) (*template.Template, error) { return template.ParseFiles(paths...) } -func SendEmail(user *models.DBResponse, data *EmailData, templateName string) error { +func SendEmail(user *models.UserDBResponse, data *EmailData, templateName string) error { config, err := config.LoadConfig(".") if err != nil {