MongoDB: Projeto Abrangente

Última atualização: 2026-08-26

Projetos integrados são a melhor maneira de avaliar os resultados de aprendizagem — este curso desenvolve um sistema completo de análise de comércio eletrônico por meio de seis módulos, reunindo todos os conceitos abordados nas Fases 1 a 5.

1. Visão geral do projeto

Projeto: Sistema de Avaliação do ShopHub Arquitetura: Node.js + Express + Mongoose + MongoDB + JWT Recursos: Autenticação de usuários, gerenciamento de produtos, comentários aninhados, análises agregadas, controle de permissões, implantação no Atlas

Abordagem de projeto da arquitetura do sistema: O sistema de avaliações de comércio eletrônico adota uma arquitetura clássica de três camadas — a camada de roteamento (mapeamento de URL + middleware), a camada de negócios (controlador responsável pela lógica de solicitações) e a camada de dados (modelo Mongoose que define estruturas de dados e validação). A separação dessas três camadas permite que cada uma seja testada e modificada de forma independente: alterar os caminhos da API não afeta a lógica de negócios, e alterar os métodos de consulta não afeta as definições de roteamento.

Processo de tomada de decisão na seleção de arquitetura:

Momento de decisão Opção A Opção B Escolha Motivo
Frameworks da Web Express Koa / Fastify Express Possui o ecossistema mais maduro e o maior número de recursos didáticos
ODM Mongoose Driver nativo Mongoose Validação de esquema + Middleware + Preenchimento
Esquemas de autenticação JWT Sessão JWT Sem estado, facilmente escalável, adequado para APIs
Estrutura de comentários Documentos aninhados Modo de citação Modo de citação (parentId) Profundidade flexível, evitando limites de aninhamento
Plataforma de implantação Atlas + Render Auto-hospedada Atlas + Render Serviço gerenciado que reduz custos de operação e manutenção, adequado para projetos de pequeno e médio porte

Decisões de modelagem de dados: O sistema de comentários optou por um modelo de referência (em que parentId aponta para o comentário pai) em vez de documentos aninhados — os documentos aninhados estão sujeitos ao limite de 16 MB do BSON, e respostas profundamente aninhadas são difíceis de consultar e paginar. Embora o modelo de referência exija consultas adicionais para montar a estrutura em árvore, ele suporta níveis ilimitados e classificação flexível.

100%
graph TB
    Client[Browser/Mobile App] -->|HTTP| Express[Express Server]

    subgraph "Express Routing Layer"
        Express --> AuthMW[auth Routing<br/>Register/Log In]
        Express --> ProductMW[products Routing<br/>CRUD]
        Express --> ReviewMW[reviews Routing<br/>Comments/Likes]
    end

    subgraph "Controller Layer"
        AuthMW --> AuthCtrl[authController]
        ProductMW --> ProdCtrl[productController]
        ReviewMW --> RevCtrl[reviewController]
    end

    subgraph "Model Layer"
        AuthCtrl --> UserModel[User Model]
        ProdCtrl --> ProdModel[Product Model]
        RevCtrl --> RevModel[Review Model]
    end

    subgraph "MongoDB Gathering"
        UserModel --> Users[(users)]
        ProdModel --> Products[(products)]
        RevModel --> Reviews[(reviews)]
    end

    style Express fill:#d4edda
    style Reviews fill:#cce5ff

Relações do modelo de dados:

100%
erDiagram
    USER ||--o{ REVIEW : "writes"
    PRODUCT ||--o{ REVIEW : "has"
    REVIEW ||--o{ REVIEW : "parent reply"

    USER {
        ObjectId _id PK
        string email UK
        string username UK
        string passwordHash
        string role
        boolean isActive
    }
    PRODUCT {
        ObjectId _id PK
        string sku UK
        string title
        number price
        string category
        number rating
        number reviewCount
    }
    REVIEW {
        ObjectId _id PK
        ObjectId productId FK
        ObjectId userId FK
        string content
        number rating
        ObjectId parentId FK
        number likeCount
        boolean isApproved
    }

2. Módulo 1: Inicialização do projeto

Uma explicação detalhada do processo de seleção da arquitetura: Escolher a tecnologia certa para um sistema de avaliações de comércio eletrônico não significa “optar pelo que estiver na moda”, mas sim encontrar a solução ideal com base em restrições — 1. Escolha o Express em vez do NestJS: para projetos de pequeno a médio porte, os decoradores e a injeção de dependências (DI) do NestJS aumentam a complexidade sem oferecer benefícios adicionais; 2. Escolha o Mongoose em vez do driver nativo: a validação de esquema e os mecanismos de middleware são essenciais para um sistema de avaliações (hash de senha, filtragem de exclusão temporária); 3. Escolha o JWT em vez de sessões: os serviços de API exigem operação sem estado; as sessões exigem armazenamento compartilhado no Redis, o que aumenta os custos operacionais; 4. Escolha o modelo de referência em vez de documentos aninhados: o número de avaliações é imprevisível, e documentos aninhados apresentam o risco de atingir o limite de 16 MB.

Princípios de interação entre módulos: As dependências entre os seis módulos formam uma hierarquia clara — o Módulo 1 (Inicialização) fornece a infraestrutura; o Módulo 2 (Modelo) define o contrato de dados; o Módulo 3 (CRUD) implementa a lógica de negócios; o Módulo 4 (Agregação) adiciona recursos analíticos; o Módulo 5 (Segurança) reforça a proteção; e o Módulo 6 (Implantação) conclui a implantação. Cada módulo depende apenas do módulo anterior e não possui dependências retroativas, o que permite que o desenvolvimento ocorra de forma iterativa — primeiro concluindo os Módulos 1 a 3 para obter uma API utilizável e, em seguida, adicionando gradualmente a agregação, a segurança e a implantação.

Princípios de design para a inicialização de projetos: A inicialização de um projeto não se resume apenas a executar o comando “npm init”; trata-se também de um processo que envolve definir a estrutura do projeto, selecionar dependências e estabelecer estratégias de gerenciamento de configuração. Um projeto bem estruturado deve refletir a arquitetura MVC: definições de dados em models/, lógica de negócios em controllers/, mapeamentos de rotas em routes/ e questões transversais (como autenticação e tratamento de erros) em middlewares/.

Princípios para a criação de estruturas de diretórios:

Índice Responsabilidades Orientações sobre dependências Estratégia de testes
modelos/ Definição e validação de dados Sem dependências externas Testes unitários
controladores/ Tratamento de solicitações + coordenação Depende dos modelos Testes de integração
rotas/ Mapeamento de URL → Controlador Dependências: controladores + middlewares Teste de rotas
middlewares/ Autenticação/Autorização/Tratamento de erros Configuração de dependências Testes unitários
validadores/ Definição do esquema joi Sem dependências externas Testes unitários
utils/ Funções utilitárias Sem dependências externas Testes unitários

Motivos para a seleção de dependências:

Dependência Finalidade Por que escolhê-la
Express Estrutura web A mais madura, com um rico ecossistema de middleware
Mongoose ODM Validação de esquema + preenchimento + middleware
jsonwebtoken Autenticação JWT Autenticação sem estado, adequada para APIs
bcrypt Hash de senha Padrão do setor, resistente a tabelas rainbow
joi Validação de entrada No estilo de esquema, reutilizável
dotenv Variáveis de ambiente Diretrizes do 12-Factor App
cors origem cruzada noções básicas sobre API

(1) Estrutura do projeto

BASH
shophub-reviews/
├── package.json
├── .env
├── .env.example
├── src/
│   ├── app.js              # Express Applications
│   ├── config/
│   │   └── db.js          # mongoose Connect
│   ├── models/            # Data Model
│   │   ├── User.js
│   │   ├── Product.js
│   │   └── Review.js
│   ├── controllers/       # Business Logic
│   │   ├── authController.js
│   │   ├── productController.js
│   │   └── reviewController.js
│   ├── routes/            # Routing
│   │   ├── auth.js
│   │   ├── products.js
│   │   └── reviews.js
│   ├── middlewares/       # Middleware
│   │   ├── auth.js        # JWT Certification
│   │   ├── errorHandler.js
│   │   └── validate.js
│   ├── validators/        # Request for a Trial Certificate
│   │   └── schemas.js
│   └── utils/             # Tools
│       ├── logger.js
│       └── jwt.js
└── README.md

(2) package.json

JSON
{
  "name": "shophub-reviews",
  "version": "1.0.0",
  "scripts": {
    "start": "node src/app.js",
    "dev": "nodemon src/app.js",
    "test": "jest --watch"
  },
  "dependencies": {
    "express": "^4.19.2",
    "mongoose": "^7.6.0",
    "jsonwebtoken": "^9.0.0",
    "bcrypt": "^5.1.0",
    "joi": "^17.13.0",
    "dotenv": "^16.4.0",
    "cors": "^2.8.5"
  },
  "devDependencies": {
    "nodemon": "^3.1.0",
    "jest": "^29.7.0"
  }
}

(3) Arquivo .env

BASH
NODE_ENV=development
PORT=3000
MONGODB_URI=mongodb://localhost:27017/shophub
JWT_SECRET=your-super-secret-key-change-in-prod
JWT_EXPIRES_IN=7d

3. Módulo 2: Modelos de usuário e de produto

Princípios de projeto do modelo de dados: O projeto do esquema não se resume simplesmente a “copiar os campos da tabela para o Mongoose”; ao contrário, envolve levar em consideração: padrões de consulta (quais são as consultas mais comuns?), relações entre os dados (quais entidades precisam ser associadas?), requisitos de desempenho (são necessários índices? Deve-se definir select:false para determinados campos?) e requisitos de segurança (as senhas devem ser mascaradas? É necessária a exclusão temporária?).

Decisões sobre o projeto do esquema:

Decisões de projeto Escolha Razão
Armazenamento de senhas passwordHash + select:false Não é retornado em consultas padrão para evitar a divulgação
Hash de senha middleware de pré-salvamento + bcrypt Geração automática de hash; transparente para a lógica de negócios
Definição de funções enum + RBAC Três funções: cliente/administrador/moderador
Avaliação do produto Campos redundantes: avaliação + número de avaliações Evitar calcular o agregado a cada vez
Estrutura dos comentários Referência ao parentId Suporta níveis ilimitados de aninhamento
Exclusão temporária isDeleted + filtragem prévia Os dados são recuperáveis; requisitos de conformidade
Carimbos de data/hora timestamps: true Gerenciamento automático de createdAt/updatedAt

Relações entre modelos e direções de referência: Uma Avaliação faz referência a um Usuário e a um Produto (muitos para um), e uma Avaliação faz referência a si mesma (a autorreferência permite o aninhamento). A direção da referência é “o lado muitos para um faz referência ao lado um para muitos” — ou seja, em vez de incorporar uma matriz de Avaliações dentro de um Produto (o que resultaria em crescimento infinito), uma Avaliação armazena um productId.

100%
graph LR
    User1[Alice] -->|userId| R1[Review: "Great!"]
    User2[Bob] -->|userId| R2[Review: "Good"]
    User3[Charlie] -->|userId| R3[Reply: "Thanks!"]
    
    Prod1[Product: Phone] -->|productId| R1
    Prod1 -->|productId| R2
    R1 -->|parentId| R3

    style User1 fill:#cce5ff
    style Prod1 fill:#d4edda
    style R1 fill:#fff3cd

(1) Modelo de usuário (incluindo o middleware de hash de senha)

JAVASCRIPT
// models/User.js
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');

const UserSchema = new mongoose.Schema({
  email: {
    type: String,
    required: [true, 'Email is required'],
    unique: true,
    lowercase: true,
    trim: true,
    match: [/^\S+@\S+\.\S+$/, 'Invalid email format']
  },
  username: {
    type: String,
    required: true,
    unique: true,
    minlength: 3,
    maxlength: 30,
    match: [/^[a-zA-Z0-9_]+$/, 'Alphanumeric + underscore only']
  },
  passwordHash: {
    type: String,
    required: true,
    minlength: 60,  // bcrypt hash Length
    select: false
  },
  role: {
    type: String,
    enum: ['customer', 'admin', 'moderator'],
    default: 'customer'
  },
  isActive: { type: Boolean, default: true }
}, { timestamps: true });

// Password Hashing Middleware
UserSchema.pre('save', async function(next) {
  if (!this.isModified('passwordHash')) return next();
  this.passwordHash = await bcrypt.hash(this.passwordHash, 10);
  next();
});

// Confirm Password
UserSchema.methods.comparePassword = function(candidate) {
  return bcrypt.compare(candidate, this.passwordHash);
};

module.exports = mongoose.model('User', UserSchema);

(2) Modelo do produto

JAVASCRIPT
// models/Product.js
const ProductSchema = new mongoose.Schema({
  sku: { type: String, required: true, unique: true, index: true },
  title: { type: String, required: true, maxlength: 200 },
  description: { type: String, maxlength: 5000 },
  price: { type: mongoose.Schema.Types.Decimal128, required: true, min: 0 },
  category: { type: String, enum: ['Electronics', 'Books', 'Clothing', 'Home'], index: true },
  stock: { type: Number, default: 0, min: 0 },
  rating: { type: Number, default: 0, min: 0, max: 5 },
  reviewCount: { type: Number, default: 0 },
  isActive: { type: Boolean, default: true, index: true }
}, { timestamps: true });

ProductSchema.index({ category: 1, price: -1 });
ProductSchema.index({ title: 'text', description: 'text' });

module.exports = mongoose.model('Product', ProductSchema);

(3) Modelo de revisão (incluindo respostas aninhadas)

JAVASCRIPT
// models/Review.js
const ReviewSchema = new mongoose.Schema({
  productId: { type: mongoose.Schema.Types.ObjectId, ref: 'Product', required: true, index: true },
  userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  content: { type: String, required: true, maxlength: 1000 },
  rating: { type: Number, required: true, min: 1, max: 5 },
  parentId: { type: mongoose.Schema.Types.ObjectId, ref: 'Review', default: null, index: true },
  likes: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
  likeCount: { type: Number, default: 0 },
  isApproved: { type: Boolean, default: true },
  isDeleted: { type: Boolean, default: false }
}, { timestamps: true });

ReviewSchema.index({ productId: 1, createdAt: -1 });
ReviewSchema.pre(/^find/, function(next) {
  this.where({ isDeleted: { $ne: true } });
  next();
});

module.exports = mongoose.model('Review', ReviewSchema);

4. Módulo 3: API CRUD de comentários (comentários aninhados)

Notas adicionais sobre os princípios de design de comentários aninhados: A estratégia de consulta para o modelo de referência (parentId) exige um equilíbrio entre o “número de consultas” e a “integridade dos dados” — o método de duas consultas (comentários de nível superior + respostas) requer apenas duas operações de E/S no banco de dados, mas suporta apenas dois níveis de aninhamento; o método de consulta recursiva ($graphLookup) suporta um número ilimitado de níveis, mas apresenta baixo desempenho. Este sistema opta pelo método de duas consultas combinado com montagem em memória porque: 1. A maioria dos usuários visualiza apenas dois níveis de comentários (nível superior + respostas); 2. Respostas mais profundas podem ser carregadas sob demanda por meio do botão “Ver mais respostas”; 3. O desempenho do método de duas consultas é muito superior ao da consulta recursiva.

Projeto da Política de Segurança de Comentários: Os sistemas de comentários enfrentam três tipos de ameaças à segurança — 1. Segurança de conteúdo: comentários de spam, palavras-chave sensíveis e links publicitários (proteção: limitação de taxa + filtragem de palavras-chave sensíveis + moderação manual); 2. Segurança de permissões: edição ou exclusão de comentários alheios por usuários que não sejam os autores (proteção: autenticação + verificação de ID do usuário); 3. Segurança contra injeções: injeção $where, DoS por $regex (proteção: consultas parametrizadas + escapamento de expressões regulares). As medidas de segurança devem ser implementadas de maneira uniforme na camada de middleware, em vez de serem duplicadas em cada controlador.

100%
graph TB
    subgraph "Nested Comments (Threaded Comments)"
        A[Review<br/>_id: ObjectId] --> B[Reply 1<br/>parentId: A._id]
        A --> C[Reply 2<br/>parentId: A._id]
        B --> D[Reply to Reply<br/>parentId: B._id]
        C --> E[Reply to Reply<br/>parentId: C._id]
    end

    style A fill:#d4edda
    style B fill:#fff3cd
    style C fill:#fff3cd
    style D fill:#f8d7da
    style E fill:#f8d7da

O principal desafio dos comentários aninhados (comentários em threads) é como representar a relação de “resposta”. Duas abordagens: (1) Documentos incorporados (incorporar a matriz de respostas dentro do Review) — simples, mas limitada pelo limite de 16 MB do BSON, e respostas profundas são difíceis de paginar; (2) Padrão de referência (parentId aponta para o comentário pai) — flexível, suporta aninhamento ilimitado, suporta paginação, mas requer consultas adicionais para montar a estrutura em árvore. Este projeto utiliza o padrão de referência.

Estratégia de consulta de comentários aninhados:

100%
sequenceDiagram
    Client Participant
    participant API as /products/:id/reviews
    participant DB as MongoDB

    Client->>API: GET /products/123/reviews
    API->>DB: Query top-level comments (parentId=null)
    DB-->>API: Returns [R1, R2, R3]
    API->>DB: Query all replies (parentId in [R1, R2, R3]._id)
    DB-->>API: Returns [R1.1, R1.2, R2.1]
    API->>API: Building a tree structure<br/>R1.replies=[R1.1, R1.2]<br/>R2.replies=[R2.1]
    API-->>Client: Returns tree-structured data

Análise do projeto da API CRUD:

Operação Ponto de extremidade Método Autenticação Regras de negócios
Ver avaliações /products/:id/reviews GET Não Paginação + respostas aninhadas
Avaliação de postagem /products/:id/reviews POST Sim Verificar se o produto existe + classificação de 1 a 5
Publicar resposta /products/:id/reviews PUBLICAR Sim Verificar se a avaliação principal existe
Editar avaliação /reviews/:id PUT Sim (autor) Apenas o autor pode editar
Excluir avaliação /reviews/:id EXCLUIR Sim (autor/administrador) Exclusão temporária: isDeleted
Curtir/Descurtir /reviews/:id/like POST Sim Alternar com $addToSet/$pull

O recurso de “curtir” utiliza $addToSet (adição idempotente) + $pull (remoção) em vez de push (que poderia causar duplicatas). Ele também mantém um campo redundante chamado likeCount para evitar ter que contar as curtidas a cada vez.

(1) Criar avaliação

JAVASCRIPT
// controllers/reviewController.js
exports.createReview = async (req, res) => {
  const { productId } = req.params;
  const { content, rating, parentId } = req.body;

  // Verify that the product exists
  const product = await Product.findById(productId);
  if (!product) return res.status(404).json({ error: 'Product not found' });

  // If this is a reply, verify that the parent comment exists
  if (parentId) {
    const parent = await Review.findById(parentId);
    if (!parent) return res.status(404).json({ error: 'Parent review not found' });
  }

  const review = await Review.create({
    productId,
    userId: req.user._id,
    content,
    rating,
    parentId: parentId || null
  });

  // Update product rating statistics
  await updateProductRating(productId);

  await review.populate('userId', 'username avatar');
  res.status(201).json(review);
};

(2) Consulta aninhada

JAVASCRIPT
exports.listReviews = async (req, res) => {
  const { productId } = req.params;
  const { sort = 'createdAt', order = 'desc', limit = 20, page = 1 } = req.query;

  // Query top-level comments
  const reviews = await Review.find({
    productId,
    parentId: null
  })
    .populate('userId', 'username avatar')
    .sort({ [sort]: order === 'desc' ? -1 : 1 })
    .limit(limit * 1)
    .skip((page - 1) * limit)
    Continue

  // View all replies
  const reviewIds = reviews.map(r => r._id);
  const replies = await Review.find({
    parentId: { $in: reviewIds }
  })
    .populate('userId', 'username avatar')
    .sort({ createdAt: 1 })
    Continue

  // Build a tree structure
  const tree = reviews.map(parent => ({
    ...parent,
    replies: replies.filter(r => r.parentId.toString() === parent._id.toString())
  }));

  res.json({ data: tree, page, limit });
};

(3) Recurso “Curtir”

JAVASCRIPT
exports.likeReview = async (req, res) => {
  const { reviewId } = req.params;
  const userId = req.user._id;

  const review = await Review.findById(reviewId);
  if (!review) return res.status(404).json({ error: 'Review not found' });

  const alreadyLiked = review.likes.some(id => id.toString() === userId.toString());

  if (alreadyLiked) {
    await Review.updateOne(
      { _id: reviewId },
      { $pull: { likes: userId }, $inc: { likeCount: -1 } }
    );
    return res.json({ liked: false });
  } else {
    await Review.updateOne(
      { _id: reviewId },
      { $addToSet: { likes: userId }, $inc: { likeCount: 1 } }
    );
    return res.json({ liked: true });
  }
};

5. Módulo 4: Análise de agregação

Detalhes do padrão de projeto do pipeline de agregação: Os requisitos de agregação do sistema de avaliações se enquadram em três categorias: 1. Estatísticas unidimensionais (distribuição de notas, contagem de avaliações): um único pipeline com $match + $group/$bucket; 2. Estatísticas multidimensionais paralelas (visão geral da página de detalhes do produto + distribuição + avaliações recentes): é necessário usar $facet para retornar os resultados em uma única consulta; 3. Estatísticas vinculadas entre coleções (rankings de produtos populares com informações sobre os produtos): combinação de $group + $lookup + $unwind. Princípio de projeto: primeiro $match para filtrar e reduzir o volume de dados, depois $group para agregar e, por fim, $sort/$limit para ordenar e limitar.

Estratégia de projeto dos sub-pipelines do $facet: Cada sub-pipeline no $facet deve ser o mais enxuto possível: 1. O sub-pipeline de contagem total precisa apenas de $count, com sobrecarga mínima; 2. O sub-pipeline de média utiliza $group({ _id: null }), produzindo uma única saída; 3. O sub-pipeline de distribuição utiliza $group({ _id: '$rating' }), com contagem de saídas igual ao tamanho do domínio de valores; 4. O sub-pipeline de lista requer $sort + $limit + $lookup, apresentando a maior sobrecarga e deve ser colocado por último. A ordem dos sub-pipelines não afeta a execução (paralela), mas afeta a legibilidade do código — recomenda-se organizá-los do menos ao mais oneroso.

100%
graph LR
    subgraph "Aggregation Pipeline"
        A[Raw Data<br/>1M reviews] -->|"$match"| B[Filtered Data<br/>100K reviews]
        B -->|"$group"| C[Aggregated<br/>grouped]
        C -->|"$sort + $limit"| D[Top Results<br/>top 10]
    end

    style A fill:#f8d7da
    style B fill:#fff3cd
    style C fill:#d4edda
    style D fill:#d4edda

O sistema de avaliações precisa de estatísticas multidimensionais — distribuição das notas (quantas de 5 estrelas/4 estrelas), nota média, rankings dos produtos mais populares e tendências recentes nas avaliações. Se essas estatísticas forem calculadas no código da camada de aplicação, grandes quantidades de dados precisam ser transferidas para o Node.js para processamento; com pipelines de agregação, o cálculo é feito na camada do banco de dados e apenas os resultados são retornados — a diferença de desempenho pode chegar a 100 vezes.

Padrão de projeto Agregador:

Requisitos estatísticos Fase de agregação Resultados
Distribuição de pontuação $match → $bucket [{_id: 5, count: 120}, ...]
Estatísticas do produto $match → $facet {total, avgRating, distribution, recent}
Produtos populares $match → $group → $sort → $lookup → $limit [{productId, avgRating, reviewCount}]

O valor do $facet: o $facet permite que vários pipelines de agregação sejam executados em paralelo na mesma entrada — uma única consulta retorna quatro dimensões: total, avgRating, ratingDistribution e recentReviews. Sem o $facet, seriam necessárias quatro consultas separadas.

100%
graph TB
    Input[Comment Data Stream] --> Facet["$facet<br/>Multi-channel parallel processing"]
    
    Facet --> P1["Pipeline1: $count<br/>Total"]
    Facet --> P2["Pipeline2: $group<br/>Average Rating"]
    Facet --> P3["Pipeline3: $group + $sort<br/>Score Distribution"]
    Facet --> P4["Pipeline4: $sort + $limit + $lookup<br/>Recent Comments(Contains user information)"]
    
    P1 --> Output[Combined Output<br/>{total, avg, distribution, recent}]
    P2 --> Output
    P3 --> Output
    P4 --> Output

    style Facet fill:#d4edda
    style Output fill:#cce5ff

Como funciona a distribuição de pontuação do $bucket: O $bucket divide as pontuações em faixas com base em limites — onde limites = [1,2,3,4,5,6] representam 5 faixas: [1,2), [2,3), [3,4), [4,5), [5,6). A contagem e a média de cada faixa são calculadas.

(1) Distribuição de pontuações ($bucket)

JAVASCRIPT
exports.getRatingDistribution = async (req, res) => {
  const { productId } = req.params;

  const distribution = await Review.aggregate([
    { $match: { productId: new mongoose.Types.ObjectId(productId), parentId: null } },
    {
      $bucket: {
        groupBy: '$rating',
        boundaries: [1, 2, 3, 4, 5, 6],
        default: 'Other',
        output: {
          count: { $sum: 1 },
          avgHelpful: { $avg: '$likeCount' }
        }
      }
    }
  ]);

  res.json({ data: distribution });
};

(2) Estatísticas do produto ($facet)

JAVASCRIPT
exports.getProductStats = async (req, res) => {
  const { productId } = req.params;

  const stats = await Review.aggregate([
    { $match: { productId: new mongoose.Types.ObjectId(productId), parentId: null } },
    {
      $facet: {
        total: [{ $count: 'count' }],
        avgRating: [{ $group: { _id: null, avg: { $avg: '$rating' } } }],
        ratingDistribution: [
          { $group: { _id: '$rating', count: { $sum: 1 } } },
          { $sort: { _id: 1 } }
        ],
        recentReviews: [
          { $sort: { createdAt: -1 } },
          { $limit: 5 },
          {
            $lookup: {
              from: 'users',
              localField: 'userId',
              foreignField: '_id',
              as: 'userInfo'
            }
          },
          { $unwind: '$userInfo' },
          {
            $project: {
              content: 1,
              rating: 1,
              createdAt: 1,
              username: '$userInfo.username',
              avatar: '$userInfo.avatar'
            }
          }
        ]
      }
    }
  ]);

  res.json(stats[0]);
};

(3) Principais produtos

JAVASCRIPT
exports.getTopProducts = async (req, res) => {
  const { limit = 10 } = req.query;

  const topProducts = await Review.aggregate([
    { $match: { parentId: null, isApproved: true } },
    {
      $group: {
        _id: '$productId',
        avgRating: { $avg: '$rating' },
        reviewCount: { $sum: 1 },
        totalLikes: { $sum: '$likeCount' }
      }
    },
    { $sort: { avgRating: -1, reviewCount: -1 } },
    { $limit: limit * 1 },
    {
      $lookup: {
        from: 'products',
        localField: '_id',
        foreignField: '_id',
        as: 'product'
      }
    },
    { $unwind: '$product' },
    {
      $project: {
        sku: '$product.sku',
        title: '$product.title',
        thumbnail: '$product.thumbnail',
        avgRating: 1,
        reviewCount: 1
      }
    }
  ]);

  res.json({ data: topProducts });
};

6. Módulo 5: Permissões e segurança

Uma explicação detalhada do princípio da defesa em profundidade: A segurança de APIs não é um único ponto de defesa, mas um sistema de defesa em várias camadas — a camada de rede (HTTPS + CORS) impede a interceptação e o abuso de origem cruzada; a camada de aplicação (autenticação JWT + autorização RBAC + validação de entrada) intercepta solicitações não autorizadas e maliciosas; e a camada de dados (consultas parametrizadas + usuários de banco de dados com privilégios mínimos) impede ataques de injeção e acesso não autorizado. A importância da defesa independente em cada camada: uma falha em qualquer camada isolada não compromete a proteção oferecida pelas outras camadas — mesmo que o CORS esteja mal configurado, a autenticação JWT ainda pode bloquear usuários não autenticados; mesmo que um JWT seja comprometido, o RBAC ainda pode restringir o escopo das operações para usuários com privilégios limitados.

Melhores práticas de segurança para tokens JWT: 1. Força da chave: Gere uma chave aleatória de 256 bits usando openssl rand -hex 32; 2. Prazo de validade: 7 dias (para equilibrar segurança e experiência do usuário; esse prazo pode ser reduzido para operações confidenciais); 3. Atualização do token: Prazo de validade curto para tokens de acesso + prazo de validade longo para tokens de atualização (mecanismo de token duplo); 4. Armazenamento de tokens: use cookies httpOnly (para prevenir XSS) no front-end em vez de localStorage; 5. Revogação de tokens: mantenha uma lista negra (Redis SET) ou use prazos de validade curtos para reduzir a necessidade de revogação.

100%
graph TB
    subgraph "Defense in Depth"
        L1[Network Layer<br/>TLS + CORS] --> L2[Application Layer<br/>Auth + Authorization + Input Validation]
        L2 --> L3[Data Layer<br/>Parameterized Queries + Least Privilege]
    end

    style L1 fill:#d4edda
    style L2 fill:#fff3cd
    style L3 fill:#f8d7da

A segurança de APIs segue o princípio da “defesa em profundidade” — não se trata de uma única linha de defesa, mas de várias camadas: camada de rede (TLS/CORS) → camada de aplicação (autenticação + autorização + validação de entrada) → camada de dados (consultas parametrizadas + privilégio mínimo). Cada camada oferece proteção de forma independente; se uma camada for violada, as outras não são afetadas.

Autenticação JWT x Autenticação por sessão:

Dimensão JWT Sessão
Local de armazenamento Cliente (Token) Servidor (Memória/Redis)
Escalabilidade Sem estado, suporta naturalmente a distribuição Requer armazenamento compartilhado de sessões
Segurança O vazamento do token não pode ser revogado instantaneamente É possível encerrar a sessão instantaneamente
Desempenho Sem sobrecarga de consultas no lado do servidor Consulta o armazenamento de sessão a cada vez
Gerenciamento de validade Reivindicação de validade, não é possível renovar proativamente Pode ser renovado com validade variável
Cenários adequados API / Microsserviços Aplicativos web tradicionais

Modelo de permissão RBAC: O Controle de Acesso Baseado em Funções (RBAC) atribui permissões com base em funções — os usuários têm funções, e as funções têm permissões. Esse sistema possui três funções:

Função Permissões Âmbito
cliente Ver, curtir e editar suas próprias avaliações /avaliações (próprias)
moderador Revisar/excluir quaisquer avaliações /avaliações (todas) + painel de moderação
admin Gerenciar produtos, usuários e todas as avaliações /produtos + /usuários + /avaliações (todas)

Noções básicas sobre proteção contra injeções: Os riscos de injeção no MongoDB provêm principalmente do $where (que executa JavaScript arbitrário) e do $regex sem âncora (ataques DoS). Princípios de proteção: (1) Sempre use consultas parametrizadas em vez de concatenação de strings; (2) O Mongoose faz o escape automático dos valores das consultas; (3) O $regex requer o escape de caracteres especiais.

100%
sequenceDiagram
    Client Participant
    participant Auth as authenticate Middleware
    participant Authorize as authorize Middleware
    participant Controller
    DB participant

    Client->>Auth: Request + Bearer Token
    Auth->>Auth: jwt.verify(token, secret)
    alt Token is invalid
        Auth-->>Client: 401 Unauthorized
    else Token Valid
        Auth->>Authorize: req.user = {id, role}
        Authorize->>Authorize: roles.includes(req.user.role)?
        alt: No permission
            Authorize-->>Client: 403 Forbidden
        else has permission
            Authorize->>Controller: Execute business logic
            Controller >> DB: Parameterized Queries
            DB-->>Client: 200 OK
        than
    than

(1) Middleware JWT

JAVASCRIPT
// middlewares/auth.js
const jwt = require('jsonwebtoken');

exports.authenticate = (req, res, next) => {
  const token = req.header('Authorization')?.replace('Bearer ', '');
  if (!token) return res.status(401).json({ error: 'No token' });

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    res.status(401).json({ error: 'Invalid token' });
  }
};

exports.authorize = (...roles) => (req, res, next) => {
  if (!req.user || !roles.includes(req.user.role)) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  next();
};

(2) API de autenticação

JAVASCRIPT
// controllers/authController.js
const User = require('../models/User');
const jwt = require('jsonwebtoken');

exports.register = async (req, res) => {
  const { email, username, password } = req.body;

  const existing = await User.findOne({ $or: [{ email }, { username }] });
  if (existing) return res.status(409).json({ error: 'Email or username already exists' });

  const user = await User.create({
    email,
    username,
    passwordHash: password  // pre-save The middleware performs hashing
  });

  const token = jwt.sign(
    { id: user._id, role: user.role },
    process.env.JWT_SECRET,
    { expiresIn: '7d' }
  );

  res.status(201).json({ user: { id: user._id, email: user.email, username: user.username }, token });
};

exports.login = async (req, res) => {
  const { email, password } = req.body;

  const user = await User.findOne({ email }).select('+passwordHash');
  if (!user) return res.status(401).json({ error: 'Invalid credentials' });

  const valid = await user.comparePassword(password);
  if (!valid) return res.status(401).json({ error: 'Invalid credentials' });

  const token = jwt.sign(
    { id: user._id, role: user.role },
    process.env.JWT_SECRET,
    { expiresIn: '7d' }
  );

  res.json({ user: { id: user._id, email: user.email, username: user.username, role: user.role }, token });
};

(3) Proteção contra injeção

JAVASCRIPT
// ❌ Danger: $where executes arbitrary JavaScript
db.reviews.find({ $where: 'this.userId == "' + userId + '"' });

// ✅ Security: Use parameterized queries
db.reviews.find({ userId: new ObjectId(userId) });

// ✅ Mongoose auto-escapes
const reviews = await Review.find({ userId: userId });

// ❌ Danger: $regex DoS
db.reviews.find({ content: { $regex: req.query.q } });

// ✅ Security: Escape special characters in regular expressions
function escapeRegex(str) {
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
db.reviews.find({ content: { $regex: escapeRegex(req.query.q), $options: 'i' } });

7. Módulo 6: Implantação

Decisão sobre a arquitetura de implantação: A escolha da estratégia de implantação se baseia na escala do projeto e na capacidade da equipe: 1. Atlas + Render (opção escolhida para este projeto): inicialização sem esforço, adequada para projetos de pequeno a médio porte e fases de MVP, custo mensal < US$ 50; 2. MongoDB auto-hospedado + Docker: totalmente controlável, mas requer capacidade de DevOps, adequado para projetos de médio a grande porte com uma equipe de DevOps; 3. Kubernetes + Atlas: escalonamento elástico + banco de dados gerenciado, adequado para sistemas de produção com tráfego flutuante. Este projeto escolheu o Atlas + Render pelo motivo principal: concentrar a energia limitada no desenvolvimento de negócios, em vez de nas operações.

Verificação de integridade e recuperação automática: As verificações de integridade na implantação de produção não se limitam a verificar se /healthz retorna 200 — elas precisam verificar três camadas de integridade: 1. Camada de aplicação (o Express está respondendo?); 2. Camada de banco de dados (o MongoDB está acessível?); 3. Camada de dependências (o Redis/ES estão funcionando normalmente?). O endpoint de verificação de integridade é chamado periodicamente pelo balanceador de carga; após N falhas consecutivas, a instância é automaticamente removida e reiniciada. Quando a conexão com o banco de dados é perdida, ela deve retornar 503 (Serviço indisponível) em vez de 200, para evitar o envio de tráfego para instâncias com problemas.

100%
graph TB
    subgraph "Deployment Architecture"
        A[Client] --> B[Render<br/>Node.js App]
        B --> C[Atlas<br/>MongoDB Cluster]
        B --> D[CDN<br/>Static Assets]
    end

    style A fill:#d4edda
    style B fill:#fff3cd
    style C fill:#d4edda
    style D fill:#fff3cd

A implantação não se resume a “git push e pronto” — é preciso levar em consideração: isolamento de ambientes (dev/staging/prod), gerenciamento de segredos (não armazenar senhas no código), verificações de integridade (reinício automático de instâncias com falhas) e segurança de dados (criptografia TLS + backup e recuperação). Estratégia de implantação deste projeto: banco de dados gerenciado pelo Atlas + aplicativo gerenciado pelo Render/Railway — startup com zero operações, adequada para projetos de pequeno a médio porte.

Arquitetura de implantação:

100%
graph LR
    Client[User's browser] -->|HTTPS| CDN[CDN / Static Resources]
    Client -->|HTTPS| LB[Load Balancer]
    LB -->|HTTP| App1[Render Instance 1<br/>Express App]
    LB -->|HTTP| App2[Render Instance 2<br/>Express App]
    App1 -->|TLS + SRV| Atlas[MongoDB Atlas<br/>3 Node Replica Set]
    App2 -->|TLS + SRV| Atlas
    Atlas -->| PITR | Backup[Automatic Backup<br/>7Day Reserved]

    style Atlas fill:#d4edda
    style App1 fill:#cce5ff

Política de configuração do ambiente:

Variável de ambiente Valor de desenvolvimento Valor de produção Método de gerenciamento
NODE_ENV desenvolvimento produção .env / Configurações da plataforma
MONGODB_URI mongodb://localhost:27017 mongodb+srv://... Gerenciamento de chaves da plataforma
JWT_SECRET test-secret sequência aleatória de 256 bits openssl rand -hex 32
CORS_ORIGIN * https://shophub.example.com .env / Configurações da plataforma
PORTA 3000 Atribuição de plataforma O padrão está correto

Projeto da verificação de integridade: O endpoint /healthz não apenas verifica se o Express está em execução, mas também se o MongoDB está acessível — caso a conexão com o banco de dados seja perdida, o aplicativo deve retornar um código de status 503 (Serviço indisponível) em vez de um 200, para que o balanceador de carga possa remover automaticamente as instâncias com falhas.

(1) Configuração do MongoDB Atlas

BASH
# 1. Create Atlas Cluster(Recommended Courses #01)
# 2. Layout IP Whitelist:0.0.0.0/0(Development)or application server IP
# 3. Create a Database User:app_user / <password>
# 4. Get the connection string:
mongodb+srv://app_user:<password>@cluster0.mongodb.net/shophub?retryWrites=true&w=majority

(2) Variáveis de ambiente (produção)

BASH
# .env.production
NODE_ENV=production
PORT=3000
MONGODB_URI=mongodb+srv://app_user:StrongPass@cluster0.mongodb.net/shophub?retryWrites=true&w=majority
JWT_SECRET=<generated-strong-secret-256-bit>
JWT_EXPIRES_IN=7d
CORS_ORIGIN=https://shophub.example.com

(3) Exame médico

JAVASCRIPT
app.get('/healthz', async (req, res) => {
  try {
    const db = mongoose.connection.db;
    await db.admin().command({ ping: 1 });
    res.json({
      status: 'ok',
      uptime: process.uptime(),
      mongo: 'connected',
      timestamp: new Date().toISOString()
    });
  } catch (err) {
    res.status(503).json({ status: 'error', error: err.message });
  }
});

(4) Renderização / Implantação ferroviária

BASH
# === Render Deployment ===
# 1. Connect GitHub Warehouse
# 2. Set Environment Variables (MONGODB_URI, JWT_SECRET, etc.)
# 3. Set Up Build Commands:npm install
# 4. Set the startup command:npm start
# 5. Automatic HTTPS + Deployment

# === Railway Deployment ===
railway login
railway init
railway add mongodb  # Add with One Click MongoDB
railway up

(5) Lista de verificação para otimização de desempenho

JAVASCRIPT
// === src/app.js Optimized Version ===
const mongoose = require('mongoose');

mongoose.connect(process.env.MONGODB_URI, {
  maxPoolSize: 50,
  minPoolSize: 5,
  serverSelectionTimeoutMS: 5000
});

app.use(express.json({ limit: '1mb' }));
app.use(cors({
  origin: process.env.CORS_ORIGIN || '*',
  credentials: true
}));

8. Demonstração completa do projeto

Processo de inicialização e teste do projeto: Uma demonstração completa do projeto segue o processo “Configuração do ambiente → Inicialização dos serviços → Teste das APIs → Verificação da funcionalidade → Implantação e lançamento”. Primeiro, inicie o conjunto de réplicas do MongoDB (transações e Change Streams exigem um conjunto de réplicas); em seguida, inicie o aplicativo Express; e, por fim, use curl para testar os endpoints principais da API.

Lista de verificação para testes de API:

# Recurso Endpoint Código de status esperado
1 Registrar POST /api/auth/register 201
2 Login POST /api/auth/login 200
3 Criar produto POST /api/products 201 (admin)
4 Lista de produtos GET /api/products 200
5 Publicar uma avaliação POST /api/products/:id/reviews 201
6 Lista de avaliações GET /api/products/:id/reviews 200
7 Curtir POST /api/reviews/:id/like 200
8 Estatísticas do produto GET /api/products/:id/stats 200
9 Verificação de integridade GET /healthz 200
BASH
# === Launch the Project ===
npm install
npm start

# === API Usage Example ===

# 1. Registered Users
curl -X POST http://localhost:3000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"alice@example.com","username":"alice","password":"Pass123!"}'

# 2. Log In
curl -X POST http://localhost:3000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"alice@example.com","password":"Pass123!"}'

# 3. Create a Review
curl -X POST http://localhost:3000/api/products/<productId>/reviews \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"content":"Great product!","rating":5}'

# 4. View Product Statistics
curl http://localhost:3000/api/products/<productId>/stats

# 5. Health Checkup
curl http://localhost:3000/healthz

▶ Exemplo 1: Demonstração de comentários aninhados + recurso de curtir

JAVASCRIPT
// === Scenario: Alice reviews a product, Bob replies to Alice, Charlie likes it ===

// 1. Alice Published 5 Star Reviews
const aliceReview = await Review.create({
  productId: '647f1f77bcf86cd799439011',
  userId: aliceId,
  content: 'Excellent smartphone! The camera quality is outstanding.',
  rating: 5,
  parentId: null  // Top Comments
});
// Back: { _id: 'review001', content: '...', rating: 5, likeCount: 0 }

// 2. Bob Reply Alice
const bobReply = await Review.create({
  productId: '647f1f77bcf86cd799439011',
  userId: bobId,
  content: 'I agree, the camera is amazing!',
  rating: 5,
  parentId: aliceReview._id  // Quote parent's comment
});
// Back: { _id: 'review002', parentId: 'review001', content: '...' }

// 3. Charlie likes Alice's comment
await Review.updateOne(
  { _id: aliceReview._id },
  { $addToSet: { likes: charlieId }, $inc: { likeCount: 1 } }
);
// Alice Comments on: { likeCount: 1, likes: [charlieId] }

// 4. Like again → Cancel
await Review.updateOne(
  { _id: aliceReview._id },
  { $pull: { likes: charlieId }, $inc: { likeCount: -1 } }
);
// Alice Comments on: { likeCount: 0, likes: [] }

// 5. Querying a Nested Comment Tree
const reviews = await Review.find({ productId: '647f...', parentId: null })
  .populate('userId', 'username avatar')
  .lean();
const replies = await Review.find({ parentId: { $in: reviews.map(r => r._id) } })
  .populate('userId', 'username avatar')
  .lean();
const tree = reviews.map(r => ({
  ...r,
  replies: replies.filter(rep => rep.parentId.toString() === r._id.toString())
}));

console.log(JSON.stringify(tree, null, 2));
// [{
//   content: 'Excellent smartphone!...',
//   userId: { username: 'alice', avatar: '...' },
//   replies: [{ content: 'I agree...', userId: { username: 'bob' } }]
// }]

Resultado: Uma estrutura em árvore de comentários aninhados: Alice comenta → Bob responde, Charlie curte/deixa de curtir.

▶ Exemplo: Avaliação Statistics com Aggregation Pipeline (Difficulty ⭐⭐)

JAVASCRIPT
// Scene: ShopHub product detail page - aggregate review stats and distribution
const mongoose = require('mongoose');

// Review Schema
const ReviewSchema = new mongoose.Schema({
  productId: { type: mongoose.Schema.Types.ObjectId, ref: 'Product', required: true },
  userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  rating: { type: Number, required: true, min: 1, max: 5 },
  title: { type: String, required: true },
  content: { type: String, required: true, maxlength: 1000 },
  helpful: { type: Number, default: 0 },
  verified: { type: Boolean, default: false },
  createdAt: { type: Date, default: Date.now }
});

const Review = mongoose.model('Review', ReviewSchema);

// Aggregation: product review summary + rating distribution + recent reviews
async function getProductReviewStats(productId) {
  const stats = await Review.aggregate([
    // Match reviews for this product
    { $match: { productId: mongoose.Types.ObjectId(productId) } },
    
    // Facet: run multiple aggregations in parallel
    { $facet: {
      // Summary stats
      summary: [
        { $group: {
          _id: null,
          totalReviews: { $sum: 1 },
          avgRating: { $avg: '$rating' },
          verifiedCount: { $sum: { $cond: ['$verified', 1, 0] } },
          totalHelpful: { $sum: '$helpful' }
        }}
      ],
      // Rating distribution (1-5 stars)
      ratingDistribution: [
        { $group: {
          _id: '$rating',
          count: { $sum: 1 }
        }},
        { $sort: { _id: 1 } }
      ],
      // Recent reviews with user info
      recentReviews: [
        { $sort: { createdAt: -1 } },
        { $limit: 5 },
        { $lookup: {
          from: 'users',
          localField: 'userId',
          foreignField: '_id',
          as: 'user'
        }},
        { $unwind: '$user' },
        { $project: {
          rating: 1,
          title: 1,
          content: 1,
          helpful: 1,
          verified: 1,
          username: '$user.username',
          createdAt: 1
        }}
      ]
    }}
  ]);
  
  return stats[0];
}

// Usage
const productId = '507f1f77bcf86cd799439011';
const stats = await getProductReviewStats(productId);
console.log(JSON.stringify(stats, null, 2));

Saída:

TEXT 📖 Somente leitura
{
  "summary": [{
    "totalReviews": 128,
    "avgRating": 4.3,
    "verifiedCount": 45,
    "totalHelpful": 312
  }],
  "ratingDistribution": [
    { "_id": 1, "count": 5 },
    { "_id": 2, "count": 8 },
    { "_id": 3, "count": 22 },
    { "_id": 4, "count": 45 },
    { "_id": 5, "count": 48 }
  ],
  "recentReviews": [
    { "rating": 5, "title": "Excellent product!", "username": "alice", "verified": true }
  ]
}

▶ Exemplo 2: Demonstração ao vivo do sistema completo de avaliações do ShopHub

BASH
# === 1. Start MongoDB Dungeon Collection ===
docker run -d --name mongo -p 27017:27017 mongo:7.0 --replSet rs0
docker exec mongo mongosh --eval 'rs.initiate()'

# === 2. Start Node.js Applications ===
npm install
npm start

# Output:
# ✅ MongoDB connected: localhost
# 🚀 Server running on port 3000

# === 3. Test Core API ===

# 3.1 User Registration
curl -X POST http://localhost:3000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"alice@example.com","username":"alice","password":"Pass123!"}'

# Back:
# {
#   "success": true,
#   "user": { "id": "...", "email": "alice@example.com", "username": "alice" },
#   "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
# }

# 3.2 User Login
curl -X POST http://localhost:3000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"alice@example.com","password":"Pass123!"}'

# 3.3 Create a Product
curl -X POST http://localhost:3000/api/products \
  -H "Authorization: Bearer <admin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "sku": "PHONE-001",
    "title": "Smartphone X",
    "price": 599,
    "category": "Electronics",
    "stock": 50
  }'

# 3.4 Product List(With pagination + Projection)
curl 'http://localhost:3000/api/products?page=1&limit=20&category=Electronics'

# Back:
# {
#   "success": true,
#   "data": [{ "sku": "PHONE-001", "title": "Smartphone X", "price": 599, "thumbnail": "...", "rating": 0 }],
#   "meta": { "page": 1, "limit": 20, "total": 1, "pages": 1 }
# }

# 3.5 Add a comment
curl -X POST http://localhost:3000/api/products/507f1f77bcf86cd799439021/reviews \
  -H "Authorization: Bearer <user_token>" \
  -H "Content-Type: application/json" \
  -d '{"content":"Great phone!","rating":5}'

# 3.6 Like and Comment
curl -X POST http://localhost:3000/api/reviews/507f1f77bcf86cd799439031/like \
  -H "Authorization: Bearer <user_token>"

# 3.7 View Product Statistics
curl http://localhost:3000/api/products/507f1f77bcf86cd799439021/stats

# Back:
# {
#   "total": [{ "count": 1 }],
#   "avgRating": [{ "avg": 5 }],
#   "ratingDistribution": [{ "_id": 5, "count": 1 }],
#   "recentReviews": [{ "content": "Great phone!", "rating": 5, "username": "alice" }]
# }

# 3.8 Health Checkup
curl http://localhost:3000/healthz

# Back:
# {
#   "status": "ok",
#   "uptime": 1234,
#   "mongo": "connected",
#   "timestamp": "2026-07-06T10:00:00.000Z"
# }

# === 4. Deploy to Render/Railway ===
git push heroku main
# Automatic Deployment,Environment Variables MONGODB_URI Orientation Atlas

# === 5. Performance Monitoring(Production)===
# Datadog APM Automatic Tracking:
# - mongoose Query Duration
# - API Response Time
# - Number of database connections
# - Slow Query Alerts

Resultado: Um sistema completo de avaliações para comércio eletrônico que demonstra todo o fluxo de trabalho — desde o cadastro, login, gerenciamento de produtos, operações CRUD de avaliações, curtidas e estatísticas até verificações de integridade — e que pode ser implantado diretamente em um ambiente de produção.

❓ Perguntas Frequentes

P: Como podemos otimizar ainda mais o projeto após sua conclusão? R: Armazenando em cache os produtos mais populares no Redis, realizando buscas de texto completo com o Elasticsearch, utilizando uma CDN para recursos estáticos e fazendo a implantação em contêineres com o K8s.

P: Quais são as medidas antispam do sistema de comentários? R: Limites de frequência de comentários, filtragem de palavras sensíveis, um sistema de denúncia por parte dos usuários e um sistema de moderação manual.

P: Como faço para configurar notificações de comentários? R: Use o recurso “Alterar fluxos” para monitorar alterações nos comentários e acionar notificações por e-mail ou push.


📖 Resumo


📝 Exercícios

  1. Questão básica (⭐): Configure a estrutura básica do projeto (incluindo package.json, .env e app.js).
  2. Problemas básicos (⭐): Implemente os três modelos: Usuário, Produto e Avaliação.
  3. Exercício avançado (⭐⭐): Implemente operações CRUD para comentários (criar, listar, curtir, exclusão temporária).
  4. Exercício avançado (⭐⭐): Implementar estatísticas agregadas (distribuição de avaliações + estatísticas de produtos + paradas de sucesso).
  5. Desafio (⭐⭐⭐): Implantar um sistema completo de avaliações de comércio eletrônico no Atlas ou no Render, incluindo todos os recursos dos seis módulos.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%