MongoDB
Document store. Dados como JSON/BSON. Schema flexível. Escala horizontal nativa via sharding.
Instalação
# Docker (recomendado para dev)
docker run -d \
--name mongodb \
-e MONGO_INITDB_ROOT_USERNAME=admin \
-e MONGO_INITDB_ROOT_PASSWORD=senha \
-p 27017:27017 \
mongo:7.0
# docker-compose
services:
mongodb:
image: mongo:7.0
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: senha
ports:
- "27017:27017"
volumes:
- mongo_data:/data/db
volumes:
mongo_data:
mongosh — CLI
mongosh
mongosh "mongodb://admin:senha@localhost:27017"
mongosh "mongodb+srv://user:pass@cluster.mongodb.net/db"
| Comando | Ação |
|---|---|
| show dbs | listar databases |
| use mydb | usar/criar database |
| show collections | listar coleções |
| db.stats() | estatísticas da database |
| db.users.stats() | estatísticas da coleção |
| exit | sair |
Conceitos
Relacional
Database
Table
Row
Column
Primary Key
JOIN
MongoDB
Database
Collection
Document
Field
_id
$lookup / embedded
// documento típico (BSON)
{
"_id": ObjectId("507f1f77bcf86cd799439011"),
"name": "Alfredo",
"email": "alfredo@example.com",
"age": 30,
"tags": ["dev", "js"],
"address": { "city": "São Paulo", "state": "SP" },
"createdAt": ISODate("2024-01-15T10:30:00Z")
}
Insert
db.users.insertOne({ name: "Alfredo", email: "a@b.com", role: "admin" })
db.users.insertMany([
{ name: "Bruno", email: "b@b.com" },
{ name: "Carlos", email: "c@c.com" }
])
Find
db.users.find()
db.users.find({ role: "admin" })
db.users.find({ age: { $gte: 18 } })
db.users.findOne({ email: "a@b.com" })
// projeção (1 = incluir, 0 = excluir)
db.users.find({}, { name: 1, email: 1, _id: 0 })
db.users.find({}, { password: 0 })
// sort, limit, skip
db.users.find().sort({ name: 1 }) // 1=ASC, -1=DESC
db.users.find().sort({ createdAt: -1 }).limit(10)
db.users.find().skip(20).limit(10) // paginação
// count
db.users.countDocuments({ role: "admin" })
db.users.estimatedDocumentCount() // rápido (metadados)
Update
// updateOne
db.users.updateOne(
{ email: "a@b.com" },
{ $set: { role: "superadmin", updatedAt: new Date() } }
)
// updateMany
db.users.updateMany({ role: "user" }, { $set: { active: true } })
// upsert
db.users.updateOne(
{ email: "novo@b.com" },
{ $set: { name: "Novo", role: "user" } },
{ upsert: true }
)
// findOneAndUpdate — retorna documento
db.users.findOneAndUpdate(
{ _id: ObjectId("...") },
{ $inc: { loginCount: 1 } },
{ returnDocument: "after" }
)
Delete
db.users.deleteOne({ email: "a@b.com" })
db.users.deleteMany({ active: false })
db.users.deleteMany({}) // todos
db.users.findOneAndDelete({ _id: ObjectId("...") })
db.users.drop() // drop collection
Operadores de Query
// comparação
{ age: { $eq: 18 } } // igual (= { age: 18 })
{ age: { $ne: 18 } } // diferente
{ age: { $gt: 18 } } // maior
{ age: { $gte: 18 } } // maior ou igual
{ age: { $lt: 65 } } // menor
{ age: { $lte: 65 } } // menor ou igual
{ role: { $in: ["admin", "mod"] } }
{ role: { $nin: ["banned"] } }
// lógicos
{ $and: [{ age: { $gte: 18 } }, { active: true }] }
{ $or: [{ role: "admin" }, { role: "mod" }] }
{ $not: { role: "banned" } }
// elemento
{ phone: { $exists: true } }
{ age: { $type: "number" } }
// array
{ tags: "javascript" } // contém
{ tags: { $all: ["js", "node"] } } // contém todos
{ tags: { $size: 3 } } // exatamente 3
{ scores: { $elemMatch: { $gte: 80, $lt: 90 } } }
// regex
{ name: /alfredo/i }
{ name: { $regex: "^alf", $options: "i" } }
// full-text (requer índice text)
{ $text: { $search: "javascript node" } }
Operadores de Update
{ $set: { name: "Novo", "address.city": "RJ" } }
{ $unset: { phone: "" } }
{ $inc: { views: 1, stock: -5 } }
{ $mul: { price: 1.1 } } // +10%
{ $rename: { "old_name": "new_name" } }
{ $min: { price: 50 } } // só atualiza se < atual
{ $max: { price: 500 } } // só atualiza se > atual
// arrays
{ $push: { tags: "typescript" } }
{ $push: { scores: { $each: [90, 85], $sort: -1, $slice: 10 } } }
{ $pull: { tags: "deprecated" } }
{ $pullAll: { tags: ["a", "b"] } }
{ $addToSet: { tags: "node" } } // adiciona se não existir
{ $pop: { tags: 1 } } // 1=último, -1=primeiro
Aggregation Pipeline
Sequência de stages que transformam documentos. Output de cada stage é input do próximo.
$match
filtrar documentos — use cedo para reduzir volume (como WHERE)
$group
agrupar e calcular — $sum, $avg, $min, $max, $push, $addToSet
$project
selecionar/transformar campos (1=incluir, 0=excluir, expressão=computar)
$sort
ordenar (1=ASC, -1=DESC)
$limit / $skip
limitar e paginar
$lookup
JOIN com outra coleção (from, localField, foreignField, as)
$unwind
expandir array — 1 documento por elemento
$addFields
adicionar campos computados ao documento
$count
contar documentos no pipeline
$facet
múltiplos pipelines paralelos em um só agregado
$bucket
agrupar em faixas/ranges
$out
gravar resultado em outra coleção
db.orders.aggregate([
{ $match: { status: "paid", createdAt: { $gte: new Date("2024-01-01") } } },
{ $group: {
_id: "$userId",
totalOrders: { $sum: 1 },
totalValue: { $sum: "$amount" },
avgValue: { $avg: "$amount" }
}},
{ $sort: { totalValue: -1 } },
{ $limit: 10 },
{ $lookup: {
from: "users",
localField: "_id",
foreignField: "_id",
as: "user"
}},
{ $unwind: "$user" },
{ $project: {
_id: 0,
userName: "$user.name",
totalOrders: 1,
totalValue: { $round: ["$totalValue", 2] }
}}
])
Índices
// criar
db.users.createIndex({ email: 1 })
db.users.createIndex({ email: 1 }, { unique: true })
db.users.createIndex({ createdAt: -1 })
// composto
db.orders.createIndex({ userId: 1, createdAt: -1 })
// sparse — só documentos com o campo
db.users.createIndex({ phone: 1 }, { sparse: true })
// TTL — expirar automaticamente
db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })
// text search
db.posts.createIndex({ title: "text", body: "text" })
// geoespacial
db.places.createIndex({ location: "2dsphere" })
// listar / remover
db.users.getIndexes()
db.users.dropIndex("email_1")
// stats de uso
db.users.aggregate([{ $indexStats: {} }])
explain()
db.users.find({ email: "a@b.com" }).explain()
db.users.find({ email: "a@b.com" }).explain("executionStats")
// "IXSCAN" = usou índice ✓
// "COLLSCAN" = scan completo (adicionar índice!)
Transações
Requer replica set ou sharded cluster. Single node sem replica set não suporta transações multi-documento.
const session = db.getMongo().startSession()
session.startTransaction()
try {
db.accounts.updateOne(
{ _id: "acc1" }, { $inc: { balance: -100 } }, { session }
)
db.accounts.updateOne(
{ _id: "acc2" }, { $inc: { balance: 100 } }, { session }
)
session.commitTransaction()
} catch(e) {
session.abortTransaction()
} finally {
session.endSession()
}
Schema Design
Reference (referenciar)
acessados independentemente
ciclo de vida próprio
relação 1:muitos ou M:N
dados crescem sem limite
{ "_id": "u1" }
{ "_id": "o1",
"userId": "u1" }
// Extended Reference — guardar campos frequentes
{
"userId": ObjectId("..."),
"userName": "Alfredo", // evita lookup só para exibir nome
"userEmail": "a@b.com"
}
// Bucket Pattern — time-series agrupado
{
"sensorId": "sensor-01",
"date": ISODate("2024-01-15"),
"measurements": [
{ "ts": ISODate("..."), "value": 22.5 }
],
"count": 1440, "avg": 22.6
}
Backup / Restore
# backup
mongodump --uri "mongodb://admin:senha@localhost:27017" --out ./backup
mongodump --uri "..." --db mydb --collection users --out ./backup
# restore
mongorestore --uri "..." ./backup
mongorestore --uri "..." --drop ./backup # dropar antes
# JSON / CSV
mongoexport --uri "..." --db mydb --collection users --out users.json
mongoexport --uri "..." --db mydb --collection users --type csv --fields name,email --out users.csv
mongoimport --uri "..." --db mydb --collection users --file users.json
Auth / Usuários
// admin
use admin
db.createUser({
user: "admin", pwd: "senha_forte",
roles: [{ role: "userAdminAnyDatabase", db: "admin" }, "readWriteAnyDatabase"]
})
// user para database específica
use mydb
db.createUser({
user: "app", pwd: "app_password",
roles: [{ role: "readWrite", db: "mydb" }]
})
// roles: read | readWrite | dbAdmin | userAdmin | clusterAdmin
Mongoose (Node.js ODM)
import mongoose from 'mongoose'
await mongoose.connect('mongodb://admin:senha@localhost:27017/mydb')
const userSchema = new mongoose.Schema({
name: { type: String, required: true, trim: true },
email: { type: String, required: true, unique: true, lowercase: true },
age: { type: Number, min: 0 },
role: { type: String, enum: ['user', 'admin'], default: 'user' },
tags: [String],
address: { city: String, state: String },
active: { type: Boolean, default: true }
}, { timestamps: true }) // adiciona createdAt e updatedAt
// índices
userSchema.index({ email: 1 }, { unique: true })
userSchema.index({ name: 'text' })
// virtual
userSchema.virtual('displayName').get(function() {
return `${this.name} <${this.email}>`
})
const User = mongoose.model('User', userSchema)
// CRUD
const user = await User.create({ name: 'Alfredo', email: 'a@b.com' })
const users = await User.find({ active: true }).sort('-createdAt').limit(10)
const u = await User.findById(id)
const u = await User.findOne({ email: 'a@b.com' })
await User.findByIdAndUpdate(id, { $set: { role: 'admin' } }, { new: true })
await User.findByIdAndDelete(id)
await User.countDocuments({ active: true })
// populate (resolver referências)
const postSchema = new mongoose.Schema({
title: String,
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
})
const Post = mongoose.model('Post', postSchema)
const posts = await Post.find().populate('author', 'name email')
// lean() — POJO, mais rápido, sem métodos Mongoose
const users = await User.find().lean()
lean() retorna objeto JS puro — mais rápido para leitura. Use quando não precisa de métodos do Mongoose (.save(), virtuals, etc).