Document Database

MongoDB

MongoDB é um banco de dados de documentos: documentos BSON flexíveis vivem em coleções, são consultados com uma linguagem similar ao JavaScript e escalam horizontalmente com replica sets e sharding.

intermediate15 min readUpdated 16 de set. de 2026
mongosh
js
// mongosh
use shop

db.orders.insertOne({
  customerId: ObjectId("64f1c2a9e13b4a7d8c9e0011"),
  status: "paid",
  items: [
    { sku: "KB-01", name: "Keyboard", qty: 1, price: 8900 },
    { sku: "MS-02", name: "Mouse", qty: 2, price: 2900 },
  ],
  total: 14700,
  createdAt: new Date(),
})

db.orders
  .find({ status: "paid", total: { $gte: 10000 } })
  .sort({ createdAt: -1 })
  .limit(10)
Lançado
2009
Modelo de dados
Document / BSON
Linguagem de consulta
MQL
Escrito em
C++
Storage engine
WiredTiger
Licença
SSPL
Versão
8.x

Por que importa

Por que as equipes escolhem o MongoDB

Documentos mapeiam para objetos

Um registro é um documento BSON que espelha o objeto que sua aplicação já utiliza, portanto, as leituras raramente precisam de joins ou mapeamento de linha para objeto.

Uma linguagem de consulta legível

Os filtros são documentos similares ao JSON com operadores como $gt e $in, o que torna as consultas compostíveis e fáceis de construir via código.

Escalabilidade horizontal quando necessário

Replica sets oferecem alta disponibilidade desde o primeiro dia, e o sharding distribui uma coleção entre nós quando um único primário não é mais suficiente.

O panorama completo

As três ideias por trás do MongoDB

Um documento armazena um objeto completo, uma coleção agrupa documentos, e um índice ou pipeline os transforma em respostas.

Documentos

Armazenar

Os dados vivem em documentos BSON com campos tipados, objetos aninhados e arrays. O schema é imposto pelo seu código, não pelo servidor.

Coleções & índices

Organizar

Documentos relacionados ficam em uma coleção. Índices nos campos que você filtra e ordena transformam varreduras completas em buscas direcionadas.

Driver & Mongoose

Acessar

O driver oficial de Node.js expõe os mesmos comandos que você executa no mongosh, enquanto o Mongoose adiciona schemas, validação e modelos.

HTML5 de uma olhada

A caixa de ferramentas do MongoDB

Coleções

db.orders é uma coleção de documentos, criada na primeira inserção.

find()

Passe um documento de filtro e obtenha um cursor de documentos correspondentes.

Aggregation

Um pipeline de estágios $match, $group e $lookup molda os resultados.

Índices

Índices simples, compostos, únicos, TTL e de texto compartilham a mesma API.

Transações

Transações ACID multi-documento para quando uma única escrita não é suficiente.

Replica sets

Um primário e secundários replicam escritas e elegem um novo primário.

Modelo de dados

Como um documento é estruturado

Um pedido, seus itens e seu status, tudo em um único documento.

Um documento de pedidoMongoDB document
  • _idObjectIdChave primária, gerada pelo driver
  • customerIdObjectIdReferencia um documento em users
  • statusstringpending, paid ou shipped
  • itemsarray<object>Itens embutidos: sku, name, qty, price
  • totalnumberTotal derivado em centavos
  • createdAtDateIndexado para consultas de pedidos recentes

Um documento na coleção orders, com seus itens embutidos e seu status indexado.

O guia completo

MongoDB: Tudo que voce precisa saber

O que é MongoDB?

O MongoDB é um banco de dados de documentos. Em vez de linhas em tabelas, ele armazena documentos — objetos semelhantes a JSON codificados como BSON — dentro de collections. Um documento pode conter objetos aninhados e arrays, permitindo que um único registro descreva um agregado completo: um pedido com seus itens, ou um usuário com seus endereços.

Ele surgiu em 2009, quando o modelo relacional parecia pesado para aplicações que cresciam rapidamente e mudavam de formato. Sua promessa era simples: armazenar o objeto que seu código já possui e escalar horizontalmente sem a necessidade de migrações dolorosas. Essa promessa o tornou o banco de dados NoSQL padrão para uma geração de equipes de Node.js e JavaScript.

O trade-off é real. O MongoDB abre mão de joins por padrão e transfere a validação do schema para a sua aplicação. Quando seus dados têm formato de documento, isso é uma vantagem. Quando são profundamente relacionais, um banco de dados relacional costuma ser a ferramenta ideal.

Documentos, coleções e BSON

Um documento é um conjunto ordenado de pares campo-valor. Uma coleção é um grupo de documentos que geralmente compartilham a mesma estrutura, embora nada os obrigue a isso. As coleções são criadas no momento em que você insere dados nelas pela primeira vez.

db.users.insertOne({
  email: "[email protected]",
  name: "Ada",
  address: { city: "London", country: "GB" },
  tags: ["admin", "beta"],
});

Os valores possuem tipos BSON, e não apenas tipos JSON. BSON é uma codificação binária que adiciona ObjectId, Date, Decimal128, BinData e outros, permitindo que você armazene datas reais e decimais precisos em vez de strings. A ordem dos campos é preservada e os nomes dos campos diferenciam maiúsculas de minúsculas (case-sensitive).

Um hábito útil é dar a cada documento de uma coleção uma estrutura consistente, mesmo que o servidor permita variações. Isso garante consultas e índices previsíveis, mantendo a flexibilidade para quando você precisar adicionar um campo a apenas um documento primeiro.

_id e ObjectId

Todo documento precisa de um _id único. Se você não fornecer um, o driver gera um ObjectId — um valor de 12 bytes composto por um timestamp, um valor aleatório por processo e um contador incremental. Como o timestamp vem primeiro, os valores de ObjectId são ordenados aproximadamente pelo tempo de criação, o que é conveniente para a paginação.

const { ObjectId } = require("mongodb");

const id = new ObjectId("64f1c2a9e13b4a7d8c9e0011");
id.getTimestamp(); // 2023-09-01T...

O campo _id é indexado automaticamente e é sempre único. Você pode fornecer seu próprio valor — uma chave natural, uma string UUID ou uma chave composta — quando isso tornar as buscas mais baratas ou quando você precisar de inserções idempotentes.

CRUD na prática

Create, read, update e delete mapeiam para um pequeno conjunto de métodos que se comportam da mesma forma no mongosh e no driver do Node.js.

// Create
db.users.insertOne({ email: "[email protected]", plan: "pro" });
db.users.insertMany([
  { email: "[email protected]", plan: "free" },
  { email: "[email protected]", plan: "team" },
]);

// Read
db.users.findOne({ email: "[email protected]" });
db.users.find({ plan: "pro" }).toArray();

// Update
db.users.updateOne(
  { email: "[email protected]" },
  { $set: { plan: "team" } },
);

// Delete
db.users.deleteOne({ email: "[email protected]" });

updateOne e updateMany recebem um filter e um update document. O update document utiliza operadores: $set substitui ou adiciona campos, $unset os remove, $inc soma a um número, $push anexa a um array, $addToSet adiciona apenas se estiver ausente e $pull remove elementos correspondentes de um array.

db.users.updateOne(
  { email: "[email protected]" },
  {
    $set: { lastSeenAt: new Date() },
    $inc: { logins: 1 },
    $addToSet: { tags: "beta" },
  },
);

Existe também o replaceOne, que substitui todo o documento, exceto o _id. Prefira os operadores de campo para que gravações simultâneas não sobrescrevam as alterações umas das outras.

Consultando com operadores

Um filtro é, por si só, um documento. A igualdade é o padrão, e os operadores começam com $.

db.orders.find({ status: "paid" });                     // equality
db.orders.find({ total: { $gt: 5000, $lte: 20000 } });  // range
db.orders.find({ status: { $in: ["paid", "shipped"] } });
db.users.find({ email: { $regex: /@example\.com$/i } });
db.orders.find({ "items.sku": "KB-01" });               // nested field
db.orders.find({
  items: { $elemMatch: { qty: { $gte: 2 }, price: { $lt: 3000 } } },
});

$elemMatch é importante quando várias condições devem ser aplicadas ao mesmo elemento de um array. Sem isso, { "items.qty": { $gte: 2 }, "items.price": { $lt: 3000 } } pode corresponder a elementos diferentes.

Combine filtros com $and, $or e $not:

db.orders.find({
  $or: [
    { status: "paid" },
    { status: "pending", total: { $lt: 1000 } },
  ],
});

A linguagem de consulta é composível porque ela é composta por dados, não por uma string. É por isso que construir filtros no código da aplicação parece natural — você monta um objeto e o passa para find.

Projeção, ordenação e paginação

Uma projeção seleciona quais campos devem ser retornados. Inclua campos com 1 ou exclua-os com 0, mas nunca misture os dois estilos, exceto para _id.

db.users.find(
  { plan: "pro" },
  { email: 1, name: 1, _id: 0 },
);

Ordene e pagine com .sort(), .skip() e .limit(). Para offsets grandes, skip torna-se mais lento porque o servidor ainda percorre os documentos ignorados. Prefira a paginação por keyset, onde você filtra pelo último valor visualizado.

db.orders
  .find({ customerId, createdAt: { $lt: lastSeen } })
  .sort({ createdAt: -1 })
  .limit(20);

Índices nos campos de ordenação tornam tanto o filtro quanto a ordenação eficientes, e é aí que entra a próxima seção.

Índices: a diferença entre o rápido e o inutilizável

Sem um índice, o MongoDB lê todos os documentos da coleção — um collection scan. Com um índice, ele busca diretamente o intervalo correspondente. Quase todo problema de performance no MongoDB é causado por um índice ausente ou mal ordenado.

db.users.createIndex({ email: 1 }, { unique: true });
db.orders.createIndex({ customerId: 1, createdAt: -1 });
db.sessions.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 });
db.products.createIndex({ name: "text", description: "text" });
  • Índices de campo único (single-field) aceleram consultas de igualdade e de intervalo em um único campo.
  • Índices compostos (compound) seguem a regra ESR: campos de igualdade primeiro, depois ordenação (sort) e, por fim, intervalo (range). { customerId: 1, createdAt: -1 } atende tanto ao filtro quanto à ordenação.
  • Índices únicos (unique) rejeitam duplicatas e tornam os upserts seguros.
  • Índices TTL deletam documentos após uma data específica, o que é perfeito para sessões e tokens de uso único.
  • Índices de texto (text) suportam a busca $text com stemming e scoring.

Use explain() para ver o que o planejador fez. Procure por IXSCAN em vez de COLLSCAN, e verifique se o número de documentos examinados está próximo do número de documentos retornados.

db.orders.find({ customerId, status: "paid" }).explain("executionStats");

O pipeline de agregação

Quando uma query não é suficiente, o framework de agregação executa um pipeline de estágios. Cada estágio transforma um fluxo de documentos e o repassa adiante. Os estágios mais comuns são $match, $group, $sort, $project, $lookup, $unwind e $limit.

Aqui está um exemplo prático: os cinco produtos com maior receita para pedidos pagos.

db.orders.aggregate([
  { $match: { status: "paid" } },
  { $unwind: "$items" },
  {
    $group: {
      _id: "$items.sku",
      units: { $sum: "$items.qty" },
      revenue: {
        $sum: { $multiply: ["$items.qty", "$items.price"] },
      },
    },
  },
  { $sort: { revenue: -1 } },
  { $limit: 5 },
  {
    $project: {
      _id: 0,
      sku: "$_id",
      units: 1,
      revenue: 1,
    },
  },
]);

Leia de cima para baixo. O $match filtra precocemente para que os estágios posteriores tenham menos trabalho. O $unwind transforma cada elemento do array em seu próprio documento. O $group acumula unidades e receita por SKU. O $sort e o $limit mantêm os cinco primeiros. O $project renomeia _id para sku e descarta o restante.

O $lookup realiza um left outer join com outra coleção, que é a forma de combinar dados referenciados:

db.orders.aggregate([
  { $match: { status: "paid" } },
  {
    $lookup: {
      from: "users",
      localField: "customerId",
      foreignField: "_id",
      as: "customer",
    },
  },
  { $unwind: "$customer" },
  { $project: { total: 1, "customer.email": 1 } },
]);

Sempre coloque o $match primeiro para que ele possa utilizar um índice, e coloque o $limit o mais cedo possível, desde que a lógica permita. A agregação é poderosa, mas um pipeline que escaneia todos os documentos em cada estágio é a receita para uma query lenta.

Embedding vs referencing

Esta é a decisão central de modelagem. Pergunte-se como os dados são lidos.

Embed (incorpore) quando os dados filhos são lidos junto com o pai, gravados com ele e possuem um tamanho limitado. Os itens de um pedido são o caso clássico: uma única leitura retorna tudo e não há join.

{
  _id: ObjectId("..."),
  customerId: ObjectId("..."),
  items: [
    { sku: "KB-01", qty: 1, price: 8900 },
    { sku: "MS-02", qty: 2, price: 2900 },
  ],
  total: 14700,
}

Reference (referencie) quando os dados são volumosos, compartilhados ou atualizados em seu próprio ciclo. Usuários, produtos e categorias são referenciados por _id, e $lookup ou uma segunda query faz o join entre eles.

{
  _id: ObjectId("..."),
  customerId: ObjectId("64f1c2a9e13b4a7d8c9e0011"),
  items: [{ productId: ObjectId("..."), qty: 1, price: 8900 }],
}

A regra geral é: dados que são acessados juntos devem ser armazenados juntos. Duplique um pouco de dados quando isso transformar a leitura comum em uma única busca, mas lembre-se de que as cópias devem ser atualizadas em todos os lugares onde residem. E nunca incorpore um array que cresça sem limite — os documentos têm um limite máximo de 16 MB.

Transações

Escritas em um único documento são atômicas. Se você precisar alterar vários documentos ou coleções como uma única unidade, utilize uma transação multi-documento. As sessões são o mecanismo para isso.

const session = client.startSession();

try {
  await session.withTransaction(async () => {
    await accounts.updateOne(
      { _id: from },
      { $inc: { balance: -100 } },
      { session },
    );
    await accounts.updateOne(
      { _id: to },
      { $inc: { balance: 100 } },
      { session },
    );
  });
} finally {
  await session.endSession();
}

Transações exigem um replica set ou um sharded cluster e possuem um custo operacional: locks, uma janela de execução mais longa e tentativas de reexecução (retries) em caso de conflito. O uso ideal de transações é raro. Se você perceber que está envolvendo cada escrita em uma transação, seu modelo de dados provavelmente precisa de mais embedding.

Replica sets e sharding

Um replica set é um grupo de nós que armazenam os mesmos dados. Um deles é o primário e recebe todas as escritas; os outros replicam o oplog do primário e podem processar leituras. Se o primário falhar, o set elege um novo automaticamente. Esta é a implantação padrão para produção e também habilita change streams e transações.

O sharding particiona uma coleção entre vários replica sets por meio de uma shard key. Cada shard detém um intervalo de valores de chave. Uma boa shard key possui alta cardinalidade, distribui as escritas de forma uniforme e aparece na maioria das queries, permitindo que o roteador direcione a requisição a um único shard. Uma chave ruim cria um hotspot ou força cada query a fazer um fan out para todos os shards.

Escolha a shard key antes de ter os dados, pois alterá-la posteriormente significa migrar a coleção. Para a maioria das aplicações, comece com um replica set e utilize sharding apenas quando um único primário não for mais capaz de suportar a carga.

Mongoose no Node

O driver oficial mongodb é tudo o que você precisa para realizar queries, mas a maioria das equipes de Node utiliza o Mongoose por causa de seus schemas, validação e models. Um schema descreve a estrutura de um documento, e um model é a classe consultável construída a partir dele.

import mongoose from "mongoose";

const orderSchema = new mongoose.Schema({
  customerId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: "User",
    required: true,
  },
  status: {
    type: String,
    enum: ["pending", "paid", "shipped"],
    default: "pending",
  },
  items: [
    {
      sku: String,
      qty: { type: Number, min: 1 },
      price: { type: Number, min: 0 },
    },
  ],
  total: Number,
  createdAt: { type: Date, default: Date.now, index: true },
});

export const Order = mongoose.model("Order", orderSchema);

const paid = await Order.find({ status: "paid" })
  .sort({ createdAt: -1 })
  .limit(20)
  .lean();

O Mongoose realiza a validação ao salvar, faz a conversão de tipos (casting) e oferece populate() para referências. Dois hábitos o mantêm rápido: declare os índices que as queries necessitam e utilize .lean() quando for apenas ler dados, o que evita a construção de documentos completos do Mongoose.

Escritas em lote e upserts

Executar um comando por documento desperdiça round trips. Quando você precisa aplicar diversas alterações, o bulkWrite envia um lote em uma única chamada e reporta exatamente o que aconteceu.

await db.users.bulkWrite([
  {
    updateOne: {
      filter: { email: "[email protected]" },
      update: { $set: { plan: "team" } },
      upsert: true,
    },
  },
  {
    insertOne: {
      document: { email: "[email protected]", plan: "pro" },
    },
  },
  {
    deleteOne: { filter: { email: "[email protected]" } },
  },
]);

A opção upsert é outra ferramenta fundamental. Ela atualiza um documento correspondente ou insere um novo caso nenhum exista, o que facilita a criação de contadores e importações idempotentes.

db.stats.updateOne(
  { day: "2026-09-16" },
  { $inc: { visits: 1 } },
  { upsert: true },
);

Escritas em lote não são transações por padrão. Passe { ordered: false } para continuar a execução após um erro e coletar todas as falhas, ou mantenha o modo ordenado (padrão) quando cada escrita depender da anterior. De qualquer forma, verifique o objeto de resultado: ele contabiliza documentos correspondentes, modificados, inseridos e deletados, e um upsert que não encontrou correspondência não é considerado um erro.

Change streams

Um replica set registra cada escrita em um oplog. Change streams expõem esse log como um feed retomável, permitindo que uma aplicação reaja a inserts, updates e deletes sem a necessidade de polling.

const changeStream = db.orders.watch([
  { $match: { "fullDocument.status": "paid" } },
]);

for await (const change of changeStream) {
  console.log(change.operationType, change.fullDocument._id);
}

O feed é retomável: armazene o _id do último evento processado como um resume token e envie-o de volta com resumeAfter após um reinício para que nenhum evento seja perdido. Como os change streams rodam sobre o oplog, eles exigem um replica set e reportam apenas as alterações que ainda não foram removidas do log.

Duas regras garantem a confiabilidade: torne o consumidor idempotente, pois um evento pode ser entregue novamente após a retomada. E mantenha o processamento rápido ou delegue o trabalho para uma fila, já que um consumidor lento permite que o oplog avance além de sua posição.

Melhores práticas

  • Modele para a leitura: incorpore o que é lido junto, referencie o que é compartilhado ou ilimitado.
  • Indexe os campos pelos quais você filtra e ordena, e siga a regra ESR para índices compostos.
  • Projete sempre apenas os campos que o cliente precisa; nunca retorne segredos.
  • Coloque $match primeiro em um aggregation pipeline para que ele possa utilizar um índice.
  • Limite o crescimento de arrays e fique atento ao limite de 16 MB por documento.
  • Valide as gravações na aplicação ou com um validador de JSON Schema da coleção.
  • Use explain() antes de assumir que uma query está correta, e teste com dados em tamanho de produção.
  • Prefira a paginação por keyset em vez de grandes offsets de skip.

Erros comuns

  • Tratar o MongoDB como schemaless e permitir que os documentos assumam formatos incompatíveis.
  • Executar queries sem index e questionar por que a latência aumenta conforme a collection cresce.
  • Criar compound indexes na ordem errada, impedindo que o sort os utilize.
  • Incorporar um array que cresce indefinidamente até que os documentos atinjam o limite de tamanho.
  • Usar $lookup em cada requisição em vez de incorporar dados que são lidos juntos.
  • Recorrer a multi-document transactions quando um single-document update seria suficiente.
  • Retornar documentos inteiros e expor hashes de senhas ou tokens.
  • Escolher uma shard key de baixa cardinalidade e criar um write hotspot.

Próximos passos

O MongoDB ensina modelagem de documentos, design de índices e agregação — habilidades que são transferíveis para qualquer armazenamento de dados. Se os seus dados forem profundamente relacionais e exigirem joins e constraints no nível do banco de dados, leia o guia de PostgreSQL. Para leituras em microssegundos, expiração via TTL e contadores, combine o MongoDB com Redis. Se quiser conectá-lo a um serviço Node, revise os fundamentos de Node.js e, em seguida, compare o modelo de consulta com SQL.

Na pratica

Quatro comandos que cobrem a maior parte do trabalho

As mesmas estruturas aparecem no mongosh e no driver de Node.js: insert, update, aggregate e index.

mongosh
db.users.insertMany([
  { email: "[email protected]", name: "Ada", plan: "pro" },
  { email: "[email protected]", name: "Linus", plan: "free" },
]);

db.users.find(
  { plan: "pro" },
  { email: 1, name: 1, _id: 0 },
);

Projetando apenas o que você precisa

Uma projeção reduz os bytes que atravessam a rede e mantém campos sensíveis fora das respostas.

Preferir
db.users.find(
  { plan: "pro" },
  { email: 1, name: 1, _id: 0 },
);
Evitar
db.users.find({ plan: "pro" });
// every field crosses the wire,
// including passwordHash and tokens

Mantendo arrays limitados

Embuta dados que são lidos juntos, mas nunca deixe um único documento crescer sem limite. Documentos têm um teto de 16 MB.

Preferir
// Line items are read with the order.
db.orders.insertOne({
  customerId,
  items: [{ sku: "KB-01", qty: 1, price: 8900 }],
});
Evitar
// This array grows forever.
db.users.updateOne(
  { _id },
  { $push: { events: event } },
);

Trade-offs

O MongoDB é o armazenamento certo para isso?

O MongoDB é excelente para leituras em formato de documento e escala horizontal. Seus custos aparecem quando os dados são profundamente relacionais ou precisam de garantias fortes entre documentos.

Strengths

  • O objeto é o registro

    Armazenar um documento que já corresponde à estrutura da sua aplicação remove a camada de mapeamento, e muitas leituras tornam-se uma única busca.

  • Flexibilidade de schema

    Documentos em uma mesma coleção podem ter campos diferentes, o que é ideal para produtos em evolução e dados heterogêneos.

  • Escala horizontal nativa

    Replica sets e sharding são nativos, então o mesmo modelo de dados pode crescer de um único nó para um cluster.

Trade-offs

  • Relacionamentos tornam-se mais difíceis

    Sem joins por padrão, modelar dados muitos-para-muitos significa duplicação ou $lookup, e manter as cópias consistentes torna-se sua responsabilidade.

  • Schema-on-write ainda é um schema

    O servidor não impedirá um documento malformado. A validação deve residir no seu código ou em um validador JSON Schema.

  • Transações custam mais

    Transações multi-documento funcionam, mas são mais lentas e complexas do que uma atualização de documento único. Projete seu sistema para raramente precisar delas.

Perguntas frequentes

Perguntas frequentes

Keep learning

Related topics from the roadmap.

$ comecar a aprender

Pronto para aprender MongoDB?

Nosso tutorial interativo te guia por MongoDB passo a passo — com quizzes e codigo real que voce pode executar no navegador.