Review Module API
Complete API contract for purchased product reviews and admin-managed testimonial reviews.
Overview
The review module now contains two parallel surfaces:
- Product reviews
- submitted by authenticated customers against delivered order items
- moderated by admins
- displayed on product pages
- Testimonials
- created and managed by admins
- not linked to products or orders
- displayed as storefront trust content
This split is intentional. Existing product review semantics stay unchanged, while testimonials provide a second review-like stream for homepage and landing page sections.
Route Inventory
Admin
| Method | Path | Permission | Purpose |
|---|---|---|---|
GET | /api/admin/reviews | Reviews_READ | List order-linked product reviews |
GET | /api/admin/reviews/:id | Reviews_READ | Get order-linked product review detail |
PATCH | /api/admin/reviews/:id/status | Reviews_UPDATE | Approve or reject product review |
GET | /api/admin/reviews/testimonials | Reviews_READ | List testimonial reviews |
GET | /api/admin/reviews/testimonials/:id | Reviews_READ | Get testimonial review detail |
POST | /api/admin/reviews/testimonials | Reviews_CREATE | Create testimonial review |
PATCH | /api/admin/reviews/testimonials/:id | Reviews_UPDATE | Update testimonial review |
DELETE | /api/admin/reviews/testimonials/:id | Reviews_DELETE | Delete testimonial review |
Public / Customer
| Method | Path | Auth | Purpose |
|---|---|---|---|
GET | /api/products/:slug/reviews | Public | List approved product reviews for published product |
GET | /api/products/id/:productId/reviews | Public | List approved product reviews by product id |
GET | /api/reviews/testimonials | Public | List visible testimonial reviews |
Mobile
| Method | Path | Auth | Purpose |
|---|---|---|---|
POST | /api/mobile/reviews | Customer JWT | Submit purchased product review |
GET | /api/mobile/reviews | Customer JWT | List current customer's submitted product reviews |
GET | /api/mobile/products/id/:productId/reviews | Public | List approved product reviews by product id |
GET | /api/mobile/reviews/testimonials | Public | List visible testimonial reviews |
Testimonial Contracts
Admin create / update fields
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | Yes | 1-120 chars after trim |
rating | number | Yes | integer 1..5 |
comment | string | Yes | non-blank testimonial body |
imageUrl | string | No | valid URL when present |
isVisible | boolean | No | defaults to true |
order | number | No | defaults to 0 |
Admin list query
| Query | Type | Default | Notes |
|---|---|---|---|
pagination | boolean | true | standard query DTO behavior |
page | number | 1 | minimum 1 |
size | number | 20 | maximum 100 |
search | string | - | matches name and comment |
isVisible | boolean | - | optional filter |
sort | order | createdAt | updatedAt | rating | order | |
order | asc | desc | asc |
Public testimonial list query
| Query | Type | Default | Notes |
|---|---|---|---|
pagination | boolean | true | standard query DTO behavior |
page | number | 1 | |
size | number | 20 | |
rating | number | - | exact match filter |
sort | order | createdAt | rating | order | |
order | asc | desc | asc |
Response Shapes
Testimonial admin item
{
"id": 1,
"name": "Navneet Verma",
"rating": 5,
"comment": "I bought a Lenovo laptop from IT Mart and the whole process felt fast, clear, and trustworthy.",
"imageUrl": "https://cdn.itmart.example/testimonials/navneet-verma.jpg",
"isVisible": true,
"order": 0,
"createdAt": "2026-06-02T10:00:00.000Z",
"updatedAt": "2026-06-02T10:00:00.000Z"
}Testimonial public item
{
"id": 1,
"name": "Navneet Verma",
"rating": 5,
"comment": "I bought a Lenovo laptop from IT Mart and the whole process felt fast, clear, and trustworthy.",
"imageUrl": "https://cdn.itmart.example/testimonials/navneet-verma.jpg",
"order": 0,
"createdAt": "2026-06-02T10:00:00.000Z"
}Errors
| HTTP | errorCode | Meaning |
|---|---|---|
400 | TESTIMONIAL_CREATE_FAILED | testimonial insert unexpectedly failed |
400 | TESTIMONIAL_INVALID_SORT | unsupported public testimonial sort value reached service |
404 | TESTIMONIAL_NOT_FOUND | testimonial id does not exist |
404 | REVIEW_NOT_FOUND | existing product review admin id does not exist |
Discovery Integration
Product review discovery is exposed through product-specific endpoints that accept a product slug or product ID:
GET /api/products/:slug/reviews— resolves the product slug (including historical slug fallback) and returns approved reviewsGET /api/products/id/:productId/reviews— direct product ID lookup for approved reviews
These endpoints are consumed by product detail pages on the storefront. They complement the customer product discovery flow:
- Customer discovers product via brand/category/series/search
- Customer views product detail (
GET /api/products/:slug) - Customer reads reviews (
GET /api/products/:slug/reviews) to inform purchase decision
Query Parameters
| Query | Type | Default | Notes |
|---|---|---|---|
pagination | boolean | true | |
page | number | 1 | |
size | number | 20 |
Only approved reviews are returned. Sorting is by createdAt DESC (most recent first).
Notes
- Product review endpoints and testimonial endpoints intentionally do not share storage.
- Testimonials do not have moderation status in v1; admin visibility controls storefront exposure.
- Product review approval flow remains unchanged and is documented separately below.
Review Summary Integration
Average rating data computed from approved order_reviews is surfaced in two places: the product review endpoints themselves, and the product list/detail endpoints.
There is no dedicated average-rating endpoint. The summary is derived from the order_reviews table at query time using SQL aggregation. See Review Module Backend section "Product Average Rating Integration" for the computation pattern.
Summary in review endpoints
The product review listing endpoints return a summary block alongside the paginated review items:
| Endpoint | Summary included |
|---|---|
GET /api/products/:slug/reviews | Yes — ProductReviewCollectionResponseDto |
GET /api/products/id/:productId/reviews | Yes — returns totalReviews in response envelope |
Response shape for GET /api/products/:slug/reviews:
{
"productId": 45,
"productSlug": "lenovo-thinkpad-e14-gen-5",
"summary": {
"averageRating": 4.67,
"totalReviews": 12
},
"items": [
{
"id": 1,
"rating": 5,
"comment": "The laptop arrived quickly and feels great.",
"customerDisplayName": "Sita S.",
"createdAt": "2026-04-23T09:00:00.000Z"
}
]
}| Field | Type | Description |
|---|---|---|
summary.averageRating | number | Mean rating of approved reviews, rounded to 2 decimal places. |
summary.totalReviews | number | Count of approved reviews. |
items[] | ProductReviewItemDto[] | Paginated list of approved reviews. |
Summary in product list endpoints
Every product list item now includes average rating data. These fields exist on both customer-facing and admin-facing list DTOs.
Customer product list (GET /api/products?kind=laptop, GET /api/products/featured, etc.):
| Field | Type | Description |
|---|---|---|
averageRating | number | null | Mean rating of approved reviews. null when no approved reviews exist. |
reviewCount | number | Count of approved reviews. 0 when none exist. |
Admin product list (GET /api/admin/products?search=...):
| Field | Type | Description |
|---|---|---|
averageRating | number | null | Same as customer list. |
reviewCount | number | Same as customer list. |
Example list item with rating data:
{
"id": 101,
"title": "Dell XPS 15 Laptop",
"slug": "dell-xps-15-laptop",
"mrp": 150000,
"sp": 135000,
"discount": 15000,
"averageRating": 4.5,
"reviewCount": 8
}Summary in product detail response
The product detail endpoint (GET /api/products/:slug) includes richer review metadata:
| Field | Type | Description |
|---|---|---|
averageRating | number | Mean rating of approved reviews. 0 when no reviews exist. |
totalReviews | number | Count of approved reviews. |
reviewStatus | string | Human-readable label: "Reviews available" or "No reviews yet". |
Example:
{
"id": 101,
"title": "Dell XPS 15 Laptop",
"slug": "dell-xps-15-laptop",
"averageRating": 4.5,
"totalReviews": 8,
"reviewStatus": "Reviews available"
}Cache implications
Since the average rating is computed from the order_reviews table at query time (not from a denormalized summary table):
- Product list and detail responses that hit the cache will serve stale review data until the cache TTL expires.
- Cache invalidation for review mutations (create, update, delete, status change) is not currently implemented. The default TTL values apply:
- Customer product list/detail: Redis-backed with configurable TTL (see product module caching documentation).
- Product detail: cached per slug with standard product TTL.
- If stricter consistency is needed, the product list/detail cache keys can be invalidated when review status changes, or a summary table can be introduced.
DTO reference
| DTO | Fields |
|---|---|
ProductReviewSummaryDto | averageRating, totalReviews |
ProductReviewCollectionResponseDto | productId, productSlug, summary: ProductReviewSummaryDto, items: ProductReviewItemDto[] |
ProductListItemDto | averageRating: number | null, reviewCount: number |
ProductResponseDto | averageRating: number, totalReviews: number, reviewStatus: string |
See also
- Backend Reference: See Review Module Backend section "Product Average Rating Integration" for the computation strategy and data model.
- Product API: See Product Module API for the full product list and detail endpoint contracts.
- Product Backend: See Product Customer Service for the service-layer query implementation.
Admin Review Create Endpoint
POST /api/admin/reviews
Creates an admin-seeded product review with an explicit fake reviewer persona. Admin-only — requires Reviews_UPDATE permission.
Request Body:
| Field | Type | Required | Validation | Notes |
|---|---|---|---|---|
| productId | number | Yes | @IsInt, @Min(1) | Product being reviewed |
| reviewerName | string | Yes | trimmed, max 120 chars | Public name shown for the seeded review |
| reviewerImageUrl | string | No | valid URL, max 500 chars | Public profile image shown for the seeded review |
| rating | number | Yes | integer 1..5 | Review rating |
| comment | string | No | max 1000 chars | Review body |
| orderId | number | No | @IsInt, @Min(1) | Optional reference only; can be omitted for seeded reviews |
Behavior:
customerIdis not accepted on this endpoint- rows are stored as
reviewSource = "admin_seeded" - admin-created reviews are inserted directly as
approved - approved admin-seeded reviews are included in public product review endpoints and rating aggregates
Success Response (201):
{
"id": 42,
"orderId": null,
"productId": 101,
"customerId": null,
"reviewSource": "admin_seeded",
"reviewerName": "Aarav Gautam",
"reviewerImageUrl": "https://cdn.itmart.example/reviews/aarav-gautam.jpg",
"rating": 5,
"comment": "Excellent product, highly recommend!",
"status": "approved",
"createdAt": "2026-06-01T12:00:00.000Z"
}Notes on public output:
- fake/admin-seeded reviews show the exact
reviewerName - customer-submitted reviews still show masked public names
- image URLs are exposed where the public DTO already supports reviewer images