DEV Community

Eduardo Hitek
Eduardo Hitek

Posted on

MongoDB Golang Driver Tutorial

After years relying on Community drivers like mgo and globalsign/mgo, last year MongoDB announced they were building it’s own solution. Last March they released the version 1.0.0, so let’s see how to make some normal operations using the Oficial driver.

First of all, you need to download the driver using go get.

go.mongodb.org/mongo-driver/mongo
Enter fullscreen mode Exit fullscreen mode

Assuming your MongoDB installation is using the default setting, your method should be like this:

package main

import (
    "context"
    "log"

    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
    "go.mongodb.org/mongo-driver/mongo/readpref"
)

func GetClient() *mongo.Client {
    clientOptions := options.Client().ApplyURI("mongodb://localhost:27017")
    client, err := mongo.NewClient(clientOptions)
    if err != nil {
        log.Fatal(err)
    }
    err = client.Connect(context.Background())
    if err != nil {
        log.Fatal(err)
    }
    return client
}
Enter fullscreen mode Exit fullscreen mode

To test the connection to the mongo, we can call a function called Ping, and check if return any error. Otherwise, out connection was successful.

func main() {
    c := GetClient()
    err := c.Ping(context.Background(), readpref.Primary())
    if err != nil {
        log.Fatal("Couldn't connect to the database", err)
    } else {
        log.Println("Connected!")
    }
}
Enter fullscreen mode Exit fullscreen mode

Now for the next examples I created a database called civilact and a collection heroes and add the followings documents:

{ 
    "_id" : ObjectId("5d0574824d9f7ff15e989171"), 
    "name" : "Tony Stark", 
    "alias" : "Iron Man", 
    "signed" : true
}
{ 
    "_id" : ObjectId("5d0574d74d9f7ff15e989172"), 
    "name" : "Steve Rodgers", 
    "alias" : "Captain America", 
    "signed" : false
}
{ 
    "_id" : ObjectId("5d0574e94d9f7ff15e989173"), 
    "name" : "Vision", 
    "alias" : "Vision", 
    "signed" : true
}
{ 
    "_id" : ObjectId("5d0575344d9f7ff15e989174"), 
    "name" : "Clint Barton", 
    "alias" : "Hawkeye", 
    "signed" : false
}
Enter fullscreen mode Exit fullscreen mode

To work with these documents, would be better if we create a struct that represents all the Fields and their json names.

type Hero struct {
    Name   string `json:"name"`
    Alias  string `json:"alias"`
    Signed bool   `json:"signed"`
}
Enter fullscreen mode Exit fullscreen mode

Now let's create a Method that will return all Heroes expecting 2 parameters: MongoDB Client and a bson.M that represents a filter. Is this filter is empty, the method will return all documents.

import (
    "context"
    "log"

    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
    "go.mongodb.org/mongo-driver/mongo/readpref"
)

func ReturnAllHeroes(client *mongo.Client, filter bson.M) []*Hero {
    var heroes []*Hero
    collection := client.Database("civilact").Collection("heroes")
    cur, err := collection.Find(context.TODO(), filter)
    if err != nil {
        log.Fatal("Error on Finding all the documents", err)
    }
    for cur.Next(context.TODO()) {
        var hero Hero
        err = cur.Decode(&hero)
        if err != nil {
            log.Fatal("Error on Decoding the document", err)
        }
        heroes = append(heroes, &hero)
    }
    return heroes
}
Enter fullscreen mode Exit fullscreen mode

The breakdown is:

  1. Create a collection that represents the collection in the database;
  2. Ask the collection to return a cursor of with the elements based oh the filter (in this case the filter is empty, so will return all elements);
  3. Iterate this cursor and Decode each document to a Hero type;
  4. Append the Hero decoded in the heroes array.

If we run inside the main function, our return will be:

heroes := ReturnAllHeroes(c, bson.M{})
for _, hero := range heroes {
    log.Println(hero.Name, hero.Alias, hero.Signed)
}

2019/06/15 21:07:00 Tony Stark Iron Man true
2019/06/15 21:07:00 Steve Rodgers Captain America false
2019/06/15 21:07:00 Vision Vision true
2019/06/15 21:07:00 Clint Barton Hawkeye false
Enter fullscreen mode Exit fullscreen mode

To retrieve just the Heroes whom signed the Sokovia Accords, we just need to change the filter.

heroes := ReturnAllHeroes(c, bson.M{"signed": true})

2019/06/15 21:18:04 Tony Stark Iron Man true
2019/06/15 21:18:04 Vision Vision true
Enter fullscreen mode Exit fullscreen mode

To retrieve just one Hero, our new method will be like this:

func ReturnOneHero(client *mongo.Client, filter bson.M) Hero {
    var hero Hero
    collection := client.Database("civilact").Collection("heroes")
    documentReturned := collection.FindOne(context.TODO(), filter)
    documentReturned.Decode(&hero)
    return hero
}
Enter fullscreen mode Exit fullscreen mode

The call will be:

    hero := ReturnOneHero(c, bson.M{"name": "Vision"})
    log.Println(hero.Name, hero.Alias, hero.Signed)

    2019/06/15 22:55:44 Vision Vision true
Enter fullscreen mode Exit fullscreen mode

Now, to increase our collection of Heroes and insert, for example Doctor Strange: The new method will be like this:

func InsertNewHero(client *mongo.Client, hero Hero) interface{} {
    collection := client.Database("civilact").Collection("heroes")
    insertResult, err := collection.InsertOne(context.TODO(), hero)
    if err != nil {
        log.Fatalln("Error on inserting new Hero", err)
    }
    return insertResult.InsertedID
}
Enter fullscreen mode Exit fullscreen mode

That's how we method will be used and checked by our previous method ReturnOneHero:

hero = Hero{Name: "Stephen Strange", Alias: "Doctor Strange", Signed: true}
insertedID := InsertNewHero(c, hero)
log.Println(insertedID)
hero = ReturnOneHero(c, bson.M{"alias": "Doctor Strange"})
log.Println(hero.Name, hero.Alias, hero.Signed)
Enter fullscreen mode Exit fullscreen mode

Great! We added a Sorcerer Supreme in our Heroes collection, but what if he didn't like and asked us to remove him from it? Well, that's why we need a RemoveOneHero method.

func RemoveOneHero(client *mongo.Client, filter bson.M) int64 {
    collection := client.Database("civilact").Collection("heroes")
    deleteResult, err := collection.DeleteOne(context.TODO(), filter)
    if err != nil {
        log.Fatal("Error on deleting one Hero", err)
    }
    return deleteResult.DeletedCount
}
Enter fullscreen mode Exit fullscreen mode

And that's how we check:

heroesRemoved := RemoveOneHero(c, bson.M{"alias": "Doctor Strange"})
log.Println("Heroes removed count:", heroesRemove
hero = ReturnOneHero(c, bson.M{"alias": "Doctor Strange"})
log.Println("Is Hero empty?", hero == Hero{ })
Enter fullscreen mode Exit fullscreen mode

For last, let's imagine that Hawkeye changed his mind and now wants to sign the Accords. So let's make de UpdateHero method.

func UpdateHero(client *mongo.Client, updatedData bson.M, filter bson.M) int64 {
    collection := client.Database("civilact").Collection("heroes")
    atualizacao := bson.D{ {Key: "$set", Value: updatedData} }
    updatedResult, err := collection.UpdateOne(context.TODO(), filter, atualizacao)
    if err != nil {
        log.Fatal("Error on updating one Hero", err)
    }
    return updatedResult.ModifiedCount
}
Enter fullscreen mode Exit fullscreen mode

That's it! The regular CRUD Operation was covered and our Heroes can decide their fate.
All the code for these examples is available here and this tutorial was also posted at my blog. Here is the Driver's oficial repo and oficial docs

Feel free to get in touch for any question, suggestion or mistake that i made.

Top comments (10)

Collapse
 
maulus0 profile image
Dan Waters

Thanks for this, I have been looking all over for how to work with MongoDB in this way. Every example I have been able to find does everything in the one main function, and I was struggling to understand how the Mongo -> Go driver was supposed to work.

Thanks.

Collapse
 
eduardohitek profile image
Eduardo Hitek

Hi Dan, thanks for your comment.
Good to know that this Post could help you. I also created a lib to help my development with these most used functions in the MongoDB Driver. You can check it out here:
github.com/eduardohitek/golang-mon...

Let me know if you need any help.

Collapse
 
jaimeparedes profile image
jaime-paredes

Hi Eduardo, thanks for sharing your code.
I was looking the common package, but I don't know how to define the interfaces to the models of MongoDB.

Any suggestion?

Collapse
 
tisoportes profile image
tisoportes

Hi!

Pretty cool all samples, thanks.

What about if I have:

type Hero struct {
MyVar1 bool json:"signed" bson:"signed"
MyVar2 string json:"alias" bson:"alias"
MyVar3 string json:"name" bson:"name"
}

How can I get its _id? Should I declare in the structure? I've tried but I cannot figure out. Just starting with mongo in go and your samples are very useful.

Thanks!

Collapse
 
linhnd2908 profile image
linhnd2908

Great tutorial, thank you

Collapse
 
findridoy profile image
findridoy

type Hero struct {
Name string json:"name"
Alias string json:"alias"
Signed bool json:"signed"
}

Why i need this json:name, json:alias, json signed with my struct value?

Collapse
 
eduardohitek profile image
Eduardo Hitek

Hi frindidoy, great question!

It's true that you don't need to add this tags on your Struct, but I do for 2 mainly reasons:

  1. Almost all of my code is form REST/Http Request, so I need to decode the Request's body into a Struck, and having theses tags make the process automatic to me.
  2. And for me is more like a kind of documentation, because I know the fields names in the database so when I look to my Struct it gives me a smallprint of how my data is organized.

One curious thing is, if you use the bson: tag. You don't need to obey the order and neither the names between the Struct and you Collection.

type Hero struct {
    MyVar1 bool   `json:"signed" bson:"signed"`
    MyVar2 string `json:"alias" bson:"alias"`
    MyVar3 string `json:"name" bson:"name"`
}

Let me know if you still have any doubts about it!

Collapse
 
tkehayov profile image
Tihomir Kehayov

Hi Eduardo,
Do we need to close dB connection?

Collapse
 
eduardohitek profile image
Eduardo Hitek

Hi Tihomir, how are you?
Yes, when I was starting to use this Mongo Driver and creating these examples, I realized that there were some open connections left in the database. That's why is recommended to close theses connection after you execute some operation.

Collapse
 
esker profile image
Isaac Gibbs

Question, how does one connect to a replica set using this driver?

Currently I have
mongoServerUrl = "mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0/"

which times out any help would be amazing!