Representational state transfer (REST) is an architectural pattern that guides an Application programming interface(API) design and development. REST APIs have become the standard of communication between the server part of the product and its client to increase performance, scalability, simplicity, modifiability, visibility, portability, and reliability.
This post will discuss building a user management application with Golang using the Mux package and MongoDB. At the end of this tutorial, we will learn how to structure a Mux application, build a REST API and persist our data using MongoDB.
Gorilla/Mux, popularly known as Mux, is a powerful HTTP router and URL matcher for building Go web servers.
MongoDB is a document-based database management program used as an alternative to relational databases. MongoDB supports working with large sets of distributed data with options to store or retrieve information seamlessly.
You can find the complete source code in this repository.
Prerequisites
The following steps in this post require Golang experience. Experience with MongoDB isnโt a requirement, but itโs nice to have.
We will also be needing the following:
- A MongoDB account to host database. Signup is completely free.
- Postman or any API testing application of your choice
Letโs code
Getting Started
To get started, we need to navigate to the desired directory and run the command below in our terminal
mkdir mux-mongo-api && cd mux-mongo-api
This command creates a mux-mongo-api
folder and navigates into the project directory.
Next, we need to initialize a Go module to manage project dependencies by running the command below:
go mod init mux-mongo-api
This command will create a go.mod
file for tracking project dependencies.
We proceed to install the required dependencies with:
go get -u github.com/gorilla/mux go.mongodb.org/mongo-driver/mongo github.com/joho/godotenv github.com/go-playground/validator/v10
github.com/gorilla/mux
is a package for building web servers.
go.mongodb.org/mongo-driver/mongo
is a driver for connecting to MongoDB.
github.com/joho/godotenv
is a library for managing environment variable.
github.com/go-playground/validator/v10
is a library for validating structs and fields.
Application Entry Point
With the project dependencies installed, we need to create main.go
file in the root directory and add the snippet below:
package main
import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
)
func main() {
router := mux.NewRouter()
router.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
json.NewEncoder(rw).Encode(map[string]string{"data": "Hello from Mux & mongoDB"})
}).Methods("GET")
log.Fatal(http.ListenAndServe(":6000", router))
}
The snippet above does the following:
- Import the required dependencies.
- Initialize a Mux router using the
NewRouter
function. - Use the
HandleFunc
function that usesnet/http
package as parameters to route to/
path and a handler function that sets the header type to a JSON and returns a JSON ofHello from Mux & mongoDB
using theencoding/json
package. We also attached the HTTP method to this function - Use the
http.ListenAndServe
function to run the application on port6000
.
Next, we can test our application by starting the development server by running the command below in our terminal.
go run main.go
Modularization in Golang
It is essential to have a good folder structure for our project. Good project structure simplifies how we work with dependencies in our application and makes it easier for us and others to read our codebase.
To do this, we need to create configs
, controllers
, models
, responses
and routes
folder in our project directory.
PS: The go.sum
file contains all the dependency checksums, and is managed by the go
tools. We donโt have to worry about it.
configs
is for modularizing project configuration files
controllers
is for modularizing application logics.
models
is for modularizing data and database logics.
responses
is for modularizing files describing the response we want our API to give. This will become clearer later on.
routes
is for modularizing URL pattern and handler information.
Setting up MongoDB
With that done, we need to log in or sign up into our MongoDB account. Click the project dropdown menu and click on the New Project button.
Enter the golang-api
as the project name, click Next, and click Create Project..
Click on Build a Database
Select Shared as the type of database.
Click on Create to setup a cluster. This might take sometime to setup.
Next, we need to create a user to access the database externally by inputting the Username, Password and then clicking on Create User. We also need to add our IP address to safely connect to the database by clicking on the Add My Current IP Address button. Then click on Finish and Close to save changes.
On saving the changes, we should see a Database Deployments screen, as shown below:
Connecting our application to MongoDB
With the configuration done, we need to connect our application with the database created. To do this, click on the Connect button
Click on Connect your application, change the Driver to Go
and the Version as shown below. Then click on the copy icon to copy the connection string.
Setup Environment Variable
Next, we must modify the copied connection string with the user's password we created earlier and change the database name. To do this, first, we need to create a .env
file in the root directory, and in this file, add the snippet below:
MONGOURI=mongodb+srv://<YOUR USERNAME HERE>:<YOUR PASSWORD HERE>@cluster0.e5akf.mongodb.net/myFirstDatabese?retryWrites=true&w=majority
Sample of a properly filled connection string below:
MONGOURI=mongodb+srv://malomz:malomzPassword@cluster0.e5akf.mongodb.net/golangDB?retryWrites=true&w=majority
Load Environment Variable
With that done, we need to create a helper function to load the environment variable using the github.com/joho/godotenv
library we installed earlier. To do this, we need to navigate to the configs
folder and in this folder, create an env.go
file and add the snippet below:
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")
}
The snippet above does the following:
- Import the required dependencies.
- Create an
EnvMongoURI
function that checks if the environment variable is correctly loaded and returns the environment variable.
Connecting to MongoDB
To connect to the MongoDB database from our application, first we need to navigate to the configs
folder and in this folder, create a setup.go
file and add the snippet below:
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
}
The snippet above does the following:
- Import the required dependencies.
- Create a
ConnectDB
function that first configures the client to use the correct URI and check for errors. Secondly, we defined a timeout of 10 seconds we wanted to use when trying to connect. Thirdly, check if there is an error while connecting to the database and cancel the connection if the connecting period exceeds 10 seconds. Finally, we pinged the database to test our connection and returned theclient
instance. - Create a
DB
variable instance of theConnectDB
. This will come in handy when creating collections. - Create a
GetCollection
function to retrieve and createcollections
on the database.
Next, we need to connect to the database when our application startup. To do this, we need to modify main.go
as shown below:
package main
import (
"log"
"mux-mongo-api/configs" //add this
"net/http"
"github.com/gorilla/mux"
)
func main() {
router := mux.NewRouter()
//run database
configs.ConnectDB()
log.Fatal(http.ListenAndServe(":6000", router))
}
Setup API Route Handler and Response Type
Route Handler
With that done, we need to create a user_route.go
file inside the routes
folder to manage all the user-related routes in our application, as shown below:
package routes
import "github.com/gorilla/mux"
func UserRoute(router *mux.Router) {
//All routes related to users comes here
}
Next, we need to attach the newly created route to the http.Server in main.go
by modifying it as shown below:
package main
import (
"log"
"mux-mongo-api/configs"
"mux-mongo-api/routes" //add this
"net/http"
"github.com/gorilla/mux"
)
func main() {
router := mux.NewRouter()
//run database
configs.ConnectDB()
//routes
routes.UserRoute(router) //add this
log.Fatal(http.ListenAndServe(":6000", router))
}
Response Type
Next, we need to create a reusable struct
to describe our APIโs response. To do this, navigate to the responses
folder and in this folder, create a user_response.go
file and add the snippet below:
package responses
type UserResponse struct {
Status int `json:"status"`
Message string `json:"message"`
Data map[string]interface{} `json:"data"`
}
The snippet above creates a UserResponse
struct with Status
, Message
, and Data
property to represent the API response type.
PS: json:"status"
, json:"message"
, and json:"data"
are known as struct tags. Struct tags allow us to attach meta-information to corresponding struct properties. In other words, we use them to reformat the JSON response returned by the API.
Finally, Creating REST APIโs
Next, we need a model to represent our application data. To do this, we need to navigate to the models
folder, and in this folder, create a user_model.go
file and add the snippet below:
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"`
}
The snippet above does the following:
- Import the required dependencies.
- Create a
User
struct with required properties. We addedomitempty
andvalidate:"required"
to the struct tag to tell Fiber to ignore empty fields and make the field required, respectively.
Create a User Endpoint
With the model setup, we can now create a function to create a user. To do this, we need to navigate to the controllers
folder, and in this folder, create a user_controller.go
file and add the snippet below:
package controllers
import (
"context"
"encoding/json"
"mux-mongo-api/configs"
"mux-mongo-api/models"
"mux-mongo-api/responses"
"net/http"
"time"
"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() http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
var user models.User
defer cancel()
//validate the request body
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
rw.WriteHeader(http.StatusBadRequest)
response := responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: map[string]interface{}{"data": err.Error()}}
json.NewEncoder(rw).Encode(response)
return
}
//use the validator library to validate required fields
if validationErr := validate.Struct(&user); validationErr != nil {
rw.WriteHeader(http.StatusBadRequest)
response := responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: map[string]interface{}{"data": validationErr.Error()}}
json.NewEncoder(rw).Encode(response)
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 {
rw.WriteHeader(http.StatusInternalServerError)
response := responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}}
json.NewEncoder(rw).Encode(response)
return
}
rw.WriteHeader(http.StatusCreated)
response := responses.UserResponse{Status: http.StatusCreated, Message: "success", Data: map[string]interface{}{"data": result}}
json.NewEncoder(rw).Encode(response)
}
}
The snippet above does the following:
- Import the required dependencies.
- Create
userCollection
andvalidate
variables to create a collection and validate models using thegithub.com/go-playground/validator/v10
library we installed earlier on, respectively. - Create a
CreateUser
function that returns anet/http
handler. Inside the returned handler, we first defined a timeout of 10 seconds when inserting user into the document, validating both the request body and required field using the validator library. We returned the appropriate message and status code using theUserResponse
struct we created earlier. Secondly, we created anewUser
variable, inserted it using theuserCollection.InsertOne
function and check for errors if there are any. Finally, we returned the correct response if the insert was successful.-
w.WriteHeader
function is used for setting the API status code. -
json.NewDecoder
โsDecode
andEncode
methods are used to convert JSON into a Go value and vice-versa.
-
Next, we need to update user_routes.go
with the route API URL and corresponding controller as shown below:
package routes
import (
"mux-mongo-api/controllers" //add this
"github.com/gorilla/mux"
)
func UserRoute(router *mux.Router) {
router.HandleFunc("/user", controllers.CreateUser()).Methods("POST") //add this
}
`
Get a User Endpoint
To get the details of a user, we need to modify user_controller.go
as shown below:
`go
package controllers
import (
//other import goes here
"github.com/gorilla/mux" //add this
"go.mongodb.org/mongo-driver/bson" //add this
)
var userCollection *mongo.Collection = configs.GetCollection(configs.DB, "users")
var validate = validator.New()
func CreateUser() http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
//create a user code goes here
}
}
func GetAUser() http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
params := mux.Vars(r)
userId := params["userId"]
var user models.User
defer cancel()
objId, _ := primitive.ObjectIDFromHex(userId)
err := userCollection.FindOne(ctx, bson.M{"id": objId}).Decode(&user)
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
response := responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}}
json.NewEncoder(rw).Encode(response)
return
}
rw.WriteHeader(http.StatusOK)
response := responses.UserResponse{Status: http.StatusOK, Message: "success", Data: map[string]interface{}{"data": user}}
json.NewEncoder(rw).Encode(response)
}
}
`
The snippet above does the following:
- Import the required dependencies.
- Create a
GetAUser
function that returns anet/http
handler. Inside the returned handler, we first defined a timeout of 10 seconds when finding a user in the document, auserId
variable to get the userโs id from the URL parameter and auser
variable. We converted theuserId
from a string to aprimitive.ObjectID
type, a BSON type MongoDB uses. Secondly, we searched for the user using theuserCollection.FindOne
, pass theobjId
as a filter and use theDecode
attribute method to get the corresponding object. Finally, we returned the decoded response.
Next, we need to update user_routes.go
with the route API URL and corresponding controller as shown below:
`go
package routes
import (
//import goes here
)
func UserRoute(router *mux.Router) {
//other routes goes here
router.HandleFunc("/user/{userId}", controllers.GetAUser()).Methods("GET") //add this
}
`
PS: We also passed a userId
as a parameter to the URL path. The specified parameter must match the one we specified in the controller.
Edit a User Endpoint
To edit a user, we need to modify user_controller.go
as shown below:
`go
package controllers
import (
//other import goes here
)
var userCollection *mongo.Collection = configs.GetCollection(configs.DB, "users")
var validate = validator.New()
func CreateUser() http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
//create a user code goes here
}
}
func GetAUser() http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
//get a user code goes here
}
}
func EditAUser() http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
params := mux.Vars(r)
userId := params["userId"]
var user models.User
defer cancel()
objId, _ := primitive.ObjectIDFromHex(userId)
//validate the request body
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
rw.WriteHeader(http.StatusBadRequest)
response := responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: map[string]interface{}{"data": err.Error()}}
json.NewEncoder(rw).Encode(response)
return
}
//use the validator library to validate required fields
if validationErr := validate.Struct(&user); validationErr != nil {
rw.WriteHeader(http.StatusBadRequest)
response := responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: map[string]interface{}{"data": validationErr.Error()}}
json.NewEncoder(rw).Encode(response)
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 {
rw.WriteHeader(http.StatusInternalServerError)
response := responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}}
json.NewEncoder(rw).Encode(response)
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 {
rw.WriteHeader(http.StatusInternalServerError)
response := responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}}
json.NewEncoder(rw).Encode(response)
return
}
}
rw.WriteHeader(http.StatusOK)
response := responses.UserResponse{Status: http.StatusOK, Message: "success", Data: map[string]interface{}{"data": updatedUser}}
json.NewEncoder(rw).Encode(response)
}
}
`
The EditAUser
function above does the same thing as the CreateUser
function. However, we included an update
variable to get updated fields and updated the collection using the userCollection.UpdateOne
. Lastly, we searched for the updated userโs details and returned the decoded response.
Next, we need to update user_routes.go
with the route API URL and corresponding controller as shown below:
`go
package routes
import (
//import goes here
)
func UserRoute(router *mux.Router) {
//other routes goes here
router.HandleFunc("/user/{userId}", controllers.EditAUser()).Methods("PUT") //add this
}
`
Delete a User Endpoint
To delete a user, we need to modify user_controller.go
as shown below:
`go
package controllers
import (
//other import goes here
)
var userCollection *mongo.Collection = configs.GetCollection(configs.DB, "users")
var validate = validator.New()
func CreateUser() http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
//create a user code goes here
}
}
func GetAUser() http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
//get a user code goes here
}
}
func EditAUser() http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
//edit a user code goes here
}
}
func DeleteAUser() http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
params := mux.Vars(r)
userId := params["userId"]
defer cancel()
objId, _ := primitive.ObjectIDFromHex(userId)
result, err := userCollection.DeleteOne(ctx, bson.M{"id": objId})
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
response := responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}}
json.NewEncoder(rw).Encode(response)
return
}
if result.DeletedCount < 1 {
rw.WriteHeader(http.StatusNotFound)
response := responses.UserResponse{Status: http.StatusNotFound, Message: "error", Data: map[string]interface{}{"data": "User with specified ID not found!"}}
json.NewEncoder(rw).Encode(response)
return
}
rw.WriteHeader(http.StatusOK)
response := responses.UserResponse{Status: http.StatusOK, Message: "success", Data: map[string]interface{}{"data": "User successfully deleted!"}}
json.NewEncoder(rw).Encode(response)
}
}
`
The DeleteAUser
function follows the previous steps by deleting the matched record using the userCollection.DeleteOne
. We also checked if an item was successfully deleted and returned the appropriate response.
Next, we need to update user_routes.go
with the route API URL and corresponding controller as shown below:
`go
package routes
import (
//import goes here
)
func UserRoute(router *mux.Router) {
//other routes goes here
router.HandleFunc("/user/{userId}", controllers.DeleteAUser()).Methods("DELETE") //add this
}
`
Get List of Users Endpoint
To get the list of users, we need to modify user_controller.go
as shown below:
`go
package controllers
import (
//other import goes here
)
var userCollection *mongo.Collection = configs.GetCollection(configs.DB, "users")
var validate = validator.New()
func CreateUser() http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
//create a user code goes here
}
}
func GetAUser() http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
//get a user code goes here
}
}
func EditAUser() http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
//edit a user code goes here
}
}
func DeleteAUser() http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
//delete a user code goes here
}
}
func GetAllUser() http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
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 {
rw.WriteHeader(http.StatusInternalServerError)
response := responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}}
json.NewEncoder(rw).Encode(response)
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 {
rw.WriteHeader(http.StatusInternalServerError)
response := responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}}
json.NewEncoder(rw).Encode(response)
}
users = append(users, singleUser)
}
rw.WriteHeader(http.StatusOK)
response := responses.UserResponse{Status: http.StatusOK, Message: "success", Data: map[string]interface{}{"data": users}}
json.NewEncoder(rw).Encode(response)
}
}
`
The GetAllUsers
function follows the previous steps by getting the list of users using the userCollection.Find
. We also read the retuned list optimally using the Next
attribute method to loop through the returned list of users.
Next, we need to update user_routes.go
with the route API URL and corresponding controller as shown below:
`go
package routes
import (
//import goes here
)
func UserRoute(router *mux.Router) {
//other routes goes here
router.HandleFunc("/users", controllers.GetAllUser()).Methods("GET") //add this
}
`
Complete user_controller.go
`go
package controllers
import (
"context"
"encoding/json"
"mux-mongo-api/configs"
"mux-mongo-api/models"
"mux-mongo-api/responses"
"net/http"
"time"
"github.com/go-playground/validator/v10"
"github.com/gorilla/mux"
"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() http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
var user models.User
defer cancel()
//validate the request body
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
rw.WriteHeader(http.StatusBadRequest)
response := responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: map[string]interface{}{"data": err.Error()}}
json.NewEncoder(rw).Encode(response)
return
}
//use the validator library to validate required fields
if validationErr := validate.Struct(&user); validationErr != nil {
rw.WriteHeader(http.StatusBadRequest)
response := responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: map[string]interface{}{"data": validationErr.Error()}}
json.NewEncoder(rw).Encode(response)
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 {
rw.WriteHeader(http.StatusInternalServerError)
response := responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}}
json.NewEncoder(rw).Encode(response)
return
}
rw.WriteHeader(http.StatusCreated)
response := responses.UserResponse{Status: http.StatusCreated, Message: "success", Data: map[string]interface{}{"data": result}}
json.NewEncoder(rw).Encode(response)
}
}
func GetAUser() http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
params := mux.Vars(r)
userId := params["userId"]
var user models.User
defer cancel()
objId, _ := primitive.ObjectIDFromHex(userId)
err := userCollection.FindOne(ctx, bson.M{"id": objId}).Decode(&user)
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
response := responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}}
json.NewEncoder(rw).Encode(response)
return
}
rw.WriteHeader(http.StatusOK)
response := responses.UserResponse{Status: http.StatusOK, Message: "success", Data: map[string]interface{}{"data": user}}
json.NewEncoder(rw).Encode(response)
}
}
func EditAUser() http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
params := mux.Vars(r)
userId := params["userId"]
var user models.User
defer cancel()
objId, _ := primitive.ObjectIDFromHex(userId)
//validate the request body
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
rw.WriteHeader(http.StatusBadRequest)
response := responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: map[string]interface{}{"data": err.Error()}}
json.NewEncoder(rw).Encode(response)
return
}
//use the validator library to validate required fields
if validationErr := validate.Struct(&user); validationErr != nil {
rw.WriteHeader(http.StatusBadRequest)
response := responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: map[string]interface{}{"data": validationErr.Error()}}
json.NewEncoder(rw).Encode(response)
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 {
rw.WriteHeader(http.StatusInternalServerError)
response := responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}}
json.NewEncoder(rw).Encode(response)
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 {
rw.WriteHeader(http.StatusInternalServerError)
response := responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}}
json.NewEncoder(rw).Encode(response)
return
}
}
rw.WriteHeader(http.StatusOK)
response := responses.UserResponse{Status: http.StatusOK, Message: "success", Data: map[string]interface{}{"data": updatedUser}}
json.NewEncoder(rw).Encode(response)
}
}
func DeleteAUser() http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
params := mux.Vars(r)
userId := params["userId"]
defer cancel()
objId, _ := primitive.ObjectIDFromHex(userId)
result, err := userCollection.DeleteOne(ctx, bson.M{"id": objId})
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
response := responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}}
json.NewEncoder(rw).Encode(response)
return
}
if result.DeletedCount < 1 {
rw.WriteHeader(http.StatusNotFound)
response := responses.UserResponse{Status: http.StatusNotFound, Message: "error", Data: map[string]interface{}{"data": "User with specified ID not found!"}}
json.NewEncoder(rw).Encode(response)
return
}
rw.WriteHeader(http.StatusOK)
response := responses.UserResponse{Status: http.StatusOK, Message: "success", Data: map[string]interface{}{"data": "User successfully deleted!"}}
json.NewEncoder(rw).Encode(response)
}
}
func GetAllUser() http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
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 {
rw.WriteHeader(http.StatusInternalServerError)
response := responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}}
json.NewEncoder(rw).Encode(response)
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 {
rw.WriteHeader(http.StatusInternalServerError)
response := responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: map[string]interface{}{"data": err.Error()}}
json.NewEncoder(rw).Encode(response)
}
users = append(users, singleUser)
}
rw.WriteHeader(http.StatusOK)
response := responses.UserResponse{Status: http.StatusOK, Message: "success", Data: map[string]interface{}{"data": users}}
json.NewEncoder(rw).Encode(response)
}
}
`
Complete user_route.go
`go
package routes
import (
"mux-mongo-api/controllers"
"github.com/gorilla/mux"
)
func UserRoute(router *mux.Router) {
router.HandleFunc("/user", controllers.CreateUser()).Methods("POST")
router.HandleFunc("/user/{userId}", controllers.GetAUser()).Methods("GET")
router.HandleFunc("/user/{userId}", controllers.EditAUser()).Methods("PUT")
router.HandleFunc("/user/{userId}", controllers.DeleteAUser()).Methods("DELETE")
router.HandleFunc("/users", controllers.GetAllUser()).Methods("GET")
}
`
With that done, we can test our application by starting the development server by running the command below in our terminal.
go run main.go
Conclusion
This post discussed how to structure a Gin-gonic application, build a REST API and persist our data using MongoDB.
You may find these resources helpful:
Top comments (4)
That was a great article. Thank you for the detailed tutorial. I learnt the basics and implemented at my end. I think there is something wrong with the rendering of code snippets. You might have misplaced a backtick.
Thank you so much Demola.
I used this article: dev.to/yasaricli/getting-mongodb-i...
So, I changed the "Id" in the model to:
Id primitive.ObjectID
bson:"_id" json:"id,omitempty"
then I changed "id" for "_id" in user_controller.go
Now it works for me
Thanks, i'm learing mongo and Go and this really helped me get started on a new project!
One issue I found is with the ID's, I was not able to locate records by the ID returned in POST response. Looking in the DB I could see i had an _id and an id. I guess _id is auto generated by Mongo, so you don't need to generate it in Go when creating the record.
So, i changed the Id in the model to:
Id primitive.ObjectID
**bson**:"**_id**,omitempty"
Removed this line which was generating the id in the Create function:
Id: primitive.NewObjectID(),
Then changed all the objId references in the Get, Delete, Edit functions to:
bson.M{"_id": objId}
It now works as expected.
Nice! Glad it helped