How to Use Golang Structs with MongoDB

Photo by Ana Abad on Unsplash

To demonstrate the use of Golang structs with MongoDB, I have created a simple email autoresponder as an example. We have some contacts to whom we wish to send emails. In addition, we have emails to send at specific times, with subject and content. Lastly, we also have a sequence that connects the contacts to the emails.

By using Golang structs, we minimize the use of BSON in our code and increase the usability of the results from the database queries. This article can be seen as a continuation of a previous article called “How To Use Go With MongoDB.”

In what follows, I go over the code of a little demo, piece by piece.

To start, we’ll import the necessary Golang packages.

We need to import these libraries to do the following:

  • context is necessary to create a context for the MongoDB operations.
  • bson for serializing the data that is sent to the MongoDB server.
  • mongo to deal with connecting to the MongoDB server and setting up the database and collections.

Instead of using bson serializations as we did in the previous article, the idea here is to use Golang structs. These structures need to use bson annotations for this to work correctly.

In the above piece of code, we can see various bson annotations.

These annotations all follow the same pattern:

bson:"<fieldname>,omitempty" where <fieldname> is the actual field name in the database and omitempty signifies that the field will be omitted if no value is given.

Note that the annotations are also between downward accent symbols and that there are no spaces.

Instead of using omitempty, you can use other struct tags (or use none at all). Read more about that here.

In lines 2, 9, and 16, we set the type of the ID fields to be primitive.ObjectID . By doing this, we tell BSON that these fields will be actual MongoDB ObjectIDs .

Also, the _id database fields will always be filled in even though we will omit them when defining instances of these structs (more about that later). The MongoDB server will fill in these fields and give them unique ObjectIDs. We’ll be using these ObjectIDs to address the individual documents in the collections.

In line 5, we create a slice of strings. This slice will automatically be converted to a bson.A (BSON array).

In line 10, you can see that it is also possible to use a field of time.Time.

In lines 17–18, we will place references to other documents in the database. These references will be lists of ObjectIDs. Here, I’m showing you two ways to do this.

You can use either []interface{} which is the most generic way in which you can do this. The danger is that you can put any values ​​here — including integers, strings, etc.

The other possibility is to use []primitive.ObjectID. This is a specific way of declaring the type. now only, ObjectIDs will be accepted. The problem with this approach is that we’ll need to write a little more code later on.

The first thing to do in the main() function is to connect to the MongoDB server and create the database with the collections that will contain the documents.

In lines 1 and 2, we create a new client for MongoDB using NewClient() and the correct URI. Note that my MongoDB is running on localhost with port 27017. No username or passwords have been set, as this is my test server.

In line 7, we create a context using context.TODO(). This is the most basic context possible.

In line 9, we let the client connect to the MongoDB server with the given context.

In line 14, we defer disconnecting from the MongoDB server. Given that the Disconnect() function is deferred, it will be executed after all other statements in the main() function have been run.

In line 16, we create a database called autoresponder.

In lines 17–19, we make three collections within this database. These collections are called contacts, emailsand sequences respectively.

In lines 21–23, we defer dropping the database collections. This is only for our example here, as I don’t want my database to fill up every time I run the code and test out new stuff. Please remove these lines if you wish to keep the data in the database.

Inserting the Contacts into the database

Let’s create some mock contacts and insert them into the contactsCollection.

In lines 1–17, we create three contacts (‘documents’ in MongoDB terminology) in the same way as we would create regular structs. Notice how this differs from using BSON as we did here (scroll halfway down the page).

In line 19, we then insert these contacts into the contact collection using InsertMany() as we want to insert multiple documents at once.

In line 24, we get the ids of the inserted contacts using the result insertResult from line 19 and the property InsertedIDs.

In line 26, we create a slice of type []primitive.ObjectID called contactIDs_.

In lines 17–29, we loop over the returned contactIDs cast each element as an primitive.ObjectID and append it to contactIDs_. Apparently, typecasting from []interface{} to []primitive.ObjectID cannot be done; that’s why we use the for loop.

Note here that we did not define the IDs ourselves. When inserting the documents, MongoDB automatically assigned the IDs. These ids are saved in the _id field the actual database and can also be accessed in the struct by using the ID property.

In line 31, we print out the contactIDs_ slice to demonstrate that we inserted the three contacts and that the type is correct.

We can also check this using MongoDB Compass.

Inserting the Emails into the database

Now, let’s also insert some emails into the emailsCollection.

This happens pretty much analogously to the insertion of the contacts.

To note here is that in lines 3, 7, and 11, we use time.Now() as a variable value.

In line 21, we retrieve the emailIDs from the return result of the InsertMany() operation in line 16.

Inserting the sequence into the database

Lastly, let’s also insert one sequence in the sequencesCollection.

In line 1, we create a sequence using the Sequence struct. In this struct, we put the emailIDs and the contactIDS_ that we retrieved from the insertion results previously.

In line 3, we use InsertOne() instead of InsertMany() that we used previously.

Find specific Contacts

In the code below, I demonstrate how to find contacts using a custom filter.

In line 2, we use the Find() function to find all contacts that satisfy the filter bson.M{"tags": "Customer"}. With this filter, we’ll find all contacts that have "Customer" in their tag list.

You can, of course, adapt this filter to your needs. If you want to find all contacts, you can use bson.M{}. Or, if you wish to find the contact who has as an email address, you can write bson.M{"email":""}.

In addition, instead of using the bson.M format for your filter, you could use bson.D. More on this here.

In line 7, the results from the query are loaded into the []Contact slice called contacts by using All().

In lines 11–15, we simply print out some of the properties of the Contact structs, as we would do with any struct.

Like this, we can maximize the use of Golang structs and reduce the use of BSON to the filters in the queries.

Find the sequence and retrieve the contacts and emails

Below, we recover the sequence from the sequencesCollection. We get the email IDs and the receivers’ IDs from this sequence. These IDs are then used to retrieve the respective emails and contacts.

In lines 10–13, we use the [0] as only one sequence was returned.

In lines 17 and 23, we use FindOne() together with bson.M{"_id": <id>} to find and filter the emails and contacts only to find one specific document based on its ObjectID (in <id>). As there is only one result, we can also use Decode() instead of All() to extract the results into Email and Contact struct, respectively.

Below you can find the complete code. Make sure to have a MongoDB server running on mongodb://localhost:27017 before executing the code — or change the URI.

Leave a Comment