MongoDB com Node.js: Mongoose, Aggregation Pipeline e Índices
Trabalhar com MongoDB em Node.js é uma experiência poderosa quando você domina as ferramentas certas. Neste guia, vamos explorar três pilares fundamentais: o Mongoose como ODM (Object Document Mapper), o Aggregation Pipeline para transformações complexas de dados, e os índices para otimização de performance. Esses conhecimentos são essenciais para construir aplicações escaláveis e eficientes.
Configuração Inicial e Conceitos Fundamentais
Antes de começar, instale as dependências necessárias:
npm install mongoose dotenv
O Mongoose é uma abstração que oferece validação de schema, middlewares e métodos auxiliares poderosos. Ao contrário do MongoDB puro, você trabalha com schemas tipados, o que reduz bugs em produção. Veja como criar uma conexão e definir um schema básico:
// config/database.js
const mongoose = require('mongoose');
mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
});
const userSchema = new mongoose.Schema({
nome: { type: String, required: true, trim: true },
email: { type: String, required: true, unique: true, lowercase: true },
idade: { type: Number, min: 18, max: 120 },
criado_em: { type: Date, default: Date.now }
});
const User = mongoose.model('User', userSchema);
module.exports = User;
Aqui definimos validações direto no schema: campos obrigatórios, tipos de dados, transformações (trim, lowercase) e valores padrão. Isso simplifica drasticamente a lógica de negócio na sua aplicação.
Mongoose: Operações CRUD e Validações Avançadas
Criando e Consultando Documentos
O Mongoose oferece métodos encadeáveis para queries que melhoram a legibilidade do código. Veja operações comuns:
// Criar um novo usuário
const novoUsuario = await User.create({
nome: 'João Silva',
email: 'joao@email.com',
idade: 28
});
// Buscar por ID
const usuario = await User.findById('507f1f77bcf86cd799439011');
// Buscar com múltiplas condições
const usuarios = await User.find({ idade: { $gte: 25 } })
.select('nome email')
.sort({ criado_em: -1 })
.limit(10);
// Atualizar
await User.findByIdAndUpdate(
'507f1f77bcf86cd799439011',
{ idade: 30 },
{ new: true, runValidators: true }
);
// Deletar
await User.findByIdAndDelete('507f1f77bcf86cd799439011');
Middleware e Hooks Customizados
O Mongoose permite executar funções antes (pre) e depois (post) de operações específicas. Isso é útil para criptografia, logs e transformações:
userSchema.pre('save', async function(next) {
if (!this.isModified('senha')) return next();
// Simular hash de senha
this.senha = `hashed_${this.senha}`;
next();
});
userSchema.post('save', function(doc) {
console.log(`Usuário ${doc.nome} foi salvo com sucesso`);
});
Aggregation Pipeline: Transformações Poderosas de Dados
Estrutura e Casos de Uso
O Aggregation Pipeline é o coração da análise de dados no MongoDB. Diferente do .find(), ele permite transformações complexas em múltiplos estágios:
// Exemplo: Agrupar usuários por faixa etária e contar
const relatorio = await User.aggregate([
{
$match: { idade: { $gte: 18 } }
},
{
$group: {
_id: {
$cond: [
{ $lt: ['$idade', 30] },
'Jovem',
'Adulto'
]
},
total: { $sum: 1 },
mediaIdade: { $avg: '$idade' }
}
},
{
$sort: { total: -1 }
}
]);
console.log(relatorio);
// Output: [
// { _id: 'Adulto', total: 15, mediaIdade: 45 },
// { _id: 'Jovem', total: 10, mediaIdade: 25 }
// ]
Exemplo Prático: Pipeline Completo
Imagine que você precisa de um relatório de usuários ativos com suas estatísticas. Vamos construir um pipeline realista:
const estatisticas = await User.aggregate([
// Estágio 1: Filtrar usuários ativos
{ $match: { ativo: true } },
// Estágio 2: Transformar documento
{
$project: {
nome: 1,
email: 1,
faixaEtaria: {
$cond: [
{ $lt: ['$idade', 30] },
'Sub-30',
{ $cond: [{ $lt: ['$idade', 50] }, '30-50', '50+'] }
]
}
}
},
// Estágio 3: Agrupar por faixa
{
$group: {
_id: '$faixaEtaria',
usuarios: { $push: '$nome' },
quantidade: { $sum: 1 }
}
},
// Estágio 4: Ordenar
{ $sort: { quantidade: -1 } }
]);
Esse pipeline é executado no servidor MongoDB, não na aplicação, o que economiza banda e processamento.
Índices: Otimizando a Performance
Criando e Gerenciando Índices
Índices são fundamentais para queries rápidas em coleções grandes. No Mongoose, você os define no schema:
const userSchema = new mongoose.Schema({
nome: { type: String, required: true },
email: { type: String, required: true, index: true, unique: true },
idade: { type: Number, index: true },
cidade: String,
criado_em: { type: Date, default: Date.now, index: true }
});
// Índice composto para queries frequentes
userSchema.index({ idade: 1, cidade: 1 });
// Índice texto para busca full-text
userSchema.index({ nome: 'text' });
Explicando Performance com explain()
Use o método .explain() para entender como MongoDB executa suas queries:
// Sem índice - COLLSCAN (varre toda coleção)
const resultadoSemIndice = await User.find({ email: 'teste@email.com' })
.explain('executionStats');
// Com índice - IXSCAN (usa índice)
const resultadoComIndice = await User.find({ email: 'teste@email.com' })
.explain('executionStats');
console.log(resultadoComIndice.executionStats.executionStages.stage);
// Output: 'IXSCAN' (mais rápido)
Dica profissional: Crie índices apenas para campos que você consulta frequentemente. Cada índice consome memória e desacelera inserts. Balance performance de leitura com eficiência de escrita.
Conclusão
Dominando MongoDB com Node.js, você alcança três objetivos críticos: validação robusta através do Mongoose, eliminando classes inteiras de bugs; transformações eficientes via Aggregation Pipeline, processando dados no servidor ao invés da aplicação; e otimização garantida com índices estratégicos, mantendo queries rápidas mesmo com milhões de registros. Esses conhecimentos formam a base para aplicações production-ready e escaláveis.