Careeroza
CRUD and Aggregation
ReviewsService
src/reviews/reviews.service.ts
import { ConflictException, Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Review } from './schemas/review.schema';
import { CreateReviewDto } from './dto/create-review.dto';
@Injectable()
export class ReviewsService {
constructor(@InjectModel(Review.name) private readonly reviewModel: Model<Review>) {}
async create(userId: number, dto: CreateReviewDto) {
try {
return await this.reviewModel.create({ ...dto, userId });
} catch (e) {
if ((e as { code?: number }).code === 11000) throw new ConflictException('You already reviewed this product');
throw e;
}
}
findForProduct(productId: number, page = 1, limit = 10) {
return this.reviewModel
.find({ productId })
.sort({ createdAt: -1 })
.skip((page - 1) * limit)
.limit(limit)
.lean() // plain objects, faster
.exec();
}
async ratingSummary(productId: number) {
const [summary] = await this.reviewModel.aggregate<{ avg: number; count: number }>([
{ $match: { productId } },
{ $group: { _id: '$productId', avg: { $avg: '$rating' }, count: { $sum: 1 } } },
{ $project: { _id: 0, avg: { $round: ['$avg', 1] }, count: 1 } },
]);
return summary ?? { avg: 0, count: 0 };
}
}Requests
Example
curl -X POST http://localhost:3000/reviews -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
-d '{"productId":7,"rating":4,"comment":"Good bass, battery lasts 2 days"}'
curl http://localhost:3000/products/7/ratingExample output
{"productId":7,"userId":12,"rating":4,"comment":"Good bass, battery lasts 2 days","images":[],"verifiedPurchase":false,"_id":"6787a1c0e4b0a5d1f2c3b4a9","createdAt":"2026-01-15T10:00:00.000Z","updatedAt":"2026-01-15T10:00:00.000Z","__v":0}
{"avg":4.3,"count":1287}What the Aggregation Computes
Example
The $match / $group / $project pipeline above, done on an array so you can see each step:
type Review = { productId: number; rating: number };
const reviews: Review[] = [
{ productId: 7, rating: 5 }, { productId: 7, rating: 4 }, { productId: 8, rating: 2 },
{ productId: 7, rating: 4 }, { productId: 7, rating: 3 }, { productId: 8, rating: 5 },
];
function ratingSummary(productId: number) {
const matched = reviews.filter((r) => r.productId === productId); // $match
const sum = matched.reduce((s, r) => s + r.rating, 0); // $group
const count = matched.length;
return { avg: count ? Math.round((sum / count) * 10) / 10 : 0, count }; // $project + $round
}
console.log('product 7:', ratingSummary(7));
console.log('product 8:', ratingSummary(8));
console.log('product 9:', ratingSummary(9));Output
product 7: { avg: 4, count: 4 }
product 8: { avg: 3.5, count: 2 }
product 9: { avg: 0, count: 0 }