MongoDB Go Driver
Introduction
The MongoDB Go Driver is the official MongoDB driver for the Go programming language. It provides a rich set of features to interact with MongoDB databases directly from Go applications. Whether you're building a web service, a data processing pipeline, or any other Go application that needs to store data, the MongoDB Go Driver offers a powerful and idiomatic way to work with MongoDB.
In this tutorial, you'll learn how to:
- Install the MongoDB Go Driver
- Connect to a MongoDB database
- Perform basic CRUD operations (Create, Read, Update, Delete)
- Work with MongoDB documents and collections
- Handle common tasks like indexing and aggregation
Prerequisites
Before you begin, make sure you have:
- Go 1.13 or later installed on your system
- A running MongoDB instance (local or remote)
- Basic knowledge of Go programming
- Familiarity with MongoDB concepts (collections, documents, etc.)
Installation
To get started with the MongoDB Go Driver, you need to install it using the go get
command:
go get go.mongodb.org/mongo-driver/mongo
This command installs the core MongoDB driver package along with its dependencies.
Connecting to MongoDB
The first step in working with MongoDB is establishing a connection. Here's a simple example of how to connect to a MongoDB instance:
package main
import (
"context"
"fmt"
"log"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func main() {
// Set client options
clientOptions := options.Client().ApplyURI("mongodb://localhost:27017")
// Create a context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Connect to MongoDB
client, err := mongo.Connect(ctx, clientOptions)
if err != nil {
log.Fatal(err)
}
// Check the connection
err = client.Ping(ctx, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println("Connected to MongoDB!")
// Disconnect when the function ends
defer func() {
if err := client.Disconnect(ctx); err != nil {
log.Fatal(err)
}
fmt.Println("Connection to MongoDB closed.")
}()
}
Understanding the Connection Code
- We import the required packages, including the MongoDB driver.
- We set client options with the MongoDB connection string.
- We create a context with a timeout of 10 seconds.
- We establish a connection using
mongo.Connect()
. - We verify the connection with
client.Ping()
. - We defer disconnection to ensure we properly close the connection.
Working with Collections and Documents
In MongoDB, data is stored in collections, which contain documents. Let's define a simple User struct and perform CRUD operations:
package main
import (
"context"
"fmt"
"log"
"time"
"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"
)
// User represents a user in our application
type User struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
Name string `bson:"name"`
Email string `bson:"email"`
Age int `bson:"age"`
CreatedAt time.Time `bson:"created_at"`
}
func main() {
// Set client options
clientOptions := options.Client().ApplyURI("mongodb://localhost:27017")
// Create a context
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Connect to MongoDB
client, err := mongo.Connect(ctx, clientOptions)
if err != nil {
log.Fatal(err)
}
defer client.Disconnect(ctx)
// Get a handle for the users collection
usersCollection := client.Database("myapp").Collection("users")
// Perform operations on the collection
// ...
}
Creating Documents (Insert Operations)
Let's insert a single document and multiple documents:
// Insert a single document
user := User{
Name: "John Doe",
Email: "john@example.com",
Age: 30,
CreatedAt: time.Now(),
}
insertResult, err := usersCollection.InsertOne(ctx, user)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Inserted a single document: %v\n", insertResult.InsertedID)
// Insert multiple documents
alice := User{Name: "Alice Smith", Email: "alice@example.com", Age: 25, CreatedAt: time.Now()}
bob := User{Name: "Bob Johnson", Email: "bob@example.com", Age: 28, CreatedAt: time.Now()}
users := []interface{}{alice, bob}
insertManyResult, err := usersCollection.InsertMany(ctx, users)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Inserted multiple documents: %v\n", insertManyResult.InsertedIDs)
Reading Documents (Query Operations)
Now let's retrieve documents from the collection:
// Find a single document
var result User
filter := bson.D{{"name", "John Doe"}}
err = usersCollection.FindOne(ctx, filter).Decode(&result)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found document: %+v\n", result)
// Find multiple documents
findOptions := options.Find()
findOptions.SetLimit(10) // Limit to 10 documents
cursor, err := usersCollection.Find(ctx, bson.D{{"age", bson.D{{"$gt", 20}}}}, findOptions)
if err != nil {
log.Fatal(err)
}
defer cursor.Close(ctx)
// Iterate through the cursor
var results []User
for cursor.Next(ctx) {
var user User
if err := cursor.Decode(&user); err != nil {
log.Fatal(err)
}
results = append(results, user)
}
if err := cursor.Err(); err != nil {
log.Fatal(err)
}
fmt.Printf("Found multiple documents: %+v\n", results)
Updating Documents
Let's update documents in our collection:
// Update a single document
filter := bson.D{{"name", "John Doe"}}
update := bson.D{
{"$set", bson.D{
{"age", 31},
{"email", "john.doe@example.com"},
}},
}
updateResult, err := usersCollection.UpdateOne(ctx, filter, update)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Matched %v documents and updated %v documents.\n",
updateResult.MatchedCount, updateResult.ModifiedCount)
// Update multiple documents
filterMany := bson.D{{"age", bson.D{{"$lt", 30}}}}
updateMany := bson.D{
{"$inc", bson.D{
{"age", 1},
}},
}
updateManyResult, err := usersCollection.UpdateMany(ctx, filterMany, updateMany)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Matched %v documents and updated %v documents.\n",
updateManyResult.MatchedCount, updateManyResult.ModifiedCount)
Deleting Documents
Finally, let's delete documents:
// Delete a single document
deleteFilter := bson.D{{"name", "John Doe"}}
deleteResult, err := usersCollection.DeleteOne(ctx, deleteFilter)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Deleted %v documents\n", deleteResult.DeletedCount)
// Delete multiple documents
deleteManyFilter := bson.D{{"age", bson.D{{"$lt", 25}}}}
deleteManyResult, err := usersCollection.DeleteMany(ctx, deleteManyFilter)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Deleted %v documents\n", deleteManyResult.DeletedCount)
Working with BSON
MongoDB stores data in BSON (Binary JSON) format, and the Go Driver provides several ways to work with BSON data:
- bson.D: An ordered representation of a BSON document
- bson.M: An unordered representation of a BSON document (map)
- bson.A: A BSON array
- bson.E: A BSON element for a D
Here's how to use these types:
// Using bson.D (ordered document - good for queries where order matters)
filter := bson.D{
{"age", bson.D{{"$gt", 25}}},
{"name", "Alice Smith"},
}
// Using bson.M (unordered map - convenient but no guaranteed order)
filterMap := bson.M{
"age": bson.M{"$gt": 25},
"name": "Alice Smith",
}
// Using bson.A (array)
pipeline := bson.A{
bson.D{
{"$match", bson.D{
{"age", bson.D{{"$gt", 25}}},
}},
},
bson.D{
{"$group", bson.D{
{"_id", "$name"},
{"avgAge", bson.D{{"$avg", "$age"}}},
}},
},
}
Advanced Operations
Indexing
Creating indexes can significantly improve query performance:
// Create a single index
indexModel := mongo.IndexModel{
Keys: bson.D{{"email", 1}}, // 1 for ascending, -1 for descending
Options: options.Index().SetUnique(true),
}
indexName, err := usersCollection.Indexes().CreateOne(ctx, indexModel)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Created index: %s\n", indexName)
// Create multiple indexes
models := []mongo.IndexModel{
{
Keys: bson.D{{"name", 1}},
},
{
Keys: bson.D{{"age", -1}},
},
}
names, err := usersCollection.Indexes().CreateMany(ctx, models)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Created indexes: %v\n", names)
Aggregation
The aggregation pipeline allows for advanced data processing:
// Define the pipeline
pipeline := bson.A{
bson.D{{"$match", bson.D{{"age", bson.D{{"$gt", 20}}}}}},
bson.D{{"$group", bson.D{
{"_id", "$name"},
{"count", bson.D{{"$sum", 1}}},
{"avgAge", bson.D{{"$avg", "$age"}}},
}}},
bson.D{{"$sort", bson.D{{"count", -1}}}},
}
cursor, err := usersCollection.Aggregate(ctx, pipeline)
if err != nil {
log.Fatal(err)
}
defer cursor.Close(ctx)
var results []bson.M
if err = cursor.All(ctx, &results); err != nil {
log.Fatal(err)
}
for _, result := range results {
fmt.Println(result)
}
Transactions
For operations that need to be atomic, MongoDB supports multi-document transactions:
// Start a session
session, err := client.StartSession()
if err != nil {
log.Fatal(err)
}
defer session.EndSession(ctx)
// Execute the transaction
callback := func(sessionContext mongo.SessionContext) (interface{}, error) {
// Get collection
coll := client.Database("myapp").Collection("users")
// Perform operations
_, err := coll.InsertOne(sessionContext, User{
Name: "Transaction User",
Email: "transaction@example.com",
Age: 35,
CreatedAt: time.Now(),
})
if err != nil {
return nil, err
}
filter := bson.D{{"name", "Alice Smith"}}
update := bson.D{{"$set", bson.D{{"age", 26}}}}
_, err = coll.UpdateOne(sessionContext, filter, update)
if err != nil {
return nil, err
}
return nil, nil
}
result, err := session.WithTransaction(ctx, callback)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Transaction result: %v\n", result)
Real-World Example: Building a REST API
Let's build a simple REST API for a user management system using the MongoDB Go Driver and the standard net/http
package:
package main
import (
"context"
"encoding/json"
"log"
"net/http"
"time"
"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"
)
type User struct {
ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
Name string `bson:"name" json:"name"`
Email string `bson:"email" json:"email"`
Age int `bson:"age" json:"age"`
CreatedAt time.Time `bson:"created_at" json:"created_at"`
}
var client *mongo.Client
var usersCollection *mongo.Collection
var ctx context.Context
func init() {
// Set client options
clientOptions := options.Client().ApplyURI("mongodb://localhost:27017")
// Create context for the connection
ctx = context.Background()
// Connect to MongoDB
var err error
client, err = mongo.Connect(ctx, clientOptions)
if err != nil {
log.Fatal(err)
}
// Check the connection
err = client.Ping(ctx, nil)
if err != nil {
log.Fatal(err)
}
usersCollection = client.Database("myapp").Collection("users")
// Create index for email uniqueness
indexModel := mongo.IndexModel{
Keys: bson.D{{"email", 1}},
Options: options.Index().SetUnique(true),
}
_, err = usersCollection.Indexes().CreateOne(ctx, indexModel)
if err != nil {
log.Fatal(err)
}
log.Println("Connected to MongoDB!")
}
// Handler for creating a new user
func createUserHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var user User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
user.CreatedAt = time.Now()
result, err := usersCollection.InsertOne(ctx, user)
if err != nil {
http.Error(w, "Error creating user: "+err.Error(), http.StatusInternalServerError)
return
}
user.ID = result.InsertedID.(primitive.ObjectID)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(user)
}
// Handler for getting all users
func getUsersHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
cursor, err := usersCollection.Find(ctx, bson.M{})
if err != nil {
http.Error(w, "Error finding users: "+err.Error(), http.StatusInternalServerError)
return
}
defer cursor.Close(ctx)
var users []User
if err = cursor.All(ctx, &users); err != nil {
http.Error(w, "Error decoding users: "+err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
// Handler for getting a user by ID
func getUserHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
id := r.URL.Path[len("/users/"):]
objID, err := primitive.ObjectIDFromHex(id)
if err != nil {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
var user User
err = usersCollection.FindOne(ctx, bson.M{"_id": objID}).Decode(&user)
if err != nil {
if err == mongo.ErrNoDocuments {
http.Error(w, "User not found", http.StatusNotFound)
} else {
http.Error(w, "Error finding user: "+err.Error(), http.StatusInternalServerError)
}
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
func main() {
http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodPost:
createUserHandler(w, r)
case http.MethodGet:
getUsersHandler(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})
http.HandleFunc("/users/", getUserHandler)
log.Println("Server starting on port 8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}
This example shows how to:
- Initialize a MongoDB connection when the application starts
- Create an index for email uniqueness
- Implement HTTP handlers for creating, listing, and finding users
- Marshal and unmarshal between JSON and MongoDB documents
Best Practices
When working with the MongoDB Go Driver, keep these best practices in mind:
-
Connection Pooling: The driver manages connection pooling automatically. Configure the pool size using
SetMaxPoolSize
andSetMinPoolSize
in the client options. -
Context Management: Always use context to control timeouts and cancellations:
goctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() -
Proper Error Handling: Always check for errors when performing database operations.
-
Bulk Operations: Use bulk write operations for better performance when inserting, updating, or deleting multiple documents.
-
Use Projections: Limit the fields returned in query results to improve performance:
goopts := options.FindOne().SetProjection(bson.D{{"name", 1}, {"email", 1}})
-
Prefer Strongly Typed Structs: Use Go structs with BSON tags for type safety:
gotype User struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
Name string `bson:"name"`
Email string `bson:"email"`
}
Summary
In this tutorial, we've covered the essential aspects of the MongoDB Go Driver:
- How to install and connect to a MongoDB database
- Performing CRUD operations (Create, Read, Update, Delete)
- Working with BSON types
- Advanced operations like indexing and aggregation
- Implementing transactions
- Building a real-world REST API example
The MongoDB Go Driver provides a powerful and idiomatic way to interact with MongoDB from your Go applications. By following the patterns and practices outlined in this tutorial, you can build robust and efficient applications that leverage MongoDB's powerful features.
Additional Resources
To deepen your understanding of the MongoDB Go Driver, check out these resources:
- MongoDB Go Driver Official Documentation
- MongoDB Go Driver GitHub Repository
- MongoDB CRUD Operations Documentation
- MongoDB Aggregation Pipeline
Exercises
-
Create a simple CLI application that allows users to create, read, update, and delete documents in a MongoDB collection of your choice.
-
Extend the REST API example to include update and delete operations for users.
-
Implement pagination for the
getUsersHandler
function in the REST API example. -
Create an aggregation pipeline that groups users by age range and calculates the average age in each group.
-
Implement a search feature that allows users to be searched by name or email using MongoDB's text indexing capabilities.
If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)