How I update documents in MongoDB with Golang
I’ve been gaining interest in Mongo DB in recent times, especially when I need to store unstructured, easily queried data. There are times when the rigidity of SQL becomes impractical for a project, especially if the requirements of the project are still unclear, and you’re working with a team. It’s easier to add a new column when inserting a new document (with a null-like provision for older documents) than sending out 20 new migrations in a week.
With Golang, my Mongo schema is the struct definition in the codebase. When I need a new column added, a simple update of the struct should suffice. I accept that may not be ideal, but in a move-fast-break-things environment, this might just be what I need. Here’s an example of a struct that corresponds to my MongoDB schema, notice I didn’t add the “bson” tag for some fields, as the MongoDB driver automatically uses the lower case of the field name. The “json” tag, however is very important for the rest of this demonstration.
Updating with Golang
I won’t bother you with the details of the rest of the CRUD processes namely Create, Retrieve, and Delete; they are straightforward. The Update operation, however, was not very convenient for me at first, and I had to come up with a different way to handle it. The code snippets I will be sharing will cover the methods for the Create, Retrieve, Find All, and Delete operations so we’re on the same page. The example I’m using is a Book collection with information to help customers who might want to purchase books from a bookstore.
Quick terminologies to cover before we continue:
- A document is a single block of data in a collection. It can contain many fields and is JSON data. It corresponds to a row in SQL.
Here’s a document from our collection:
{
"_id": {
"$oid": "65c75f78381434ed30cfaec2"
},
"title": "The Alchemist",
"author": "Paulo Coelho",
"price": 25,
"category": "fiction",
"formats": [
"paper",
"pdf",
"epub"
],
"count": 35
}
- A collection is a group of documents in a database that represent a type of information. You can think of it like a table in SQL. The collection name we’re using here is named “books”, and it contains many Book documents.
- A database is an actual database. You can think of it as a whole store, that has different collections of items sold there. The database we’re creating here will be called “book_store”. It might end up having different collections. We’ve already decided on the “books” collection. Other collections might include “stationeries”, “staff”, “customers” and others.
Let’s jump into the code.
In the example above, we created a MongoDB instance, which we will be using going forward. We then select a database and a collection using the created client instance. I try to be sure to avoid doing any “instantiation” more than once. One way to do that is to look into the driver’s source code and check what happens when something is created (or easier still, follow the examples on the official documentation). From the MongoDB’s driver source code, only the client instantiation should ideally be reused. The other methods simply use information from the client. That said, it’s still cool to have one mongo.Database handle, which is what I’m doing here.
The rest of the code shows how the other operations are handled:
Updating the Document
Now, to the main thing, how do we update an entire document, using the struct we have defined, without having to resort to something like this (this sample was gotten from ChatGPT):
update := bson.D{
{"$set", bson.D{
{"Title", "New Title"},
{"Author", "New Author"},
{"Field1", "New Value1"},
{"Field2", "New Value2"},
// ... update other fields similarly
{"Field15", "New Value15"},
}},
}
// Perform the update operation
updateResult, err := collection.UpdateOne(context.Background(), filter, update)
if err != nil {
log.Fatal(err)
}
If the above method works for you, that’s perfect. But sometimes we might have too many fields in the struct and just want a simple function to handle updates for us even when a new field is added. The function I use to handle that is shown below:
Yes, reflection. I have a habit of trying to solve problems in Golang without using reflection, but I found a great use case for it here. Instead of setting fields individually, we can just leverage the “json” struct tags in the fields. This makes future adaptations in the event of field additions a breeze. Note that it will be better to use “bson” tags to separate it from json tags. In this case, your struct tags will have the “bson” field, and you will use field.Tag.Get(“bson”) instead.
I believe the comments on the code snippet provided enough explanation, but the basic idea is simple:
- prepare the struct you want to use as an update
- get the type of the struct and values of the struct object using reflection
- loop through the fields of the struct
- get each field’s tag and value
- use the tag and value to construct an update filter with bson.D and bson.E
- run the update operation
As you may have noticed, the zero value for each field was checked. This is because omitted fields from the struct will have the zero value of the struct, and unless you’re carrying out a complete update of all fields, such values may produce unwanted effects. If you want zero values in the future, dedicated functions can be written for that.
The full GitHub repository can be found here: https://github.com/joshuaetim/golang_mongo_update
I came up with this idea and thought to share it. Let me know your thoughts in the comments, or on my direct communications channels. Thanks for reading, let me know if this helped you by leaving some claps.