CRUD no MongoDB com Node.js e o driver oficial
Faça create, read, update e delete no MongoDB usando Node.js, com filtros, projeção, operadores, contagens reais e erro de _id duplicado.
CRUD é o conjunto de operações para criar, ler, atualizar e remover dados. Nesta lição, você vai executar as quatro com o driver oficial do MongoDB para Node.js e confirmar cada mudança por contagens e consultas reais.
Os nomes técnicos no driver são insert, find, update e delete. A sigla
serve como mapa mental; o método específico diz quantos documentos a operação
pretende atingir, como updateOne() ou updateMany().
Quatro pedidos diferentes no balcão
Imagine o balcão da Livraria Horizonte. “Cadastre estes livros” cria fichas; “mostre os ativos abaixo de R$ 50” lê fichas; “baixe duas unidades deste id” altera uma ficha; “retire este item inativo” remove uma ficha.
O atendente não recebe apenas um verbo. Ele recebe também um critério. No
MongoDB, o filtro cumpre esse papel. { _id: "livro-1" } é um pedido bem
delimitado; {} aceita qualquer documento. O limite da analogia é que métodos
como updateMany() e deleteMany() trabalham sobre conjuntos sem pedir
confirmação humana. Um filtro vazio pode atingir a coleção inteira.
Antes de avançar, confira documentos e coleções
se _id, BSON e campos internos ainda não estiverem claros.
Conecte uma vez e garanta o fechamento
Instale o pacote mongodb 7.5.0 em uma pasta vazia:
npm init -y
npm install mongodb@7.5.0Crie crud.mjs. O database exclusivo deixa dropDatabase() seguro dentro
deste laboratório. try/finally garante que o processo libere a conexão mesmo
quando provocarmos o erro do fim da aula:
import { MongoClient } from "mongodb";
const uri = process.env.MONGODB_URI ?? "mongodb://127.0.0.1:27017";
const client = new MongoClient(uri);
try {
await client.connect();
const db = client.db("devclub_crud");
await db.dropDatabase();
console.log(await db.command({ ping: 1 }));
// Os próximos exemplos entram aqui.
} finally {
await client.close();
}Criar MongoClient não comprova a conexão. connect() inicia a comunicação e
ping fornece uma evidência explícita. Em uma API, mantenha um cliente
reutilizável; abrir outro por requisição cria conexões e latência sem necessidade.
Create: insira um conjunto conhecido
Use ids determinados para repetir o teste. insertMany() devolve um resultado
de escrita, não os documentos completos:
const livros = db.collection("livros");
const criacao = await livros.insertMany([
{
_id: "livro-1",
titulo: "JavaScript do zero",
preco: 39.9,
estoque: 8,
ativo: true,
},
{
_id: "livro-2",
titulo: "Node.js na prática",
preco: 54.5,
estoque: 5,
ativo: true,
},
{
_id: "livro-3",
titulo: "CSS sem sustos",
preco: 29.9,
estoque: 0,
ativo: false,
},
]);
console.log({ operacao: "create", inseridos: criacao.insertedCount });insertedCount: 3 é a confirmação mensurável. Em uma inserção ordenada, um
erro interrompe os itens seguintes. Operações em lote têm opções próprias; não
presuma que uma lista inteira entrou sem ler o resultado ou tratar a falha.
Read: filtro, projeção e ordenação têm trabalhos diferentes
A vitrine quer livros ativos abaixo de R$ 50. ativo e $lt formam o filtro;
a projeção remove _id e limita os campos; sort() define a ordem:
const encontrados = await livros
.find(
{ ativo: true, preco: { $lt: 50 } },
{ projection: { _id: 0, titulo: 1, preco: 1 } },
)
.sort({ preco: 1 })
.toArray();
console.log({ operacao: "read", livros: encontrados });$lt significa “menor que”. Se quisesse incluir 50, seria $lte. A ordenação
1 é crescente e -1 é decrescente. Esses símbolos são parte da MongoDB Query
API, não nomes inventados pelo Node.
Use findOne() quando a intenção é receber no máximo um documento. Ele retorna
null quando nada combina; não lança um erro de “não encontrado”. Sua rota é
que decide se null deve virar HTTP 404.
Update: coloque a regra dentro do filtro
Uma venda de duas unidades só pode acontecer se houver pelo menos duas. Essa condição precisa participar da mesma operação que baixa o estoque:
const alteracao = await livros.updateOne(
{ _id: "livro-1", estoque: { $gte: 2 } },
{
$inc: { estoque: -2 },
$set: { atualizadoEm: new Date("2026-08-22T12:00:00.000Z") },
},
);
console.log({
operacao: "update",
encontrados: alteracao.matchedCount,
alterados: alteracao.modifiedCount,
});MongoDB garante atomicidade da escrita no nível de um documento. O filtro vê o
estoque e $inc o muda como uma operação única. Isso evita o intervalo entre
“li 8” e “gravei 6” no código da aplicação.
Contagem não substitui conferência quando você está aprendendo. Leia o estado final:
console.log(await livros.findOne(
{ _id: "livro-1" },
{ projection: { _id: 0, titulo: 1, estoque: 1 } },
));Se matchedCount fosse zero, poderia ser id inexistente ou estoque
insuficiente. Uma API pode fazer uma leitura complementar para distinguir as
mensagens, mas não deve desfazer a segurança do filtro condicional.
Quando uma operação precisa mudar vários documentos como unidade, a fronteira muda. A modelagem MongoDB mostra uma transação entre estoque e pedido, além de explicar quando incorporar evita essa coordenação.
Delete: o filtro deve descrever o motivo da remoção
Queremos retirar somente o livro já marcado como inativo. Colocar ativo: false
no filtro impede que o mesmo comando remova acidentalmente um livro reativado
por outra parte do sistema:
const remocao = await livros.deleteOne({
_id: "livro-3",
ativo: false,
});
console.log({
operacao: "delete",
removidos: remocao.deletedCount,
restantes: await livros.countDocuments(),
});Em sistemas com auditoria, exclusão física pode não ser a regra de negócio. Um
campo como arquivadoEm preserva histórico, mas também exige que consultas
normais filtrem os registros arquivados. “Soft delete” não é automaticamente
mais seguro; ele muda as obrigações do sistema.
Reproduza o _id duplicado
MongoDB cria um índice único para _id. Tentar cadastrar outro documento com
livro-1 faz o servidor rejeitar a escrita com código 11000:
try {
await livros.insertOne({
_id: "livro-1",
titulo: "Cópia",
preco: 10,
estoque: 1,
});
} catch (erro) {
console.log({
erro: "_id duplicado",
codigo: erro.code,
indice: "_id_",
});
}Não “resolva” criando outro id silenciosamente se esse id representa uma operação repetida. Às vezes, o duplicado é exatamente a proteção contra cobrar ou processar o mesmo pedido duas vezes. Entenda a identidade antes de tratar todo 11000 como mero inconveniente.
Driver oficial e Mongoose não são sinônimos
O pacote usado aqui é o MongoDB Node.js Driver oficial. Ele trabalha com coleções, filtros, cursores, sessões e resultados próximos da API do banco. Mongoose é um ODM: adiciona modelos, schemas na aplicação, hooks e outras convenções.
Minha ordem de aprendizado é driver primeiro. Assim, matchedCount, $inc,
projeção e índice continuam reconhecíveis quando uma abstração gera a consulta
errada. Depois, adote Mongoose se modelos e middleware trouxerem valor para o
time. O banco ainda precisa de regras próprias para proteger escritas vindas de
scripts, jobs ou outras aplicações.
Para transformar listas em relatórios, o próximo mecanismo é o aggregation pipeline. Para evitar que consultas frequentes examinem a coleção inteira, estude índices MongoDB.
Missão: uma venda que não deixa estoque negativo
Acrescente uma segunda chamada a updateOne(), pedindo 99 unidades do
livro-2, e imprima resultado e estoque final:
const tentativa = await livros.updateOne(
{ _id: "livro-2", estoque: { $gte: 99 } },
{ $inc: { estoque: -99 } },
);
const livro2 = await livros.findOne(
{ _id: "livro-2" },
{ projection: { _id: 0, estoque: 1 } },
);
console.log({
encontrados: tentativa.matchedCount,
alterados: tentativa.modifiedCount,
estoqueFinal: livro2.estoque,
});A missão termina quando a operação recusa a baixa e o estoque continua 5. Troque 99 por 3 e repita: as contagens devem virar 1 e o estoque, 2. Você não está apenas praticando sintaxe; está provando que o filtro carrega uma regra de negócio verificável.
Perguntas frequentes
O que significa CRUD no MongoDB?
Preciso abrir uma conexão para cada operação?
Qual a diferença entre find e findOne?
O driver oficial substitui Mongoose?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
Todo o código deste artigo foi executado em MongoDB 8.0.28, MongoDB Node.js Driver 7.5.0, Node 26.3.0 e Docker 29.5.3, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- MongoDB Node.js Driver — CRUD Operations — mongodb.com
- MongoDB Node.js Driver — Insert Documents — mongodb.com
- MongoDB Node.js Driver — Find Documents — mongodb.com
- MongoDB Manual — CRUD Operations — mongodb.com


