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.
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:
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
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
{
"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
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.
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)
// 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
// 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)
// 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.
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:
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
// 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
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”
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.
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.
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)
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)
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
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.
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.
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
// 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
// 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
// ❌ 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.
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:
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
# 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)
# .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
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
# === 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
// === 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 |
# === 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
// === 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 ⭐⭐)
// 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
# === 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
- Arquitetura completa do sistema de avaliações para comércio eletrônico (Node.js + Express + Mongoose)
- 6 módulos: Inicialização → Modelo → CRUD → Análise de agregados → Permissões → Implantação
- Comentários aninhados (referência a parent_id)
- Estatísticas agregadas ($bucket + $facet)
- Autenticação JWT + Funções RBAC
- Implantação do MongoDB Atlas na nuvem
📝 Exercícios
- Questão básica (⭐): Configure a estrutura básica do projeto (incluindo package.json, .env e app.js).
- Problemas básicos (⭐): Implemente os três modelos: Usuário, Produto e Avaliação.
- Exercício avançado (⭐⭐): Implemente operações CRUD para comentários (criar, listar, curtir, exclusão temporária).
- Exercício avançado (⭐⭐): Implementar estatísticas agregadas (distribuição de avaliações + estatísticas de produtos + paradas de sucesso).
- 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.