Editor’s note: This article was last updated on 7 October 2022 to include information about mapping Go structs with MongoDB documents.
The demand for applications that use NoSQL-based databases is on the rise, with many developers looking to learn how to integrate databases like MongoDB into applications built with their favorite language and frameworks.
In this tutorial, I’ll teach you how to integrate MongoDB into Go applications by performing CRUD operations using the official MongoDB Go driver and providing code samples along the way.
To jump ahead:
To follow and understand this tutorial, you will need the following:
The first step is to install the MongoDB Go driver, the official Go driver for MongoDB. It provides functionalities that allow a Go application to connect to a MongoDB database and execute queries.
Create a new Go project in your text editor or IDE and initialize your go.mod
file. You are free to use any name for your package:
go mod init mongo-with-golang
go get go.mongodb.org/mongo-driver/mongo go get go.mongodb.org/mongo-driver/bson
Import the Go driver package into your application, then create a MongoDB client instance for a database on port 27017
(MongoDB’s default port).
Create a file named main.go
and save the following code in it:
package main import ( "context" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/readpref" ) func main() { client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI("mongodb://localhost:27017")) if err != nil { panic(err) } }
Here, you imported the mongo
, mongo/options
, and mongo/readpref
modules from the Go driver into your application to connect to the local database.
Then, you created a client instance using the mongo.Connect()
function and passed a Go context
to it. Any time you make requests to a server (the database, in this case), you should create a context using context.TODO()
that the server will accept.
Finally, you checked errors in the database connection using the err
variable returned from calling mongo.Connect()
. If the err
value is not empty, it means there was an error (wrong credentials or connecting to a non-existent database), and you should terminate the application using panic()
.
The
mongo.Connect
documentation contains more advanced configurations for creating a MongoDB client instance, including authentication.
The MongoDB client provides a Ping()
method to tell you if a MongoDB database has been found and connected.
Let’s see how you can use it:
if err := client.Ping(context.TODO(), readpref.Primary()); err != nil { panic(err) }
Here, you called the Ping()
method and passed a context
to it along with a primary read preference using readpref.Primary()
, which tells the MongoDB client how to read operations to the replica set members.
Then, you checked for errors using the err
variable like we did earlier, and terminated the program using panic()
, if required. If the code runs without any errors, it means the database connection is successful.
After you have connected to a MongoDB database, you need to create a Collection
instance from the client
instance that you will use to execute queries.
Add the following code to the main.go
file to create a Collection
instance retrieved from the "users"
collection named "testing"
:
usersCollection := client.Database("testing").Collection("users")
This code retrieves the "users"
collection from the "testing"
database in our local MongoDB database. If a database or collection does not exist before retrieving it, MongoDB will create it automatically.
Now that you have successfully established a connection to a MongoDB server and created a Collection
instance, let’s execute queries in our database from Go. This section covers how to insert, fetch, update, and delete data in a MongoDB database using the Go driver.
Before working with data in MongoDB, first import the bson
package we installed earlier into your project.
Add "go.mongodb.org/mongo-driver/bson"
to your imports:
To create new documents in a MongoDB collection, the database client provides an InsertOne()
method that allows you to insert a single document, and an InsertMany()
method to insert multiple documents.
Let’s see how you can use them:
// insert a single document into a collection // create a bson.D object user := bson.D{{"fullName", "User 1"}, {"age", 30}} // insert the bson object using InsertOne() result, err := usersCollection.InsertOne(context.TODO(), user) // check for errors in the insertion if err != nil { panic(err) } // display the id of the newly inserted object fmt.Println(result.InsertedID) // insert multiple documents into a collection // create a slice of bson.D objects users := []interface{}{ bson.D{{"fullName", "User 2"}, {"age", 25}}, bson.D{{"fullName", "User 3"}, {"age", 20}}, bson.D{{"fullName", "User 4"}, {"age", 28}}, } // insert the bson object slice using InsertMany() results, err := usersCollection.InsertMany(context.TODO(), users) // check for errors in the insertion if err != nil { panic(err) } // display the ids of the newly inserted objects fmt.Println(results.InsertedIDs)
Here, you created a bson
object to store data you want to insert into the database, because the MongoDB Go driver requires you to prepare your data as bson
. You can also create an array and slice of bson
objects to store multiple values.
Then, you used the InsertOne()
method to insert a single object and InsertMany()
method to insert a list of objects into the database collection.
Finally, you checked if there was an error in the operation using the err
variable returned by the method, and displayed the ID of the newly inserted documents using the InsertedID
and InsertedIDs
fields of the insertion results:
To retrieve documents from a MongoDB collection, the database client provides a Find()
method that returns all documents that match a search filter, and a FindOne()
method that returns only the first document that matches the filter.
Let’s look at how you can use them:
// retrieve single and multiple documents with a specified filter using FindOne() and Find() // create a search filer filter := bson.D{ {"$and", bson.A{ bson.D{ {"age", bson.D{{"$gt", 25}}}, }, }, }, } // retrieve all the documents that match the filter cursor, err := usersCollection.Find(context.TODO(), filter) // check for errors in the finding if err != nil { panic(err) } // convert the cursor result to bson var results []bson.M // check for errors in the conversion if err = cursor.All(context.TODO(), &results); err != nil { panic(err) } // display the documents retrieved fmt.Println("displaying all results from the search query") for _, result := range results { fmt.Println(result) } // retrieving the first document that matches the filter var result bson.M // check for errors in the finding if err = usersCollection.FindOne(context.TODO(), filter).Decode(&result); err != nil { panic(err) } // display the document retrieved fmt.Println("displaying the first result from the search filter") fmt.Println(result)
Here, you created a search filter to query the database for documents with values greater than 25
in their age
field. A filter defines the set of parameters MongoDB should use to match the documents in the database and retrieve them for the user.
Next, you used the Find()
method to retrieve all the documents that match the search filter by providing a request context and search filter as arguments. The Find()
method returns a cursor
object representing the retrieved documents and an error
variable containing any errors when querying the database.
After getting the resulting cursor
object, you used the cursor.All()
function to convert the cursor data to a slice of bson
objects. Then, we checked for errors using the err
variable and displayed the retrieved document in the terminal.
Then, you used the FindOne()
method to retrieve the first document that matches the search filter. The FindOne()
method returns an object you can convert to a bson
object using the Decode()
method.
Finally, you checked for errors in the Find()
and Decode()
operations using the err
variable and displayed the retrieved document in the terminal:
You can also retrieve every document in a collection by matching the Find()
method with an empty filter:
// retrieve all the documents in a collection cursor, err := usersCollection.Find(context.TODO(), bson.D{}) // check for errors in the finding if err != nil { panic(err) } // convert the cursor result to bson var results []bson.M // check for errors in the conversion if err = cursor.All(context.TODO(), &results); err != nil { panic(err) } // display the documents retrieved fmt.Println("displaying all results in a collection") for _, result := range results { fmt.Println(result) }
You should use
bson.D
objects when you care about the field order in thebson
object (e.g., command and filtering documents), then usebson.M
objects when you don’t care about the order of the fields.
MongoDB provides two operations to change documents in a collection: Update
and Replace
. Update
changes only specified fields in a document, while Replace
overwrites existing data with new fields you provide.
The MongoDB Go driver also provides the following functions to change documents in a collection. They are:
UpdateByID()
UpdateOne()
UpdateMany()
ReplaceOne()
FindOneAndUpdate()
FindOneAndReplace()
Let’s explore each of the functions, starting with UpdateByID()
, which updates the fields of a single document with a specified ObjectID
:
// update a single document with a specified ObjectID using UpdateByID() // insert a new document to the collection user := bson.D{{"fullName", "User 5"}, {"age", 22}} insertResult, err := usersCollection.InsertOne(context.TODO(), user) if err != nil { panic(err) } // create the update query for the client update := bson.D{ {"$set", bson.D{ {"fullName", "User V"}, }, }, {"$inc", bson.D{ {"age", 1}, }, }, } // execute the UpdateByID() function with the filter and update query result, err := usersCollection.UpdateByID(context.TODO(), insertResult.InsertedID, update) // check for errors in the updating if err != nil { panic(err) } // display the number of documents updated fmt.Println("Number of documents updated:", result.ModifiedCount)
Here, you inserted a new document into the collection and created an update query that will set the fullName
field of matched documents with "User V"
, then increment the age
field by 1
.
Next, you used the UpdateByID()
function to update the specified document by providing a context, the ObjectID
of the document you want to modify, and the update
query to execute as arguments.
Finally, you checked for errors in the update
operation using the err
variable, and displayed the number of modified documents using the UpdateResult
object returned from calling UpdateByID()
:
Now, let’s look at the UpdateOne()
and UpdateMany()
functions to update single and multiple documents that match a specified search filter:
// update single and multiple documents with a specified filter using UpdateOne() and UpdateMany() // create a search filer filter := bson.D{ {"$and", bson.A{ bson.D{ {"age", bson.D{{"$gt", 25}}}, }, }, }, } // create the update query update := bson.D{ {"$set", bson.D{ {"age", 40}, }, }, } // execute the UpdateOne() function to update the first matching document result, err := usersCollection.UpdateOne(context.TODO(), filter, update) // check for errors in the updating if err != nil { panic(err) } // display the number of documents updated fmt.Println("Number of documents updated:", result.ModifiedCount) // execute the UpdateMany() function to update all matching first document results, err := usersCollection.UpdateMany(context.TODO(), filter, update) // check for errors in the updating if err != nil { panic(err) } // display the number of documents updated fmt.Println("Number of documents updated:", results.ModifiedCount)
Here, you first created a search filter that matches documents with values greater than 25
in their age
field. Then, you created an update
query that changes the value of the age
field to 40
.
Next, you used the UpdateOne()
function to update the first document that matches the search filter by providing a context, the filter with which to match documents, and the update
query to execute as arguments.
The UpdateOne()
method returns an UpdateResult
object containing information about the operation results, and an error
variable containing any errors when updating the database.
Finally, you used the UpdateMany()
function to update all the documents that match the search filter by providing the same arguments as the UpdateOne()
function above:
Now, let’s look at the ReplaceOne()
function to overwrite the data in a document that matches a specified search filter:
// replace the fields of a single document with ReplaceOne() // create a search filer filter := bson.D{{"fullName", "User 1"}} // create the replacement data replacement := bson.D{ {"firstName", "John"}, {"lastName", "Doe"}, {"age", 30}, {"emailAddress", "[email protected]"}, } // execute the ReplaceOne() function to replace the fields result, err := usersCollection.ReplaceOne(context.TODO(), filter, replacement) // check for errors in the replacing if err != nil { panic(err) } // display the number of documents updated fmt.Println("Number of documents updated:", result.ModifiedCount)
Here, you created a search filter that matches documents with a value of "User 1"
in their fullName
field and a bson
object containing the new data to store.
Then, you used the ReplaceOne()
function to overwrite the data of the first document that matches the search filter by providing a context, the filter to match documents with, and the replacement data as arguments.
Finally, you checked for errors in the replace operation using the err
variable and displayed the number of modified documents using the UpdateResult
object returned from calling ReplaceOne()
:
The FindOneAndUpdate()
and FindOneAndReplace()
functions perform the same operation as FindOne()
and ReplaceOne()
, but will return a copy of the document before modifying it.
To delete documents from a MongoDB collection, the database client provides a DeleteOne()
method to delete a single document and a DeleteMany()
method to delete multiple documents in a collection.
Let’s see how you can use them:
// delete single and multiple documents with a specified filter using DeleteOne() and DeleteMany() // create a search filter filter := bson.D{ {"$and", bson.A{ bson.D{ {"age", bson.D{{"$gt", 25}}}, }, }, }, } // delete the first document that match the filter result, err := usersCollection.DeleteOne(context.TODO(), filter) // check for errors in the deleting if err != nil { panic(err) } // display the number of documents deleted fmt.Println("deleting the first result from the search filter") fmt.Println("Number of documents deleted:", result.DeletedCount) // delete every document that match the filter results, err := usersCollection.DeleteMany(context.TODO(), filter) // check for errors in the deleting if err != nil { panic(err) } // display the number of documents deleted fmt.Println("deleting every result from the search filter") fmt.Println("Number of documents deleted:", results.DeletedCount)
Working with bson
documents in Go may be quite tasking, especially as your operations become more complex. If you’re working with bson
, the functions return the map[string]interface{}
type, and as the number of documents increase, the interface may be hard to work with depending on your operation.
The MongoDB Go driver also supports working with Go structs. You get to map your documents to Go structs for easy operations and data migrations. Because most Go database packages also work with structs, this feature is useful for operating with other databases and struct-based operations.
Here’s an example of struct matching the documents in the MongoDB database:
type Person struct { ID primitive.ObjectID `bson:"_id,omitempty"` Age int `bson:"age,omitempty"` FullName string `bson:"full_name,omitempty"` }
The ID
field is of the primitive.ObjectID
type, and that’s the ObjectID of the document. You’ll need to add the corresponding bson
tags for names of the fields. You can also add additional struct tags for other operations:
You also need to export the fields, by capitalizing the identifiers, for effective data migration.
After connecting to your database and collection instance, you can use the database methods to work with structs and struct instances.
Here’s the instantiated struct with the Age
and FullName
fields initialized. You don’t have to instantiate the ObjectID
field:
individual := Person{ Age: 390, FullName: "John James Doe", }
You can use the insert methods to create documents in your database. Here’s how you can insert a single document with the individual
struct:
_, err = collection.InsertOne(context.TODO(), &individual) if err != nil { log.Fatalln("Error Inserting Document", err) }
The InsertOne
method inserts a single document into the MongoDB database collection. You can pass a reference to the instantiated struct to migrate the data to the database.
Here’s the result of the operation in the database:
You can also query your MongoDB databases with Go structs for easier data accessibility. The output of the query will be decoded into a struct instance that you can easily access and manipulate.
Here’s an example of querying the collection for the inserted document with the Age
field:
cursor, err := collection.Find(ctx, bson.M{"age": 390}) if err != nil { panic(err) }
The Find
method returns the result of the query from the collection. In this case, it returned documents where the age
field evaluates to 380.
You’ll have to instantiate a struct model instance similar to the document (the Person
struct matches the document in this case):
for _, entry := range person { fmt.Println(entry.FullName) fmt.Println(entry.Age) fmt.Println(entry.ID) }
You can use structs for most of the operations where you’ll use the map data structure. Check out the Go packages website documentation to learn more about using Go structs with MongoDB.
I hope this was a helpful guide to what can often be a challenging task. The lack of straightforward resources on using MongoDB with Go requires developers to spend a lot of time exploring documentation. With this article as a reference guide, you can confidently integrate MongoDB into a Go application.
You can head over to the official MongoDB and Go driver documentation to explore more functionalities that MongoDB provides. Also, you can visit MongoDB University to build your skills and advance your career with courses and certifications.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.