Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

MongoDB no Node.js: Driver Oficial

Atualizado em: 4 de setembro de 2026

Rack de servidores processando fluxos de dados no Node.js

Usar MongoDB no Node.js com o driver oficial oferece acesso direto a collections, documentos, índices, aggregation pipelines, transações e change streams. A aplicação trabalha com a API nativa do banco sem uma camada ODM obrigatória.

O driver é adequado quando a equipe quer controle explícito sobre queries e modelos, precisa usar recursos recentes do MongoDB ou prefere evitar abstrações adicionais. Em troca, validação, mapeamento e organização da persistência ficam sob responsabilidade da aplicação.

Neste guia, você aprenderá conexão, pooling, CRUD, ObjectId, índices, paginação, agregações, transações, sessões, segurança, TypeScript, testes e shutdown com o driver oficial.

Driver oficial do MongoDB

A documentação atual do MongoDB Node.js Driver cobre Atlas, Enterprise e Community. No momento da redação, a documentação marca a linha 7.x como atual.

Este artigo complementa o conteúdo introdutório O que é MongoDB, focando implementação em Node.js.

Instalação

npm install mongodb

Em TypeScript, os tipos já acompanham o pacote.

Conexão

import { MongoClient } from 'mongodb';

const client = new MongoClient(
  process.env.MONGODB_URI,
  {
    appName: 'orders-api'
  }
);

await client.connect();

const db = client.db('orders');

Crie um cliente por processo, não um por requisição.

Connection pool

MongoClient gerencia pool internamente. Opções relevantes incluem:

const client = new MongoClient(uri, {
  maxPoolSize: 50,
  minPoolSize: 5,
  maxIdleTimeMS: 30000,
  waitQueueTimeoutMS: 2000,
  serverSelectionTimeoutMS: 5000
});

Ajuste com métricas e capacidade do cluster. Pools grandes demais multiplicam conexões por réplica da aplicação.

Validando configuração

Não inicie sem URI válida. Consulte Variáveis de Ambiente no Node.js.

Collection tipada

type OrderDocument = {
  _id: ObjectId;
  customerId: ObjectId;
  status: 'pending' | 'approved' | 'cancelled';
  totalCents: number;
  createdAt: Date;
  updatedAt: Date;
  version: number;
};

const orders = db.collection<OrderDocument>('orders');

A tipagem ajuda no editor, mas não valida dados em runtime.

Inserindo documento

const now = new Date();

const result = await orders.insertOne({
  _id: new ObjectId(),
  customerId: new ObjectId(customerId),
  status: 'pending',
  totalCents,
  createdAt: now,
  updatedAt: now,
  version: 1
});

Use valores monetários inteiros e datas BSON.

ObjectId

function parseObjectId(value: string): ObjectId {
  if (!ObjectId.isValid(value)) {
    throw new InvalidIdError(value);
  }

  return new ObjectId(value);
}

Valide na borda antes de consultar.

Buscando por ID

const order = await orders.findOne({
  _id: parseObjectId(orderId)
});

findOne retorna null quando não encontra.

Projection

const order = await orders.findOne(
  { _id },
  {
    projection: {
      status: 1,
      totalCents: 1,
      customerId: 1
    }
  }
);

Evite transferir documentos grandes quando poucos campos são necessários.

Atualização

const result = await orders.updateOne(
  { _id },
  {
    $set: {
      status: 'approved',
      updatedAt: new Date()
    },
    $inc: { version: 1 }
  }
);

if (result.matchedCount === 0) {
  throw new OrderNotFoundError(orderId);
}

Lock otimista

const result = await orders.updateOne(
  {
    _id,
    version: expectedVersion
  },
  {
    $set: {
      status: 'approved',
      updatedAt: new Date()
    },
    $inc: { version: 1 }
  }
);

if (result.matchedCount === 0) {
  throw new OptimisticLockError(orderId);
}

Consulte Lock Otimista no Node.js.

findOneAndUpdate

const updated = await orders.findOneAndUpdate(
  { _id, version: expectedVersion },
  {
    $set: { status: 'approved' },
    $inc: { version: 1 }
  },
  { returnDocument: 'after' }
);

Use quando precisa do documento atualizado em uma única operação.

Exclusão

const result = await orders.deleteOne({ _id });

Em domínios com auditoria, prefira status ou deletedAt.

Upsert

await settings.updateOne(
  { key: 'billing-mode' },
  {
    $set: {
      value: 'automatic',
      updatedAt: new Date()
    }
  },
  { upsert: true }
);

Use quando “criar ou atualizar” é a semântica real.

Filtros

const cursor = orders.find({
  status: 'pending',
  createdAt: { $gte: createdAfter }
});

Construa filtros por allowlist. Não aceite operadores MongoDB arbitrários do usuário.

NoSQL Injection

Entradas como:

{ "email": { "$ne": null } }

podem alterar a query se o payload for repassado diretamente. Valide tipos e monte o filtro explicitamente.

Consulte Ajv no Node.js.

Ordenação

const SORT_FIELDS = {
  createdAt: 'createdAt',
  total: 'totalCents'
} as const;

cursor.sort({
  [SORT_FIELDS[input.sort]]: input.direction === 'asc' ? 1 : -1,
  _id: 1
});

Inclua um desempate estável.

Paginação por cursor

const filter = cursorId
  ? { _id: { $gt: new ObjectId(cursorId) } }
  : {};

const items = await orders
  .find(filter)
  .sort({ _id: 1 })
  .limit(limit + 1)
  .toArray();

Consulte Paginação em APIs Node.js.

Limitando resultados

Nunca deixe limit ilimitado. Defina default e máximo:

const limit = Math.min(
  Math.max(input.limit ?? 20, 1),
  100
);

Cursor e memória

Evite toArray() em milhões de documentos. Itere:

for await (const order of cursor) {
  await processOrder(order);
}

Aplique limites e backpressure no processamento.

Índices

await orders.createIndex({ customerId: 1, createdAt: -1 });
await orders.createIndex(
  { externalId: 1 },
  { unique: true }
);

Índices devem acompanhar padrões de consulta e constraints.

Índice parcial

await orders.createIndex(
  { nextAttemptAt: 1 },
  {
    partialFilterExpression: {
      status: 'retry_pending'
    }
  }
);

Isso reduz tamanho quando apenas parte dos documentos é relevante.

Explain

const plan = await orders
  .find(filter)
  .explain('executionStats');

Analise quantidade examinada, retornada e uso de índice.

Aggregation Pipeline

const result = await orders.aggregate([
  {
    $match: {
      createdAt: { $gte: start, $lt: end }
    }
  },
  {
    $group: {
      _id: '$status',
      totalOrders: { $sum: 1 },
      totalCents: { $sum: '$totalCents' }
    }
  },
  { $sort: { totalOrders: -1 } }
]).toArray();

Coloque filtros seletivos cedo.

Lookup

$lookup faz junções entre collections, mas documentos embutidos ou read models podem ser melhores. Meça custo e cardinalidade.

Modelagem embutida

{
  _id,
  status,
  items: [
    { productId, nameSnapshot, quantity, priceCents }
  ]
}

Embuta quando dados são lidos e atualizados juntos e o tamanho é limitado.

Referências

Use IDs entre documentos quando ciclos de vida são independentes ou arrays podem crescer sem limite.

Limite do documento

MongoDB possui limite por documento. Não mantenha históricos ou eventos ilimitados dentro de um único agregado.

Schema validation no banco

Collections podem usar JSON Schema para proteção adicional. Validação na aplicação continua necessária.

Transações

const session = client.startSession();

try {
  await session.withTransaction(async () => {
    await orders.insertOne(order, { session });
    await outbox.insertOne(event, { session });
  });
} finally {
  await session.endSession();
}

Transações exigem topologia compatível. Mantenha curtas.

Unit of Work

Uma Unit of Work pode expor repositories ligados à mesma session. Consulte Unit of Work no Node.js.

Retry de transação

withTransaction pode repetir determinados erros transitórios. O callback não deve executar e-mail ou pagamento externo não idempotente.

Outbox

Salve documento e evento na mesma transação quando publicar depois. Consulte Outbox Pattern no Node.js.

Change Streams

const stream = orders.watch([
  {
    $match: {
      operationType: 'insert'
    }
  }
]);

for await (const change of stream) {
  await handleChange(change);
}

Change streams exigem resiliência, resume token e tratamento de duplicidade.

Não substitua outbox automaticamente

Change stream observa alterações, mas o contrato de integração pode exigir transformação, versionamento e confirmação transacional explícita.

Read Concern e Write Concern

Configure garantias conforme criticidade. Valores mais fortes podem aumentar latência. Não altere defaults sem entender topologia e requisitos.

Read Preference

Leituras em secondary podem retornar dados atrasados. Não use para fluxos que precisam ler imediatamente uma escrita recém-confirmada.

Timeouts

Use limites de seleção e operação. AbortSignal pode cancelar operações suportadas pela versão do driver.

Repository Pattern

Encapsule queries e mapeamento:

class MongoOrderRepository
  implements OrderRepository {
  constructor(
    private readonly collection: Collection<OrderDocument>
  ) {}
}

Consulte Repository Pattern no Node.js.

Mapper

const OrderMapper = {
  toDomain(doc: OrderDocument): Order {
    return Order.restore({
      id: OrderId.from(doc._id.toHexString()),
      status: doc.status,
      total: Money.create(doc.totalCents, 'BRL'),
      version: doc.version
    });
  }
};

Não deixe ObjectId e BSON vazarem para o domínio.

Migrations

MongoDB é flexível, mas documentos antigos continuam existindo. Use:

  • campo schemaVersion;
  • leitura compatível;
  • backfill em lotes;
  • índices criados com planejamento;
  • remoção posterior de compatibilidade.

Backfill

Atualize em lotes por _id e monitore carga. Não faça um update massivo sem testar impacto.

Segurança

  • use TLS;
  • privilégio mínimo;
  • credenciais por Secret;
  • restrição de rede;
  • rotação;
  • auditoria;
  • não registre URI.

Consulte Gestão de Segredos no Node.js.

Command monitoring

O driver oferece eventos de comandos. Use com cuidado para não registrar filtros e documentos sensíveis.

Observabilidade

Meça:

  • tempo de aquisição;
  • latência de operação;
  • timeouts;
  • pool em uso;
  • erros por código;
  • documentos examinados;
  • lag de change streams.

Shutdown

await client.close();

Pare de aceitar novas requisições, conclua operações e feche o client. Consulte Graceful Shutdown no Node.js.

Testes

Use uma instância real em container para validar:

  • ObjectId;
  • índices unique;
  • agregações;
  • transações;
  • mapeamento;
  • paginação;
  • concorrência;
  • change streams.

Não confie apenas em mocks

Mocks não reproduzem BSON, índices, session, topologia ou pipeline. Use integração para repositories.

Erros comuns

  • Cliente por request: conexões explodem.
  • Payload como filtro: NoSQL injection.
  • Sem índices: scans crescem.
  • toArray sem limite: memória é consumida.
  • ObjectId no domínio: infraestrutura vaza.
  • Documento ilimitado: limite é atingido.
  • Transação longa: contenção aumenta.
  • Secondary para leitura crítica: dados ficam atrasados.

Boas práticas

  • Reutilize MongoClient.
  • Tipifique collections.
  • Valide em runtime.
  • Monte filtros explicitamente.
  • Crie índices por consulta.
  • Use cursor e limites.
  • Mapeie para domínio.
  • Mantenha transações curtas.
  • Proteja credenciais.
  • Teste em MongoDB real.

Conclusão

Usar MongoDB no Node.js com o driver oficial fornece controle direto sobre documents, queries, índices, aggregations e transações. A conexão deve ser compartilhada e o pool configurado com base em carga real.

Com validação, filtros explícitos, índices, mappers, segurança e testes de integração, o driver oficial atende aplicações robustas sem exigir um ODM. A próxima etapa é comparar essa abordagem com Mongoose, que adiciona schemas e middleware na aplicação.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita