Golang MongoDB tutorial: driver v2, BSON, ObjectId, and CRUD

Step-by-step golang and mongodb with go.mongodb.org/mongo-driver/v2: URI connect Ping, implicit create database, BSON golang bson ObjectId, CRUD, bson regex golang, dates; legacy mgo note.

Published

Updated

Read time 6 min read

Reviewed byDeepak Prasad

Golang MongoDB tutorial: driver v2, BSON, ObjectId, and CRUD

This is a step-by-step guide for golang mongodb, mongodb golang, golang and mongodb, and mongodb golang example–style searches: install the official MongoDB Go Driver, connect with a URI, understand why golang mongodb create database is really “write the first document,” then walk through BSON, golang bson ObjectId, CRUD, bson regex golang filters, and golang mongodb date fields. Advanced sections sketch indexes, aggregation, and multi-document transactions. Legacy golang mongodb mgo is covered only as history—mgo is deprecated; new code should use mongo-driver v2.

Tested with Go 1.24 on Linux and go.mongodb.org/mongo-driver/v2 v2.6.x (compile-checked). A live MongoDB instance (Atlas or local) is required to run the examples end-to-end.


Step 0 — Choose where MongoDB runs

Option A — MongoDB Atlas (managed)

Create a free cluster at MongoDB Atlas, add a database user, allow your IP (or 0.0.0.0/0 for quick tests only), open Connect → Drivers, copy the connection string, and substitute <password>.

Option B — Self-hosted

Install MongoDB on your own host—for example install MongoDB on Rocky Linux or use your distro’s packages (see apt command for Debian/Ubuntu families). Default single-node URI is usually mongodb://127.0.0.1:27017.

Put the URI in an environment variable so examples never hard-code secrets:

bash
export MONGODB_URI='mongodb://127.0.0.1:27017'
# or mongodb+srv://USER:PASS@cluster0.xxxxx.mongodb.net/?retryWrites=true&w=majority

Step 1 — Create a module and add the official Go driver (v2)

MongoDB’s current documentation recommends the v2 module path (Getting Started):

bash
mkdir app && cd app
go mod init example.com/mongotut
go get go.mongodb.org/mongo-driver/v2/mongo

Imports look like:

text
go.mongodb.org/mongo-driver/v2/mongo
go.mongodb.org/mongo-driver/v2/mongo/options
go.mongodb.org/mongo-driver/v2/bson
go.mongodb.org/mongo-driver/v2/mongo/readpref

Older tutorials use go.mongodb.org/mongo-driver/mongo without /v2; that line is still common in existing repos, but new projects should standardize on v2.


Step 2 — Connect, defer disconnect, and Ping

mongo.Connect takes merged ClientOptions (for example from ApplyURI). Connect does not fully validate reachability—call Ping with a timeout when you want to fail fast.

go
package main

import (
	"context"
	"log"
	"os"
	"time"

	"go.mongodb.org/mongo-driver/v2/mongo"
	"go.mongodb.org/mongo-driver/v2/mongo/options"
	"go.mongodb.org/mongo-driver/v2/mongo/readpref"
)

func main() {
	uri := os.Getenv("MONGODB_URI")
	if uri == "" {
		log.Fatal("set MONGODB_URI")
	}
	client, err := mongo.Connect(options.Client().ApplyURI(uri))
	if err != nil {
		log.Fatal(err)
	}
	defer func() {
		_ = client.Disconnect(context.Background())
	}()

	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()
	if err := client.Ping(ctx, readpref.Primary()); err != nil {
		log.Fatal("ping:", err)
	}
	log.Println("connected")
}

Step 3 — “Create database” the MongoDB way (implicit)

There is no separate CREATE DATABASE step like some SQL servers. When you first write to client.Database("myapp").Collection("users"), MongoDB materializes myapp and users. That answers golang mongodb create database intent: your Go code creates them by inserting or creating indexes.


Step 4 — BSON, bson.D, and golang bson ObjectId

MongoDB stores BSON (binary JSON). In Go you typically build filters and payloads with:

  • bson.D{{"k", v}, ...} — ordered document (important for operators like $set).
  • bson.M{"k": v} — unordered map-style document (fine for simple filters).

ObjectId: in driver v2 use bson.ObjectID and bson.NewObjectID(). If you omit _id on insert, the server allocates one.


Step 5 — Insert: InsertOne, InsertMany, structs

go
package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"time"

	"go.mongodb.org/mongo-driver/v2/bson"
	"go.mongodb.org/mongo-driver/v2/mongo"
	"go.mongodb.org/mongo-driver/v2/mongo/options"
)

type Event struct {
	ID    bson.ObjectID `bson:"_id,omitempty"`
	Name  string        `bson:"name"`
	Score float64       `bson:"score"`
	At    time.Time     `bson:"at"`
}

func main() {
	uri := os.Getenv("MONGODB_URI")
	client, err := mongo.Connect(options.Client().ApplyURI(uri))
	if err != nil {
		log.Fatal(err)
	}
	defer client.Disconnect(context.Background())
	ctx := context.Background()

	coll := client.Database("demo_app").Collection("events")

	// Ordered document (classic tutorial style)
	res, err := coll.InsertOne(ctx, bson.D{
		{"name", "Anna"},
		{"score", 9.5},
		{"at", time.Date(2026, 6, 1, 12, 0, 0, 0, time.UTC)},
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("inserted _id:", res.InsertedID)

	// Struct with tags (good for larger models)
	ev := Event{
		ID:    bson.NewObjectID(),
		Name:  "Bob",
		Score: 8.0,
		At:    time.Now().UTC(),
	}
	if _, err := coll.InsertOne(ctx, ev); err != nil {
		log.Fatal(err)
	}
}

InsertMany accepts []interface{} of bson.D or structs the same way.


Step 6 — Read: FindOne, Find, decode into structs or bson.M

go
// FindOne into a struct
var out Event
err := coll.FindOne(ctx, bson.D{{"name", "Anna"}}).Decode(&out)

// Find with cursor — filter names starting with "A" (regex below is friendlier for indexes)
cur, err := coll.Find(ctx, bson.D{{"name", bson.Regex{Pattern: "^A", Options: ""}}})
if err != nil {
	log.Fatal(err)
}
var rows []Event
if err := cur.All(ctx, &rows); err != nil {
	log.Fatal(err)
}

Use options.Find().SetLimit(n) for pagination; use projection to trim fields in large documents.


Step 7 — Update: UpdateOne, UpdateMany, ReplaceOne

Operators live in the update document, usually with $set, $inc, $push, etc.

go
filter := bson.D{{"name", "Anna"}}
update := bson.D{{"$set", bson.D{{"score", 9.9}}}}
res, err := coll.UpdateOne(ctx, filter, update)
// res.ModifiedCount == 0 if matched but no change, or no match — check MatchedCount

ReplaceOne replaces the entire document (except _id unless you change it).


Step 8 — Delete: DeleteOne, DeleteMany

go
_, err := coll.DeleteMany(ctx, bson.D{{"score", bson.D{{"$lt", 5.0}}}})

Step 9 — bson regex golang and other query operators

Case-sensitive prefix:

go
bson.D{{"name", bson.Regex{Pattern: "^A", Options: ""}}}

Case-insensitive substring (common bson regex golang pattern):

go
bson.D{{"name", bson.Regex{Pattern: "alice", Options: "i"}}}

Prefer anchoring patterns when possible so indexes can help. For rich filters combine $and, $or, $in as in the MongoDB query operators docs.


Step 10 — golang mongodb date (time.Time and BSON Date)

Store Go time.Time in structs or documents; the driver encodes them as BSON Date where appropriate. Always store UTC in the database and convert for display. For date-only logic, be explicit about midnight vs end-of-day ranges:

go
start := time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC)
end := time.Date(2026, 6, 2, 0, 0, 0, 0, time.UTC)
filter := bson.D{{"at", bson.D{{"$gte", start}, {"$lt", end}}}}

Advanced use cases (sketch)

  • Indexes: Indexes().CreateOne with mongo.IndexModel{Keys: bson.D{{"name", 1}}, Options: options.Index()}; build indexes that match your real query shapes.
  • Aggregation: Collection.Aggregate with a mongo.Pipeline or []bson.D for $match, $group, $lookup, etc.
  • Transactions: multi-document ACID requires a replica set (Atlas M10+ or local replica set). Use client.UseSession and session.WithTransaction per the driver transactions guide.
  • Bulk writes: BulkWrite batches inserts/updates/deletes for throughput.

Legacy golang mongodb mgo (read-only history)

gopkg.in/mgo.v2 powered many early mongodb golang tutorials. It is no longer maintained and lacks modern protocol and auth features. Treat mgo samples as read-only archaeology; port to go.mongodb.org/mongo-driver/v2 (or at least v1 mongo-driver) for any new mongodb bson golang work.


Summary

This golang mongodb walkthrough used the official driver v2: connect with mongo.Connect(options.Client().ApplyURI(os.Getenv("MONGODB_URI"))), confirm with Ping, and rely on implicit database and collection creation on first write—that is what people mean when they search golang mongodb create database. You modeled BSON with bson.D and structs, generated golang bson ObjectId values with bson.NewObjectID(), ran CRUD, used bson regex golang via bson.Regex, and stored dates as time.Time in UTC. For production, add indexes, context deadlines everywhere, and read the Advanced links; leave mgo in the past.


References


Frequently Asked Questions

1. How do I run golang mongodb create database?

MongoDB creates a database and collection lazily when you first write data. Call Database(name).Collection(name) then InsertOne or similar; no separate CREATE DATABASE statement is required.

2. What happened to golang mongodb mgo?

gopkg.in/mgo.v2 is unmaintained. Use the official go.mongodb.org/mongo-driver module (v2 import path for new projects).

3. How do I store a golang bson ObjectId?

With driver v2 use bson.ObjectID from go.mongodb.org/mongo-driver/v2/bson and bson.NewObjectID(), or let the server generate _id by omitting the field.

4. How do I use bson regex golang for case-insensitive search?

Use bson.Regex{Pattern: "alice", Options: "i"} in a filter document, or $regex in bson.D with options string "i".
Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise …