MongoDB armazena dados como documentos BSON — uma representação binária de JSON. Em vez de linhas em tabelas com esquema fixo, cada documento pode ter estrutura diferente dentro da mesma coleção. Isso o torna especialmente adequado para dados com estrutura variável, hierarquias naturais, catálogos de produtos com atributos heterogêneos e cenários onde o esquema evolui rapidamente.
A decisão de usar MongoDB versus um banco relacional não é sobre qual é melhor — é sobre qual modelo de dados representa mais naturalmente o problema em questão. Aplicações que naturalmente pensam em documentos aninhados se beneficiam do MongoDB. Aplicações com relacionamentos complexos e integridade referencial forte se beneficiam do modelo relacional.
Instalação e configuração
# Driver oficial Go para MongoDB
go get go.mongodb.org/mongo-driver/v2/mongo
# MongoDB com Docker
docker run --name mongodb-dev \
-e MONGO_INITDB_ROOT_USERNAME=admin \
-e MONGO_INITDB_ROOT_PASSWORD=senha \
-p 27017:27017 \
-d mongo:7
Conexão e configuração do cliente
package main
import (
"context"
"fmt"
"log"
"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 conectarMongoDB(ctx context.Context, uri string) (*mongo.Client, error) {
opts := options.Client().
ApplyURI(uri).
SetMaxPoolSize(25).
SetMinPoolSize(5).
SetMaxConnIdleTime(2 * time.Minute).
SetConnectTimeout(10 * time.Second).
SetServerSelectionTimeout(5 * time.Second)
cliente, err := mongo.Connect(opts)
if err != nil {
return nil, fmt.Errorf("conectar MongoDB: %w", err)
}
// Verifica conectividade com o servidor primário
pingCtx, cancelar := context.WithTimeout(ctx, 5*time.Second)
defer cancelar()
if err := cliente.Ping(pingCtx, readpref.Primary()); err != nil {
cliente.Disconnect(ctx)
return nil, fmt.Errorf("verificar conexão: %w", err)
}
log.Println("MongoDB conectado com sucesso")
return cliente, nil
}
func main() {
ctx := context.Background()
uri := "mongodb://admin:senha@localhost:27017/?authSource=admin"
cliente, err := conectarMongoDB(ctx, uri)
if err != nil {
log.Fatal(err)
}
defer cliente.Disconnect(ctx)
// Referências ao banco e coleção
db := cliente.Database("loja")
colecao := db.Collection("produtos")
fmt.Println("Coleção pronta:", colecao.Name())
}
Tipos e mapeamento com BSON
O MongoDB usa BSON internamente. O driver Go mapeia structs para BSON através de tags bson:
import (
"time"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/bson/primitive"
)
type Endereco struct {
Rua string `bson:"rua"`
Cidade string `bson:"cidade"`
Estado string `bson:"estado"`
CEP string `bson:"cep"`
}
type Avaliacao struct {
UsuarioID primitive.ObjectID `bson:"usuario_id"`
Nota float64 `bson:"nota"`
Comentario string `bson:"comentario,omitempty"`
Data time.Time `bson:"data"`
}
type Produto struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
Nome string `bson:"nome"`
Descricao string `bson:"descricao,omitempty"`
Preco float64 `bson:"preco"`
Categorias []string `bson:"categorias"`
Atributos bson.M `bson:"atributos,omitempty"` // mapa flexível
Estoque int `bson:"estoque"`
Ativo bool `bson:"ativo"`
Avaliacoes []Avaliacao `bson:"avaliacoes,omitempty"`
CriadoEm time.Time `bson:"criado_em"`
AtualizadoEm time.Time `bson:"atualizado_em"`
}
primitive.ObjectID é o tipo Go para o _id do MongoDB — um identificador de 12 bytes que inclui timestamp, identificador de máquina e contador sequencial. bson.M é um map[string]any para documentos sem estrutura fixa.
Insert: inserindo documentos
func inserirProduto(ctx context.Context, col *mongo.Collection, p *Produto) error {
if p.ID.IsZero() {
p.ID = primitive.NewObjectID()
}
p.CriadoEm = time.Now()
p.AtualizadoEm = time.Now()
resultado, err := col.InsertOne(ctx, p)
if err != nil {
return fmt.Errorf("inserir produto: %w", err)
}
// O ID inserido é retornado como interface{}
id, ok := resultado.InsertedID.(primitive.ObjectID)
if ok {
p.ID = id
}
fmt.Printf("Produto inserido: %s\n", p.ID.Hex())
return nil
}
func inserirVariosProdutos(ctx context.Context, col *mongo.Collection, produtos []Produto) error {
docs := make([]any, len(produtos))
for i := range produtos {
produtos[i].ID = primitive.NewObjectID()
produtos[i].CriadoEm = time.Now()
produtos[i].AtualizadoEm = time.Now()
docs[i] = produtos[i]
}
opts := options.InsertMany().SetOrdered(false) // continua mesmo se um falhar
resultado, err := col.InsertMany(ctx, docs, opts)
if err != nil {
return fmt.Errorf("inserir produtos: %w", err)
}
fmt.Printf("%d produtos inseridos\n", len(resultado.InsertedIDs))
return nil
}
FindOne e Find: buscando documentos
func buscarPorID(ctx context.Context, col *mongo.Collection, id string) (*Produto, error) {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, fmt.Errorf("ID inválido: %w", err)
}
filtro := bson.M{"_id": oid}
var produto Produto
err = col.FindOne(ctx, filtro).Decode(&produto)
if err == mongo.ErrNoDocuments {
return nil, fmt.Errorf("produto %s não encontrado", id)
}
if err != nil {
return nil, fmt.Errorf("buscar produto: %w", err)
}
return &produto, nil
}
type FiltroProduto struct {
Categoria string
PrecoMin float64
PrecoMax float64
ApenasAtivo bool
Limite int64
Pagina int64
}
func listarProdutos(ctx context.Context, col *mongo.Collection, f FiltroProduto) ([]Produto, error) {
// Construção dinâmica do filtro
filtro := bson.D{}
if f.Categoria != "" {
filtro = append(filtro, bson.E{Key: "categorias", Value: f.Categoria})
}
if f.ApenasAtivo {
filtro = append(filtro, bson.E{Key: "ativo", Value: true})
}
if f.PrecoMin > 0 || f.PrecoMax > 0 {
preco := bson.D{}
if f.PrecoMin > 0 {
preco = append(preco, bson.E{Key: "$gte", Value: f.PrecoMin})
}
if f.PrecoMax > 0 {
preco = append(preco, bson.E{Key: "$lte", Value: f.PrecoMax})
}
filtro = append(filtro, bson.E{Key: "preco", Value: preco})
}
limite := f.Limite
if limite <= 0 {
limite = 20
}
skip := (f.Pagina - 1) * limite
if skip < 0 {
skip = 0
}
opts := options.Find().
SetSort(bson.D{{Key: "nome", Value: 1}}).
SetLimit(limite).
SetSkip(skip)
cursor, err := col.Find(ctx, filtro, opts)
if err != nil {
return nil, fmt.Errorf("listar produtos: %w", err)
}
defer cursor.Close(ctx)
var produtos []Produto
if err := cursor.All(ctx, &produtos); err != nil {
return nil, fmt.Errorf("decodificar produtos: %w", err)
}
return produtos, nil
}
bson.D é uma slice ordenada de pares chave-valor — útil quando a ordem dos campos importa. bson.M é um mapa não ordenado — mais conveniente para filtros simples.
Update: atualizando documentos
func atualizarPreco(ctx context.Context, col *mongo.Collection, id string, novoPreco float64) error {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return fmt.Errorf("ID inválido: %w", err)
}
filtro := bson.M{"_id": oid}
update := bson.M{
"$set": bson.M{
"preco": novoPreco,
"atualizado_em": time.Now(),
},
}
resultado, err := col.UpdateOne(ctx, filtro, update)
if err != nil {
return fmt.Errorf("atualizar preço: %w", err)
}
if resultado.MatchedCount == 0 {
return fmt.Errorf("produto %s não encontrado", id)
}
return nil
}
func adicionarAvaliacao(ctx context.Context, col *mongo.Collection,
produtoID string, av Avaliacao) error {
oid, err := primitive.ObjectIDFromHex(produtoID)
if err != nil {
return fmt.Errorf("ID inválido: %w", err)
}
av.Data = time.Now()
filtro := bson.M{"_id": oid}
update := bson.M{
"$push": bson.M{"avaliacoes": av},
"$set": bson.M{"atualizado_em": time.Now()},
}
_, err = col.UpdateOne(ctx, filtro, update)
return err
}
func decrementarEstoque(ctx context.Context, col *mongo.Collection,
produtoID string, quantidade int) error {
oid, err := primitive.ObjectIDFromHex(produtoID)
if err != nil {
return fmt.Errorf("ID inválido: %w", err)
}
// $inc incrementa ou decrementa atomicamente
filtro := bson.M{
"_id": oid,
"estoque": bson.M{"$gte": quantidade}, // garante estoque suficiente
}
update := bson.M{
"$inc": bson.M{"estoque": -quantidade},
"$set": bson.M{"atualizado_em": time.Now()},
}
resultado, err := col.UpdateOne(ctx, filtro, update)
if err != nil {
return fmt.Errorf("decrementar estoque: %w", err)
}
if resultado.MatchedCount == 0 {
return fmt.Errorf("produto não encontrado ou estoque insuficiente")
}
return nil
}
Upsert: inserir ou atualizar
func upsertProduto(ctx context.Context, col *mongo.Collection, p *Produto) error {
filtro := bson.M{"nome": p.Nome}
p.AtualizadoEm = time.Now()
update := bson.M{
"$set": p,
"$setOnInsert": bson.M{
"criado_em": time.Now(),
"_id": primitive.NewObjectID(),
},
}
opts := options.Update().SetUpsert(true)
resultado, err := col.UpdateOne(ctx, filtro, update, opts)
if err != nil {
return fmt.Errorf("upsert produto: %w", err)
}
if resultado.UpsertedCount > 0 {
fmt.Println("Produto criado (upsert)")
} else {
fmt.Println("Produto atualizado (upsert)")
}
return nil
}
Delete: removendo documentos
func deletarProduto(ctx context.Context, col *mongo.Collection, id string) error {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return fmt.Errorf("ID inválido: %w", err)
}
resultado, err := col.DeleteOne(ctx, bson.M{"_id": oid})
if err != nil {
return fmt.Errorf("deletar produto: %w", err)
}
if resultado.DeletedCount == 0 {
return fmt.Errorf("produto %s não encontrado", id)
}
return nil
}
func deletarProdutosInativos(ctx context.Context, col *mongo.Collection) (int64, error) {
resultado, err := col.DeleteMany(ctx, bson.M{"ativo": false})
if err != nil {
return 0, fmt.Errorf("deletar inativos: %w", err)
}
return resultado.DeletedCount, nil
}
Aggregation Pipeline
O aggregation pipeline é um dos recursos mais poderosos do MongoDB — permite transformar e analisar dados em múltiplos estágios:
func estatisticasPorCategoria(ctx context.Context, col *mongo.Collection) ([]bson.M, error) {
pipeline := mongo.Pipeline{
// Estágio 1: filtra apenas produtos ativos
{{"$match", bson.D{{Key: "ativo", Value: true}}}},
// Estágio 2: desaninha o array de categorias
{{"$unwind", "$categorias"}},
// Estágio 3: agrupa por categoria
{{"$group", bson.D{
{Key: "_id", Value: "$categorias"},
{Key: "total_produtos", Value: bson.D{{Key: "$sum", Value: 1}}},
{Key: "preco_medio", Value: bson.D{{Key: "$avg", Value: "$preco"}}},
{Key: "preco_min", Value: bson.D{{Key: "$min", Value: "$preco"}}},
{Key: "preco_max", Value: bson.D{{Key: "$max", Value: "$preco"}}},
{Key: "estoque_total", Value: bson.D{{Key: "$sum", Value: "$estoque"}}},
}}},
// Estágio 4: ordena por total de produtos decrescente
{{"$sort", bson.D{{Key: "total_produtos", Value: -1}}}},
// Estágio 5: formata a saída
{{"$project", bson.D{
{Key: "categoria", Value: "$_id"},
{Key: "total_produtos", Value: 1},
{Key: "preco_medio", Value: bson.D{{Key: "$round", Value: bson.A{"$preco_medio", 2}}}},
{Key: "preco_min", Value: 1},
{Key: "preco_max", Value: 1},
{Key: "estoque_total", Value: 1},
{Key: "_id", Value: 0},
}}},
}
cursor, err := col.Aggregate(ctx, pipeline)
if err != nil {
return nil, fmt.Errorf("aggregate: %w", err)
}
defer cursor.Close(ctx)
var resultados []bson.M
if err := cursor.All(ctx, &resultados); err != nil {
return nil, fmt.Errorf("decodificar resultados: %w", err)
}
return resultados, nil
}
Índices: essenciais para performance
func criarIndices(ctx context.Context, col *mongo.Collection) error {
indices := []mongo.IndexModel{
// Índice único no campo nome
{
Keys: bson.D{{Key: "nome", Value: 1}},
Options: options.Index().SetUnique(true),
},
// Índice composto para filtragem por categoria e preço
{
Keys: bson.D{
{Key: "categorias", Value: 1},
{Key: "preco", Value: 1},
},
},
// Índice para produtos ativos
{
Keys: bson.D{
{Key: "ativo", Value: 1},
{Key: "criado_em", Value: -1},
},
},
// Índice de texto para busca
{
Keys: bson.D{
{Key: "nome", Value: "text"},
{Key: "descricao", Value: "text"},
},
Options: options.Index().
SetDefaultLanguage("portuguese").
SetWeights(bson.D{
{Key: "nome", Value: 10},
{Key: "descricao", Value: 1},
}),
},
}
_, err := col.Indexes().CreateMany(ctx, indices)
if err != nil {
return fmt.Errorf("criar índices: %w", err)
}
fmt.Println("Índices criados com sucesso")
return nil
}
Transações multi-documento
MongoDB suporta transações ACID desde a versão 4.0 em replica sets:
func processarPedido(ctx context.Context, cliente *mongo.Client,
produtoID string, quantidade int, clienteNome string) error {
return cliente.UseSession(ctx, func(sessao mongo.SessionContext) error {
err := sessao.StartTransaction()
if err != nil {
return fmt.Errorf("iniciar transação: %w", err)
}
db := cliente.Database("loja")
colProdutos := db.Collection("produtos")
colPedidos := db.Collection("pedidos")
// Verifica e reserva estoque
oid, _ := primitive.ObjectIDFromHex(produtoID)
var produto Produto
err = colProdutos.FindOneAndUpdate(
sessao,
bson.M{
"_id": oid,
"estoque": bson.M{"$gte": quantidade},
"ativo": true,
},
bson.M{
"$inc": bson.M{"estoque": -quantidade},
"$set": bson.M{"atualizado_em": time.Now()},
},
options.FindOneAndUpdate().SetReturnDocument(options.After),
).Decode(&produto)
if err == mongo.ErrNoDocuments {
sessao.AbortTransaction(sessao)
return fmt.Errorf("produto indisponível ou estoque insuficiente")
}
if err != nil {
sessao.AbortTransaction(sessao)
return fmt.Errorf("reservar estoque: %w", err)
}
// Cria o pedido
pedido := bson.M{
"_id": primitive.NewObjectID(),
"cliente_nome": clienteNome,
"produto_id": oid,
"produto_nome": produto.Nome,
"quantidade": quantidade,
"preco_unit": produto.Preco,
"total": produto.Preco * float64(quantidade),
"status": "confirmado",
"criado_em": time.Now(),
}
_, err = colPedidos.InsertOne(sessao, pedido)
if err != nil {
sessao.AbortTransaction(sessao)
return fmt.Errorf("criar pedido: %w", err)
}
return sessao.CommitTransaction(sessao)
})
}
Tratando erros do MongoDB
import "go.mongodb.org/mongo-driver/v2/mongo"
func tratarErroMongo(err error) error {
if err == nil {
return nil
}
if err == mongo.ErrNoDocuments {
return fmt.Errorf("documento não encontrado")
}
if mongo.IsDuplicateKeyError(err) {
return fmt.Errorf("registro duplicado: %w", err)
}
if mongo.IsNetworkError(err) {
return fmt.Errorf("erro de rede ao conectar ao MongoDB: %w", err)
}
if mongo.IsTimeout(err) {
return fmt.Errorf("timeout na operação MongoDB: %w", err)
}
return err
}
Resumo do que foi coberto
Este artigo apresentou MongoDB com Go usando o driver oficial em profundidade: conexão com pgxpool, mapeamento de structs com tags bson, tipos nativos como primitive.ObjectID e bson.M, operações de insert individual e em massa, Find com filtros dinâmicos e paginação, Update com operadores como $set, $push e $inc, upsert, delete, aggregation pipeline para análise de dados, criação de índices incluindo texto e transações multi-documento. Com este artigo o módulo de banco de dados com os principais bancos está completo.
Referências e leituras complementares
-
Driver oficial MongoDB para Go — Documentação e exemplos oficiais. https://www.mongodb.com/docs/drivers/go/current/
-
Documentação MongoDB — Referência completa de operadores e aggregation. https://www.mongodb.com/docs/manual/
-
pkg.go.dev — mongo-driver — Referência da API do driver Go. https://pkg.go.dev/go.mongodb.org/mongo-driver/v2/mongo
-
MongoDB University — Cursos gratuitos oficiais incluindo Go. https://learn.mongodb.com
-
BSON specification — Especificação do formato de dados do MongoDB. https://bsonspec.org
-
MongoDB Compass — GUI oficial para visualizar e gerenciar dados MongoDB. https://www.mongodb.com/products/tools/compass