Boas Práticas de MongoDB com Node.js: Mongoose, Aggregation Pipeline e Índices para Times Ágeis Já leu

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: 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: 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

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.

Referências


Artigos relacionados