Article

Create a CRUD Application with Golang and MongoDB on Ubuntu 20.04

Author: Francis Ndungu

Last Updated: Wed, Mar 16, 2022
Golang MongoDB

Introduction

MongoDB is one of the best document-based open-source database management systems. You can use the application across a wide range of business systems due to its flexible design patterns, which are not met by traditional SQL databases.

With its robust database schema, the MongoDB platform is suitable for setting up systems for managing products' data, creating content management systems (CMS), and in most cases, storing and querying big data.

On the other hand, Golang is a fast, scalable, and easy-to-learn modern programming language for coding advanced software applications. Since MongoDB provides a comprehensive API for the Golang language, you can use the two applications together to come up with solutions for finance, e-commerce, research, and more.

In this guide, you'll set up a database with MongoDB and communicate with it from a custom Golang application to perform basic create, read, update, and delete (CRUD) operations on your Ubuntu 20.04 server.

Prerequisites

To proceed with this guide, ensure you have got the following:

1. Create a MongoDB Database

Your data-driven application must be able to store, retrieve, update, and delete records. You can only perform all these operations after setting up a database for your software. SSH to your server and follow the steps below to initialize a MongoDB database.

  1. Log in to your MongoDB database. Replace mongo_db_admin with the admin account for your database.

    $ mongosh -u mongo_db_admin -p --authenticationDatabase admin
    
  2. Enter a password for your MongoDB account when prompted and press ENTER to proceed. Next, run the statement below to create a shop_db database.

    test> use shop_db
    
  3. Confirm that you've switched to the new shop_db database.

    switched to db shop_db
    
  4. Next, insert three documents in a new products collection by using the MongoDB insertMany() function.

    shop_db> db.products.insertMany([
                {"product_id" : 1,
                 "product_name" : "LEATHER BELT",
                 "retail_price" : 24.35   
                },
                {"product_id" : 2,
                 "product_name" : "WINTER JACKET",
                 "retail_price" : 99.95    
                },
                {"product_id" : 3,
                 "product_name" : "WOOLEN SWEATER",
                 "retail_price" : 43.20
                }  
               ]);
    
  5. Make sure the command has succeeded by confirming the output below.

    {
      acknowledged: true,
      insertedIds: {
        '0': ObjectId("62188b2358979df39bbcf178"),
        '1': ObjectId("62188b2358979df39bbcf179"),
        '2': ObjectId("62188b2358979df39bbcf17a")
      }
    }
    
  6. Next, use the following statement to query the products collection to make sure the data is in place.

    shop_db> db.products.find()
    
  7. You should get a list of all products together with the associated _ids as follows.

    [
      {
        _id: ObjectId("62188b2358979df39bbcf178"),
        product_id: 1,
        product_name: 'LEATHER BELT',
        retail_price: 24.35
      },
      {
        _id: ObjectId("62188b2358979df39bbcf179"),
        product_id: 2,
        product_name: 'WINTER JACKET',
        retail_price: 99.95
      },
      {
        _id: ObjectId("62188b2358979df39bbcf17a"),
        product_id: 3,
        product_name: 'WOOLEN SWEATER',
        retail_price: 43.2
      }
    ]
    
  8. Log out from the MongoDB server.

    shop_db> quit
    
  9. You've now set up the shop_db database, products collection, and sample documents. In the next steps, you'll create some scripts using the Golang language to manipulate your MongoDB collection.

2. Create a main.go File

The main.go file will hold the main() function for your application. This is the main method that fires when you execute your application.

  1. Before you create the source code for the main.go file, create a project directory to separate your source code from the rest of the Linux files.

    $ mkdir project
    
  2. Then, switch to the new project directory.

    $ cd project
    
  3. Next, use the nano text editor to open a new main.go file for editing purposes.

    $ nano main.go
    
  4. With the main.go file open, enter the information below into the file. Replace mongo_db_admin and EXAMPLE_PASSWORD with the correct values for your MongoDB user account.

    package main
    
    import (
        "context"
        "net/http"
        "encoding/json" 
        _"log" 
        "fmt" 
        "go.mongodb.org/mongo-driver/mongo" 
    "go.mongodb.org/mongo-driver/mongo/options"         
    )
    
    const (  
        dbUser = "mongo_db_admin"
        dbPass = "EXAMPLE_PASSWORD"
        dbName = "shop_db"
    )
    
    func main() {
         http.HandleFunc("/api/v1/products", requestHandler)
         http.ListenAndServe(":8080", nil)
    }
    
    func requestHandler(w http.ResponseWriter, req *http.Request) {
    
        w.Header().Set("Content-Type", "application/json")
    
        response := map[string]interface{}{}
    
        ctx := context.Background()
    
        client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://" + dbUser + ":" + dbPass + "@localhost:27017"))
    
        if err != nil { 
            fmt.Println(err.Error())
        } 
    
        collection := client.Database(dbName).Collection("products")  
    
        data := map[string]interface{}{} 
    
        err = json.NewDecoder(req.Body).Decode(&data)
    
        if err != nil { 
            fmt.Println(err.Error())
        }
    
        switch req.Method {
            case "POST":
                response, err = createRecord(collection, ctx, data)
            case "GET":
                response, err = getRecords(collection, ctx)
            case "PUT":
                response, err = updateRecord(collection, ctx, data)
            case "DELETE":
                response, err = deleteRecord(collection, ctx, data)
        }
    
        if err != nil { 
            response = map[string]interface{}{"error": err.Error(),}  
        } 
    
        enc := json.NewEncoder(w)
        enc.SetIndent("", "  ")
    
        if err := enc.Encode(response); err != nil {
            fmt.Println(err.Error())
        }   
    }
    
  5. Save and close the file when you're through with editing.

  6. In the above file, you're creating a web server that listens for incoming HTTP requests on port 8080 using the statements http.HandleFunc("/api/v1/products", requestHandler) and http.ListenAndServe(":8080", nil).

  7. Under the requestHandler() function, you're connecting to the MongoDB instance that you created earlier. Next, you're using the Golang switch statement to route the HTTP request to the respective CRUD functions by passing a products collection reference. Finally, you're using the JSON function to format and output the data in a human-readable format.

  8. Now that you've created the main.go file, you'll now set up individual functions on different files to handle all CRUD operations for your application.

3. Set Up a New create_record.go File

The first file you're going to set up for the CRUD operations is the create_record.go file. This file contains a function for inserting documents into the products collection.

  1. Run the command below to set up the create_record.go file.

    $ nano create_record.go
    
  2. Next, enter the information below into the file.

    package main
    
    import (
        "context"      
        "go.mongodb.org/mongo-driver/mongo"       
    )
    
    func createRecord(collection *mongo.Collection, ctx context.Context, data map[string]interface{})(map[string]interface{}, error){     
    
        req, err := collection.InsertOne(ctx, data)
    
        if err != nil { 
            return nil, err                    
        }
    
        insertedId := req.InsertedID
    
        res := map[string]interface{}{
                   "data" : map[string]interface{}{                            
                        "insertedId": insertedId,
                    },
               } 
    
        return res, nil
    }
    
  3. Save and close the file.

  4. The main function in the above file is collection.InsertOne(ctx, data) which saves the BSON payload from the requesting HTTP client to the MongoDB database. Under the above file, you're returning the insertedId of the new document if the statement executes without any errors.

  5. Next, you'll set up a function to remove documents from the MongoDB collection.

4. Create a delete_record.go File

As with any other application, you must provide a function to delete records from the products collection if you no longer need them.

  1. Open a new delete_record.go file using nano.

    $ nano delete_record.go
    
  2. Next, enter the information below into the delete_record.go file.

    package main
    
    import (
        "context"          
        "go.mongodb.org/mongo-driver/mongo"     
        "go.mongodb.org/mongo-driver/bson"
    )
    
    func deleteRecord(collection *mongo.Collection, ctx context.Context, data map[string]interface{})(map[string]interface{}, error){
    
        _, err := collection.DeleteOne(ctx, bson.M{"product_id": data["product_id"]})
    
        if err != nil { 
            return nil, err                    
        }     
    
        res := map[string]interface{}{
                   "data" : "Document deleted successfully.",   
               } 
    
        return res, nil
    }
    
  3. Save and close the file.

  4. In the file above, you're using the function collection.DeleteOne(...) to delete a document from the MongoDB database. To ensure you're deleting the right document, you're retrieving the product_id of the item you want to delete using the statement bson.M{"product_id": data["product_id"]}. In other words, you should pass a product_id in the HTTP payload when submitting a DELETE request to the application.

  5. Next, you'll set up a function to update the documents.

5. Create a New update_record.go File

You'll use the update_record.go file to make changes to your documents. The function(updateRecord()) under this file relies on the payload that contains the fields that you want to update as well as the unique product_id of the document.

  1. Use nano to open up a new update_record.go file.

    $ nano update_record.go
    
  2. Next, enter the following information into the update_record.go file.

    package main
    
    import (
        "context"
        "go.mongodb.org/mongo-driver/bson"     
        "go.mongodb.org/mongo-driver/mongo" 
    )
    
    func updateRecord(collection *mongo.Collection, ctx context.Context, data map[string]interface{})(map[string]interface{}, error){            
    
        filter := bson.M{"product_id": data["product_id"]}
        fields := bson.M{"$set": data}
    
        _, err := collection.UpdateOne(ctx, filter, fields)
    
        if err != nil { 
            return nil, err                    
        }
    
        res := map[string]interface{}{
                   "data" : "Document updated successfully.",   
               } 
    
        return res, nil
    }
    
  3. In the above file, you're first providing a filter parameter for the document that you want to update using the statement filter := bson.M{"product_id": data["product_id"]}. Then, you're submitting the new document values using the statement fields := bson.M{"$set": data}. The data value here comes from an HTTP payload as submitted by the requesting client.

  4. Next, you're using the function collection.UpdateOne(ctx, filter, fields) to submit an update request to your collection. In the next step, you'll create a function for retrieving records from your MongoDB collection.

6. Create a New get_records.go File

The MongoDB API for Golang comes with very intuitive functions for retrieving documents from the database in the form of a map. You'll use these functions to query your database collection and return the documents to the main.go file that you created earlier.

  1. Use nano to create a new get_records.go file.

    $ nano get_records.go
    
  2. Then, enter the following information into the get_records.go file.

    package main
    
    import (
        "context"          
        "go.mongodb.org/mongo-driver/bson"
        "go.mongodb.org/mongo-driver/mongo" 
    )
    
    func getRecords(collection *mongo.Collection, ctx context.Context)(map[string]interface{}, error){ 
    
        cur, err := collection.Find(ctx, bson.D{})
    
        if err != nil { 
            return nil, err
        }
    
        defer cur.Close(ctx) 
    
        var products []bson.M           
    
        for cur.Next(ctx) {
    
            var product bson.M
    
            if err = cur.Decode(&product); err != nil {
                return nil, err
            }
    
            products = append(products, product)
    
        }
    
        res := map[string]interface{}{}
    
        res = map[string]interface{}{
                  "data" : products,   
              }             
    
        return res, nil
    }
    
  3. Save and close the file.

  4. In the above file, you're using the function cur, err := collection.Find(ctx, bson.D{}) to return a cursor of the documents that you've saved in your products collection. Then, you're using the for cur.Next(ctx) {...} loop to iterate through the documents which you're later appending to the products []bson.M array.

  5. Towards the end, you're returning the data back to the calling function as a map of [string]interface{}. You've now set up all the CRUD functions for your app. In the next step, you'll test the application to make sure everything is working as expected.

7. Test the Golang Application

In this step, you'll test the application to make sure it can handle all CRUD operations without any errors.

  1. Import the MongoDB driver for your Golang application.

    $ go get go.mongodb.org/mongo-driver/mongo
    
  2. Next, execute the following command to run your application. The command below allows your application to start a web server and listen for incoming HTTP connections on port 8080 and has a blocking function. Don't run any other command on this SSH terminal window.

    $ go run ./
    
  3. Next, establish a new SSH session to your server in a separate terminal window.

  4. Attempt creating a new document by running the curl command below.

    $ curl -X POST localhost:8080/api/v1/products -H "Content-Type: application/json" -d '{"product_id": 4, "product_name": "WIRELESS KEYBOARD",  "retail_price": 45.30}'
    
  5. You should get an insertedId of the new record, as shown below.

    {
      "data": {
        "insertedId": "621c9acf3f4e8882c3eeabef"
      }
    }
    
  6. Next, use the command below to retrieve all documents from the products collection.

    $ curl -X GET localhost:8080/api/v1/products
    
  7. You should now see a list of four documents. The first three are the documents that you set up when you first initialized the database, and the last record(WIRELESS KEYBOARD) is the one that you've just inserted using the curl command.

    {
      "data": [
        {
          "_id": "621c9aaf35ece941bcc5b80d",
          "product_id": 1,
          "product_name": "LEATHER BELT",
          "retail_price": 24.35
        },
        {
          "_id": "621c9aaf35ece941bcc5b80e",
          "product_id": 2,
          "product_name": "WINTER JACKET",
          "retail_price": 99.95
        },
        {
          "_id": "621c9aaf35ece941bcc5b80f",
          "product_id": 3,
          "product_name": "WOOLEN SWEATER",
          "retail_price": 43.2
        },
        {
          "_id": "621c9acf3f4e8882c3eeabef",
          "product_id": 4,
          "product_name": "WIRELESS KEYBOARD",
          "retail_price": 45.3
        }
      ]
    }
    
  8. Next, run the command below to update the document with a product_id of 1 and change its product_name from LEATHER BELT to METAL BUCKLE LEATHER BELT.

    $ curl -X PUT localhost:8080/api/v1/products -H "Content-Type: application/json" -d '{"product_id": 1, "product_name": "METAL BUCKLE LEATHER BELT",  "retail_price": 45.30}'
    
  9. The following output confirms that you've updated the product details successfully.

    {
      "data": "Document updated successfully."
    }
    
  10. Delete the document with a product_id of 4(WIRELESS KEYBOARD) by running the command below.

    $ curl -X DELETE localhost:8080/api/v1/products -H "Content-Type: application/json" -d '{"product_id": 4}'
    
  11. You should get the following confirmation message.

    {
      "data": "Document deleted successfully."
    }
    
  12. Next, retrieve the records one more time to make sure you've effected the UPDATE and DELETE operations in the products collection.

    $ curl -X GET localhost:8080/api/v1/products
    
  13. As you can see from the output below, you've deleted the document with a product_id of 4, and you've also successfully updated the value of the document with a product_id of 1 to METAL BUCKLE LEATHER BELT.

    {
      "data": [
        {
          "_id": "621c9aaf35ece941bcc5b80d",
          "product_id": 1,
          "product_name": "METAL BUCKLE LEATHER BELT",
          "retail_price": 45.3
        },
        {
          "_id": "621c9aaf35ece941bcc5b80e",
          "product_id": 2,
          "product_name": "WINTER JACKET",
          "retail_price": 99.95
        },
        {
          "_id": "621c9aaf35ece941bcc5b80f",
          "product_id": 3,
          "product_name": "WOOLEN SWEATER",
          "retail_price": 43.2
        }
      ]        
    }
    
  14. Your code is now working as expected, and it is able to handle all CRUD operations.

Conclusion

In this guide, you've used the Golang programming language to connect and manipulate data in a MongoDB collection on your Ubuntu 20.04 server. Use the functions in this guide when designing your next data-driven MongoDB application with Golang.

Want to contribute?

You could earn up to $600 by adding new articles.