Episode 17 of 17

MongoDB with Golang

Connect MongoDB with Go using the official MongoDB Go Driver. Learn how to set up connections, define struct models with BSON tags, perform CRUD operations, handle aggregation, implement pagination, and build a production-ready Go API backed by MongoDB.

Go's performance and simplicity make it an excellent choice for building backend services. The official MongoDB Go Driver provides a type-safe, idiomatic way to interact with MongoDB. This final episode covers everything you need to build production-ready Go applications with MongoDB.

Installation

# Initialize a Go module
mkdir mongo-go-app
cd mongo-go-app
go mod init mongo-go-app

# Install the MongoDB Go Driver
go get go.mongodb.org/mongo-driver/mongo

Connecting to MongoDB

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"
)

var client *mongo.Client

func connectDB() *mongo.Database {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    var err error
    client, err = mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017"))
    if err != nil {
        log.Fatal("Connection failed:", err)
    }

    // Verify connection
    err = client.Ping(ctx, nil)
    if err != nil {
        log.Fatal("Ping failed:", err)
    }

    fmt.Println("Connected to MongoDB!")
    return client.Database("blogApp")
}

func disconnectDB() {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    client.Disconnect(ctx)
}

Defining Models with Struct Tags

type User struct {
    ID        primitive.ObjectID `bson:"_id,omitempty" json:"id"`
    Name      string             `bson:"name" json:"name"`
    Email     string             `bson:"email" json:"email"`
    Age       int                `bson:"age" json:"age"`
    Role      string             `bson:"role" json:"role"`
    Tags      []string           `bson:"tags,omitempty" json:"tags"`
    Address   Address            `bson:"address,omitempty" json:"address"`
    IsActive  bool               `bson:"isActive" json:"isActive"`
    CreatedAt time.Time          `bson:"createdAt" json:"createdAt"`
    UpdatedAt time.Time          `bson:"updatedAt" json:"updatedAt"`
}

type Address struct {
    Street string `bson:"street" json:"street"`
    City   string `bson:"city" json:"city"`
    State  string `bson:"state" json:"state"`
    Zip    string `bson:"zip" json:"zip"`
}

Insert Documents

func insertUser(collection *mongo.Collection) {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    // Insert one
    user := User{
        Name:      "Alice",
        Email:     "alice@example.com",
        Age:       28,
        Role:      "developer",
        Tags:      []string{"go", "mongodb"},
        IsActive:  true,
        CreatedAt: time.Now(),
        UpdatedAt: time.Now(),
    }

    result, err := collection.InsertOne(ctx, user)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Inserted ID: %v\n", result.InsertedID)

    // Insert many
    users := []interface{}{
        User{Name: "Bob", Email: "bob@example.com", Age: 34, Role: "designer", CreatedAt: time.Now()},
        User{Name: "Charlie", Email: "charlie@example.com", Age: 25, Role: "developer", CreatedAt: time.Now()},
    }

    results, err := collection.InsertMany(ctx, users)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Inserted %d documents\n", len(results.InsertedIDs))
}

Query Documents

func findUsers(collection *mongo.Collection) {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    // Find one
    var user User
    err := collection.FindOne(ctx, bson.M{"email": "alice@example.com"}).Decode(&user)
    if err != nil {
        if err == mongo.ErrNoDocuments {
            fmt.Println("User not found")
            return
        }
        log.Fatal(err)
    }
    fmt.Printf("Found: %s (%s)\n", user.Name, user.Email)

    // Find many with options
    filter := bson.M{
        "role":     "developer",
        "isActive": true,
    }
    opts := options.Find().
        SetSort(bson.D{{Key: "name", Value: 1}}).
        SetLimit(10).
        SetProjection(bson.M{"name": 1, "email": 1, "role": 1})

    cursor, err := collection.Find(ctx, filter, opts)
    if err != nil {
        log.Fatal(err)
    }
    defer cursor.Close(ctx)

    var users []User
    if err := cursor.All(ctx, &users); err != nil {
        log.Fatal(err)
    }

    for _, u := range users {
        fmt.Printf("- %s (%s)\n", u.Name, u.Role)
    }

    // Count
    count, _ := collection.CountDocuments(ctx, bson.M{"role": "developer"})
    fmt.Printf("Developer count: %d\n", count)
}

Update Documents

func updateUsers(collection *mongo.Collection) {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    // Update one
    result, err := collection.UpdateOne(
        ctx,
        bson.M{"email": "alice@example.com"},
        bson.M{
            "$set": bson.M{"age": 29, "updatedAt": time.Now()},
            "$inc": bson.M{"loginCount": 1},
        },
    )
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Modified: %d\n", result.ModifiedCount)

    // Update many
    result, err = collection.UpdateMany(
        ctx,
        bson.M{"role": "developer"},
        bson.M{"$set": bson.M{"department": "Engineering"}},
    )
    fmt.Printf("Modified %d developers\n", result.ModifiedCount)

    // Find one and update (returns the document)
    var updated User
    err = collection.FindOneAndUpdate(
        ctx,
        bson.M{"email": "alice@example.com"},
        bson.M{"$inc": bson.M{"views": 1}},
        options.FindOneAndUpdate().SetReturnDocument(options.After),
    ).Decode(&updated)
    fmt.Printf("Updated user: %s (views: %d)\n", updated.Name, updated.Age)
}

Delete Documents

func deleteUsers(collection *mongo.Collection) {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    // Delete one
    result, err := collection.DeleteOne(ctx, bson.M{"email": "test@example.com"})
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Deleted: %d\n", result.DeletedCount)

    // Delete many
    result, err = collection.DeleteMany(ctx, bson.M{"isActive": false})
    fmt.Printf("Deleted %d inactive users\n", result.DeletedCount)
}

Aggregation

func aggregate(collection *mongo.Collection) {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    pipeline := mongo.Pipeline{
        {{Key: "$match", Value: bson.M{"isActive": true}}},
        {{Key: "$group", Value: bson.D{
            {Key: "_id", Value: "$role"},
            {Key: "count", Value: bson.M{"$sum": 1}},
            {Key: "avgAge", Value: bson.M{"$avg": "$age"}},
        }}},
        {{Key: "$sort", Value: bson.M{"count": -1}}},
    }

    cursor, err := collection.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 _, r := range results {
        fmt.Printf("Role: %v | Count: %v | Avg Age: %.1f\n",
            r["_id"], r["count"], r["avgAge"])
    }
}

Pagination Helper

type PaginatedResult struct {
    Data       []User `json:"data"`
    Page       int64  `json:"page"`
    PerPage    int64  `json:"perPage"`
    Total      int64  `json:"total"`
    TotalPages int64  `json:"totalPages"`
}

func paginate(collection *mongo.Collection, filter bson.M, page, perPage int64) (*PaginatedResult, error) {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    total, err := collection.CountDocuments(ctx, filter)
    if err != nil {
        return nil, err
    }

    skip := (page - 1) * perPage
    opts := options.Find().
        SetSkip(skip).
        SetLimit(perPage).
        SetSort(bson.D{{Key: "createdAt", Value: -1}})

    cursor, err := collection.Find(ctx, filter, opts)
    if err != nil {
        return nil, err
    }

    var users []User
    if err := cursor.All(ctx, &users); err != nil {
        return nil, err
    }

    totalPages := (total + perPage - 1) / perPage

    return &PaginatedResult{
        Data:       users,
        Page:       page,
        PerPage:    perPage,
        Total:      total,
        TotalPages: totalPages,
    }, nil
}

Putting It All Together

func main() {
    db := connectDB()
    defer disconnectDB()

    users := db.Collection("users")

    insertUser(users)
    findUsers(users)
    updateUsers(users)
    aggregate(users)

    // Paginate
    result, err := paginate(users, bson.M{"isActive": true}, 1, 10)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Page %d of %d (Total: %d)\n",
        result.Page, result.TotalPages, result.Total)
}

Series Complete! 🎉

Congratulations! You've completed the entire MongoDB Tutorial series. You now know how to install MongoDB, perform CRUD operations, build complex queries, use aggregation pipelines, manage indexes, handle backups, and integrate MongoDB with Node.js, PHP, and Go. You have everything you need to build production-ready applications with MongoDB.

GoTutorialMongoDBGolang