Building CRUD REST APIs using Golang and MongoDB

Introduction

In this tutorial, we will learn how to build CRUD (Create, Read, Update, Delete) REST APIs using Golang and a MongoDB database. We'll cover everything from setting up the environment to creating and testing the APIs. By the end of this tutorial, you will have a working CRUD API that can interact with a MongoDB database.

Installation

Prerequisites

  1. Golang (latest version): Download and install Go
  2. MongoDB (latest version): Download and install MongoDB
  3. Postman or curl for testing the APIs: Download Postman

Setting Up the Environment

  1. Install Golang: Download and install Go

  2. Install MongoDB: Follow the instructions for your operating system to install MongoDB.

  3. Create a database and collection:

    mongo
    use go_crud_api
    db.createCollection("users")
    

Steps to Build the REST APIs

Step 1: Initialize the Go Module

mkdir go-crud-api
cd go-crud-api
go mod init go-crud-api

Explanation: This initializes a new Go module in the go-crud-api directory. This is important for managing dependencies.

Step 2: Install Required Packages

go get -u github.com/gin-gonic/gin
go get -u go.mongodb.org/mongo-driver/mongo
go get -u go.mongodb.org/mongo-driver/mongo/options

Explanation: The gin-gonic/gin package is a web framework for building APIs in Go, and mongo and options are part of the MongoDB driver for Go.

Step 3: Create the Main Application File

Create a file named main.go and add the following code:

package main

import (
    "context"
    "log"
    "net/http"
    "time"

    "github.com/gin-gonic/gin"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
    "go.mongodb.org/mongo-driver/bson/primitive"
    "go.mongodb.org/mongo-driver/bson"
)

var client *mongo.Client

func init() {
    // Set client options
    clientOptions := options.Client().ApplyURI("mongodb://localhost:27017")

    // Connect to MongoDB
    var err error
    client, err = mongo.Connect(context.TODO(), clientOptions)
    if err != nil {
        log.Fatal(err)
    }

    // Check the connection
    err = client.Ping(context.TODO(), nil)
    if err != nil {
        log.Fatal(err)
    }

    log.Println("Connected to MongoDB!")
}

func main() {
    router := gin.Default()

    router.POST("/users", createUser)
    router.GET("/users", getUsers)
    router.GET("/users/:id", getUserByID)
    router.PUT("/users/:id", updateUser)
    router.DELETE("/users/:id", deleteUser)

    router.Run(":8080")
}

Explanation: This code initializes the MongoDB client, sets up the Gin router, and defines the API endpoints and their handlers.

Step 4: Define the User Model and Handlers

Define the User Model

Create a file named models.go and add the following code:

package main

import "go.mongodb.org/mongo-driver/bson/primitive"

// User represents the user model
type User struct {
    ID    primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"`
    Name  string             `json:"name,omitempty" bson:"name,omitempty"`
    Email string             `json:"email,omitempty" bson:"email,omitempty"`
    Age   int                `json:"age,omitempty" bson:"age,omitempty"`
}

Explanation: This defines the User struct, which represents the structure of our user data. The ID field uses MongoDB's ObjectID type.

Implement the Handlers

Update main.go with the following code:

// createUser handles the creation of a new user
func createUser(c *gin.Context) {
    var user User
    // Bind the incoming JSON payload to the user struct
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    collection := client.Database("go_crud_api").Collection("users")
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    result, err := collection.InsertOne(ctx, user)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    user.ID = result.InsertedID.(primitive.ObjectID)
    c.JSON(http.StatusCreated, user)
}

// getUsers handles the retrieval of all users
func getUsers(c *gin.Context) {
    var users []User
    collection := client.Database("go_crud_api").Collection("users")
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    cursor, err := collection.Find(ctx, bson.M{})
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    defer cursor.Close(ctx)

    for cursor.Next(ctx) {
        var user User
        cursor.Decode(&user)
        users = append(users, user)
    }

    if err := cursor.Err(); err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, users)
}

// getUserByID handles the retrieval of a user by ID
func getUserByID(c *gin.Context) {
    id, err := primitive.ObjectIDFromHex(c.Param("id"))
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
        return
    }

    var user User
    collection := client.Database("go_crud_api").Collection("users")
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    err = collection.FindOne(ctx, bson.M{"_id": id}).Decode(&user)
    if err != nil {
        if err == mongo.ErrNoDocuments {
            c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
        } else {
            c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        }
        return
    }

    c.JSON(http.StatusOK, user)
}

// updateUser handles the update of an existing user
func updateUser(c *gin.Context) {
    id, err := primitive.ObjectIDFromHex(c.Param("id"))
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
        return
    }

    var user User
    // Bind the incoming JSON payload to the user struct
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    collection := client.Database("go_crud_api").Collection("users")
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    update := bson.M{
        "$set": user,
    }
    _, err = collection.UpdateOne(ctx, bson.M{"_id": id}, update)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    user.ID = id
    c.JSON(http.StatusOK, user)
}

// deleteUser handles the deletion of a user by ID
func deleteUser(c *gin.Context) {
    id, err := primitive.ObjectIDFromHex(c.Param("id"))
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
        return
    }

    collection := client.Database("go_crud_api").Collection("users")
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    _, err = collection.DeleteOne(ctx, bson.M{"_id": id})
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{"message": "User deleted"})
}

Explanation: This code implements the handlers for creating, retrieving, updating, and deleting users. Each handler interacts with the MongoDB collection to perform the necessary operations.

Explanation of Each REST API

Create User

  • Method: POST
  • URL: /users
  • Description: This endpoint creates a new user in the database. The user information is sent as a JSON payload in the request body.
  • Handler Function: createUser
    • Binds the incoming JSON payload to the User struct.
    • Inserts the user into the database.
    • Returns the created user with a status code of 201 (Created).

Get All Users

  • Method: GET
  • URL: /users
  • Description: This endpoint retrieves all users from the database.
  • Handler Function: getUsers
    • Queries the database for all users.
    • Appends each user to a slice of users.
    • Returns the list of users with a status code of 200 (OK).

Get User by ID

  • Method: GET
  • URL: /users/:id
  • Description: This endpoint retrieves a user by their ID.
  • Handler Function: getUserByID
    • Extracts the user ID from the URL parameter.
    • Queries the database for the user by ID.
    • Returns the user with a status code of 200 (OK) if found or 404 (Not Found) if the user does not exist.

Update User

  • Method: PUT
  • URL: /users/:id
  • Description: This endpoint updates an existing user's information.
  • Handler Function: updateUser
    • Extracts the user ID from the URL parameter.
    • Binds the incoming JSON payload to the User struct.
    • Updates the user in the database.
    • Returns the updated user with a status code of 200 (OK).

Delete User

  • Method: DELETE
  • URL: /users/:id
  • Description: This endpoint deletes a user by their ID.
  • Handler Function: deleteUser
    • Extracts the user ID from the URL parameter.
    • Deletes the user from the database.
    • Returns a confirmation message with a status code of 200 (OK).

Test REST APIs using Postman or curl

Create User

  • Method: POST
  • URL: http://localhost:8080/users
  • Body:
    {
        "name": "Ramesh Fadatare",
        "email": "[email protected]",
        "age": 30
    }
    

Get All Users

  • Method: GET
  • URL: http://localhost:8080/users

Get User by ID

  • Method: GET
  • URL: http://localhost:8080/users/{id}

Update User

  • Method: PUT
  • URL: http://localhost:8080/users/{id}
  • Body:
    {
        "name": "Ram Jadhav",
        "email": "[email protected]",
        "age": 25
    }
    

Delete User

  • Method: DELETE
  • URL: http://localhost:8080/users/{id}

Conclusion

In this tutorial, we built a set of CRUD REST APIs using Golang and a MongoDB database. We covered the entire process from setting up the environment, creating the necessary models and handlers, to testing the APIs. Following this guide, you should have a solid foundation for creating and managing RESTful services using Go and MongoDB. This knowledge can be extended to create more complex and feature-rich APIs for various applications.

Comments