Go with MongoDB

We created a simple REST API that integrates with a MySQL database in my previous article. We will do the same in this article. But with MongoDB as a NoSQL database.

MongoDB is a cross-platform document-oriented database program. Classified as a NoSQL database. MongoDB uses JSON-like documents with optional schemas.

NoSQL refers to any non-relational database. NoSQL databases can store relationship data. They just store it differently than relational databases do. NoSQL data models allow related data to be nested within a single data structure.

SQL databases use structured query language and have a predefined schema. NoSQL databases have dynamic schemas for unstructured data. SQL databases are table-based, while NoSQL databases are document, key-value, graph, or wide-column stores.

It has a learning curve while working with relational databases because we need to understand SQL. It requires a schema while working with non-relational databases and a client library to integrate it with the programming language. Most NoSQL database systems perform fast, are flexible, are best for unstructured data.

Install

For local development, I suggest you use the Docker image of MongoDB. There is also Mongo Express for data administration.

Go

Let's code with GoLang.

1
2
3
4
5
6
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"
    "go.mongodb.org/mongo-driver/mongo/readpref"
)

First, import these client libraries.

1
2
3
4
5
type article struct {
    ID      primitive.ObjectID `bson:"_id"`
    Title   string             `bson:"Title"`
    Content string             `bson:"Content"`
}

Define the struct. The type of ID is primitive.ObjectID. We use bson tag to access data from MongoDB.

1
2
3
4
5
6
7
8
var (
    articles   []article
    client     *mongo.Client
    collection *mongo.Collection
    ctx        context.Context
    err        error
    objectID   primitive.ObjectID
)

We defined the global variables because we use them in many functions.

Index

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func index(w http.ResponseWriter, r *http.Request) {
    cur, err := collection.Find(ctx, bson.D{})
    if err != nil {
        log.Fatal(err)
    }
    defer cur.Close(context.TODO())

    for cur.Next(context.TODO()) {
        var document article
        err := cur.Decode(&document)
        if err != nil {
            log.Fatal(err)
        }
        articles = append(articles, document)
    }

    response, err := json.Marshal(articles)
    if err != nil {
        log.Fatal(err)
    }

    w.Header().Set("Content-Type", "application/json")
    w.Write(response)
}

Create

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func create(w http.ResponseWriter, r *http.Request) {
    var request article
    err := json.NewDecoder(r.Body).Decode(&request)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    request.ID = primitive.NewObjectID()

    insertResult, err := collection.InsertOne(context.TODO(), request)
    if err != nil {
        log.Fatal(err)
    }
    if insertResult.InsertedID != nil {
        response, err := json.Marshal(article{request.ID, request.Title, request.Content})
        if err != nil {
            log.Fatal(err)
        }

        w.Header().Set("Content-Type", "application/json")
        w.Write(response)
    }
}

Read

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func read(w http.ResponseWriter, r *http.Request, key string) {
    objectID, err = primitive.ObjectIDFromHex(key)
    if err != nil {
        log.Fatal(err)
    }

    filter := bson.D{{Key: "_id", Value: objectID}}

    var document article
    err = collection.FindOne(ctx, filter).Decode(&document)
    if err != nil {
        http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
        return
    }

    response, err := json.Marshal(document)
    if err != nil {
        log.Fatal(err)
    }

    w.Header().Set("Content-Type", "application/json")
    w.Write(response)
}

Update

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
func update(w http.ResponseWriter, r *http.Request, key string) {
    objectID, err = primitive.ObjectIDFromHex(key)
    if err != nil {
        log.Fatal(err)
    }

    var request article
    err := json.NewDecoder(r.Body).Decode(&request)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    request.ID = objectID

    filter := bson.D{{Key: "_id", Value: objectID}}
    update := bson.D{
        {Key: "$set", Value: bson.D{
            {Key: "Title", Value: request.Title},
            {Key: "Content", Value: request.Content},
        }},
    }

    updateResult, err := collection.UpdateOne(ctx, filter, update)
    if err != nil {
        log.Fatal(err)
    }
    if updateResult.MatchedCount != 0 {
        response, err := json.Marshal(request)
        if err != nil {
            log.Fatal(err)
        }

        w.Header().Set("Content-Type", "application/json")
        w.Write(response)
    }
}

Delete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func delete(w http.ResponseWriter, r *http.Request, key string) {
    objectID, err = primitive.ObjectIDFromHex(key)
    if err != nil {
        log.Fatal(err)
    }

    filter := bson.D{{Key: "_id", Value: objectID}}
    deleteResult, err := collection.DeleteOne(ctx, filter)
    if err != nil {
        log.Fatal(err)
    }
    if deleteResult.DeletedCount != 1 {
        http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
    }
}

Closing

We created the same CRUD functions as we did to the version of MySQL. The difference is these are for MongoDB. There is no SQL syntax here.

We use bson.D{} as a document to find, filter, and update. We use primitive.NewObjectID() to create a new ID and primitive.ObjectIDFromHex() to read the ID.

You can get the source code on https://github.com/aristorinjuang/go-databases/tree/master/04_mongodb. I also provide dummy data that you may import to your MongoDB.

References