Catalog Product Feature List
Product module feature contract across admin, customer, and mobile surfaces.
Catalog Product - Feature List
1. Feature Overview
The product submodule owns sellable catalog items used by cart and order flows for physical products. It supports admin CRUD, public/customer discovery, featured storefront listing, and historical slug resolution.
2. Route Ownership and Surfaces
- Admin controller:
apps/api/src/modules/catalog/admin/product/product-admin.controller.ts- Base path:
/api/admin/products
- Base path:
- Customer controller:
apps/api/src/modules/catalog/customer/product/product-customer.controller.ts- Base path:
/api/products
- Base path:
- Mobile composition:
apps/api/src/modules/mobile/mobile.module.ts- Same customer controller additionally exposed as
/api/mobile/products
- Same customer controller additionally exposed as
3. Admin Feature Matrix
| Feature | Behavior |
|---|---|
| List products | Filter/search/sort/paginate with permission Products_READ |
| Get product by id | Numeric :id (ParseIntPipe) |
| Create product | Validates pricing/category/tags and writes product + product_tag |
| Update product | Supports partial updates, status transition validation, slug-history capture |
| Delete product | Hard delete by id with cache invalidation |
| Audit activity | Create/update/delete activity records when actor context is present |
4. Customer/Mobile Feature Matrix
| Feature | Behavior |
|---|---|
| Product list | Published + sellable products only |
| Featured products list | Returns products linked through featured rows that are active and in date window |
| Product by slug | Resolves current slug first, then product_slug_history fallback |
| Product availability by slug | Returns live stock availability for cart actions (in_stock, low_stock, out_of_stock) |
| Search | Title/description/category/tag ILIKE + title similarity threshold |
| Pagination | Supported for list and featured list |
| Filter by brandId | Published sellable products for a specific brand |
| Filter by parentCategoryId | Products in parent category + all direct children (2-level max) |
| Filter by price range (minPrice, maxPrice) | Integer paisa comparison on sp column |
| Filter by isBestSeller | Admin-curated bestseller flag |
| Filter by minRating | Minimum average approved review rating (1-5) |
| Filter by brandSeriesId | Products linked to a specific brand series via seriesId FK |
| Filter by isTrending | Admin-set trending flag with optional per-product expiry |
| Sort by rating | Sorted by average approved review score |
| Sort by isBestSeller | Bestseller products first |
| New Arrivals | /products/new-arrivals — products created in last N days |
| Best Sellers | /products/best-sellers — isBestSeller=true with expiry check |
| Popular | /products/popular — same signal as best-sellers (isBestSeller=true) |
| Trending | /products/trending — isTrending=true with expiry check |
5. Key Business Rules
- Pricing rule:
sp <= mrp. - Status transitions are constrained:
draft -> published -> unlisted -> archived. - Customer reads always enforce
status = publishedandisSellable = true. - Slug changes persist previous slug in
product_slug_history. - Tag IDs are de-duplicated before persistence and must all exist.
averageRatingandreviewCountare always included in list item responses (never null for reviewCount — returns 0 when no approved reviews).- Discovery endpoint routes (
/new-arrivals,/best-sellers,/popular,/trending) are declared before the/:slugroute in the controller to prevent route ambiguity. isBestSellerexpiry (isBestSellerExpiresAt) andisTrendingexpiry (trendingExpiresAt) are set at product create/update usingpopularDaysandtrendingDaysadmin DTO fields.0ornull= infinite.
6. State Models
Product lifecycle
Product kind
productKind is a required enum that determines product availability and fulfillment behavior:
physical— shipped physical goods (laptops, monitors, peripherals). Stock-tracked viaproduct_inventory.digital— downloadable or key-based products (software licenses, e-books). Always available when published and sellable.care_package— warranty or service bundles. Always available when published and sellable.
productType is a free-form string (max 80 chars) that describes the specific product category within a kind. Examples: laptop, monitor, software_license, extended_warranty. Not validated against an enum — allows new product types without schema changes.
Product fields (new in IT Mart foundation)
productKind: required enum —physical | digital | care_package.productType: required free-form string (max 80 chars) — e.g.laptop,monitor,software_license,extended_warranty.specifications: optional JSONB object for flexible per-product-type specs — e.g.{ "processor": "Intel Core i7", "ram": "16GB" }. Not validated against a schema; shape is product-type specific.isBestSeller: bestseller marker for storefront merchandising.
Availability by product kind
| productKind | Availability behavior |
|---|---|
physical | Stock-tracked via product_inventory. Returns in_stock, low_stock, or out_of_stock based on inventory row. |
digital | Always available when status=published and isSellable=true. Returns canAddToCart: true, stockStatus: in_stock, availableQuantity: null, maxPurchasableQuantity: 99. |
care_package | Same as digital — always available when published and sellable. |
null (legacy rows) | Treated as non-physical for safety — same semantics as digital/care_package. |
7. Caching Strategy
| Key Prefix | Used For |
|---|---|
catalog:products:list: | Customer list responses |
catalog:product:slug: | Product detail-by-slug responses |
catalog:products:featured: | Customer featured product list |
Stock availability route intentionally bypasses product cache:
GET /api/products/:slug/availabilityGET /api/mobile/products/:slug/availability
TTL resolution in customer service:
CATALOG_PRODUCT_CACHE_TTL_SECONDS- fallback
CATALOG_CACHE_TTL_SECONDS - fallback
REDIS_CACHE_TTL_SECONDS
| Key Prefix | Used For |
|---|---|
catalog:products:new-arrivals: | New arrivals discovery endpoint |
catalog:products:best-sellers: | Best sellers discovery endpoint |
catalog:products:popular: | Popular discovery endpoint |
catalog:products:trending: | Trending discovery endpoint |
All new filter params (brandId, minPrice, maxPrice, isBestSeller, minRating, parentCategoryId, brandSeriesId, isTrending) are included in buildCacheKey() so each unique combination gets its own cache entry.
Admin write invalidation covers:
catalog:products:list:*catalog:product:slug:*catalog:products:featured:*
8. Rate Limiting
Search/list endpoints apply Redis sliding-window throttling in controllers:
- Admin:
CATALOG_ADMIN_PRODUCT_SEARCH_RATE_LIMIT,CATALOG_ADMIN_PRODUCT_SEARCH_RATE_WINDOW_SECONDS - Customer/mobile:
CATALOG_CUSTOMER_PRODUCT_SEARCH_RATE_LIMIT,CATALOG_CUSTOMER_PRODUCT_SEARCH_RATE_WINDOW_SECONDS - Exceeded requests throw
RateLimitExceededException(RATE_LIMIT_EXCEEDED).
9. Queue/Worker Features
No BullMQ queue or worker is implemented inside catalog product flows for this subphase.
10. Error UX Mapping
| errorCode | Typical UX Handling |
|---|---|
PRODUCT_NOT_FOUND | Show not-found page/toast and stop detail flow |
PRODUCT_CATEGORY_NOT_FOUND | Prompt admin to choose valid category |
PRODUCT_TAG_NOT_FOUND | Prompt admin to fix tag selection |
PRODUCT_SP_EXCEEDS_MRP | Inline pricing validation error |
PRODUCT_INVALID_STATUS_TRANSITION | Block invalid status update with guidance |
PRODUCT_INVALID_SORT | Fallback UI to default sort option |
PRODUCT_CREATE_FAILED | Generic admin retry/error banner |
RATE_LIMIT_EXCEEDED | Backoff + retry after delay |
11. Integration Flows
11.1 Product Creation Flow
An admin creates a product with category and tag associations.
- Admin calls
POST /api/admin/productswith title, description, pricing (sp, mrp), category, and tag IDs. - System validates
sp <= mrpand all category/tags exist. - System creates product and associates tags via
product_tagtable. - System captures product slug in
product_slug_historyon creation. - Admin calls
PATCH /api/admin/products/:idto transition status topublished.
11.2 Customer Discovery Flow
A customer discovers and searches products via storefront.
- Customer calls
GET /api/productsto browse published + sellable products. - Customer applies search query via
GET /api/products?search=.... - System performs ILIKE on title/description and similarity scoring.
- Customer filters by category via
GET /api/products?categoryId=.... - Customer filters by brand via
GET /api/products?brandSlug=...orGET /api/products?brandId=.... - Customer filters by brand series via
GET /api/products?seriesId=.... - Customer filters by product type via
GET /api/products?productType=.... - Customer calls
GET /api/products/featuredto see featured products. - Customer calls
GET /api/products/:slugto view product detail. - System resolves current slug first, then falls back to
product_slug_historyfor legacy URLs.
11.2.1 Brand-Based Discovery
Products can be discovered by navigating a brand or brand series:
- Customer calls
GET /api/brandsto browse brands (e.g. Dell, Lenovo, HP). - Customer calls
GET /api/brands/:slug/seriesto see brand series (e.g. XPS Series under Dell). - Customer calls
GET /api/products?brandSlug=dellto list products filtered by brand. - Customer calls
GET /api/products?seriesId=5to list products in a specific series. - Customer calls
GET /api/brand-series/:slugto see series detail with nested product list. - Customer calls
GET /api/products/:slugto view individual product detail.
Public brand and series endpoints return only visible records (brands with isVisible=true, series with isVisible=true). The brand slug is URL-safe and human-readable for SEO-friendly URLs.
11.3 Product Lifecycle Flow
An admin manages product status transitions with slug history.
- Product starts as
draftstatus. - Admin publishes product:
draft -> published. - Admin unlists product:
published -> unlisted. - Admin archives product:
unlisted -> archived. - On any slug change, system writes previous slug to
product_slug_historyfor backward compatibility.
12. Brand Linkage
Products can be optionally associated with a brand:
- Admin CRUD includes optional
brandIdfield — nullable FK tobrand.id. - Brand appears in product detail responses as
brand(nested brand object) orbrandId(flat reference). - Brand-to-product linkage is enforced at the application layer; the FK on
product.brand_idis nullable. - See Catalog Brand - Feature List for brand module capabilities.
Warranty information extends brand-product linkage:
warranty_infoentries are scoped to a brand viawarranty_info.brand_id.- Warranty claims resolve their brand context through
warranty_info.brand_id. - See Warranty - Feature List for warranty module details.
13. Filter & Sort Capabilities
13.1 Admin List Filters
The admin product list (GET /api/admin/products) supports the following filter parameters via QueryProductsAdminDto:
| Parameter | Type | Description |
|---|---|---|
search | string | Partial match on title, slug, description, category name, tag name. Also uses pg_trgm similarity scoring on title. |
categoryId | integer | Exact match by category ID. |
parentCategoryId | integer | Match product category or any direct child category (2-level hierarchy). |
brandId | integer | Exact match by brand ID. |
seriesId | integer | Exact match by brand series ID (products.series_id). |
productKind | enum | physical, digital, or care_package. |
productType | string | Exact match on free-form product type string. |
tagId | integer | Products that have the specified tag (exists subquery on product_tag). |
status | enum | draft, published, unlisted, or archived. |
isSellable | boolean | Filter by sellable flag. |
isBestSeller | boolean | Filter to best-seller products. |
isTrending | boolean | Filter trending products (applies expiry check when true). |
minPrice | integer | Inclusive minimum selling price (paisa). |
maxPrice | integer | Inclusive maximum selling price (paisa). |
minRating | integer | Minimum average approved rating (1-5). Uses a correlated subquery on order_reviews. |
Admin sort fields: title, slug, productType, productKind, mrp, sp, status, isSellable, createdAt, updatedAt, relevance, isBestSeller, rating.
Order: asc or desc (default desc).
Admin list cache key incorporates all active filter, sort, and pagination values. Cache invalidation occurs on any admin create/update/delete.
13.2 Customer List Filters
The customer product list (GET /api/products) supports a subset of admin filters:
| Parameter | Type | Description |
|---|---|---|
search | string | ILIKE on title, description, category name, tag name + similarity scoring. |
categoryId | integer | Exact match by category ID. |
parentCategoryId | integer | Match product category or direct child (2-level hierarchy). |
brandId | integer | Exact match by brand ID. |
brandSeriesId | integer | Exact match by brand series ID. |
productKind | enum | physical, digital, or care_package. |
productType | string | Exact match on product type string. |
tagId | integer | Products with the specified tag. |
isBestSeller | boolean | Filter to best-seller products. |
isTrending | boolean | Filter trending products (applies expiry check when true). |
minPrice | integer | Inclusive minimum selling price. |
maxPrice | integer | Inclusive maximum selling price. |
minRating | integer | Minimum average approved rating (1-5). |
Customer sort fields: title, mrp, sp, createdAt, relevance, isBestSeller, rating.
Order: asc or desc (default desc).
All customer list responses are subject to PRODUCT_INVALID_SORT validation — unknown sort values are caught at the DTO layer.
13.3 Pagination
Both admin and customer list endpoints use PaginationUtil for consistent pagination:
| Parameter | Type | Default | Max | Description |
|---|---|---|---|---|
page | integer | 1 | - | Page number (1-indexed). |
size | integer | 20 | 100 | Items per page. |
pagination | boolean | true | - | Set to false to return all matching records without pagination. |
Paginated responses include totalCount, page, size, and pagination metadata.
14. Average Rating
14.1 Review Summary Integration
Each product list item and product detail response includes rating data from the order_reviews table:
averageRating: Aggregated from allorder_reviewsrows wherestatus = 'approved'for the product. UsesAVG(r.rating)withCOALESCEto return0when no approved reviews exist.reviewCount:COUNT(*)of approved reviews for the product.
The rating subquery is computed inline in the product list query:
SELECT AVG(r.rating)
FROM order_reviews r
WHERE r.product_id = products.id
AND r.status = 'approved'14.2 Impact on List Payload Shape
The averageRating and reviewCount fields are present in every list item response:
{
"id": 101,
"title": "Dell XPS 15 Laptop",
"averageRating": 4.2,
"reviewCount": 12
}averageRatingisnullwhen no approved reviews exist for the product.- The
ratingsort option sorts the product list by this computed average (descending by default).
14.3 Detail Page Rating
The product detail response (ProductResponseDto) additionally includes:
totalReviews: Total count of approved reviews.reviewStatus: Human-readable string —"Reviews available"whentotalReviews > 0,"No reviews yet"otherwise.averageRating: Numeric average (falls back to0when no reviews).
This rating data also feeds the aggregateRating field in the JSON-LD product structured data for SEO.
15. Discover by Brand / Brand-Series
15.1 Brand-Based Product Discovery
Products can be discovered by filtering the product list with a brand identifier:
GET /api/products?brandId=2— Filter by brand numeric ID.GET /api/products?brandSlug=dell— Filter by brand URL slug (resolved to brand ID server-side).
The brand slug resolution flow:
- Customer navigates brands via
GET /api/brands(visible brands only). - Customer selects a brand (e.g. Dell) obtaining the brand slug.
- Customer calls product list with
?brandSlug=dell. - System resolves the slug to the brand ID via the
brandstable. - Products matching
products.brand_id = resolvedIdare returned.
15.2 Brand-Series Product Discovery
Products in a specific brand series are discovered using the series ID filter:
GET /api/products?seriesId=5— Filter by brand series numeric ID.
The series-based discovery flow:
- Customer navigates brand series via
GET /api/brands/:slug/series(visible series only). - Customer selects a series (e.g. XPS Series), obtaining the series ID.
- Customer calls product list with
?seriesId=5. - Products matching
products.series_id = 5are returned.
Both brand and brand-series filters can be combined with other filters (categoryId, productType, price range, etc.).
15.3 Series Detail Page
The standalone brand series detail endpoint provides series information with nested products:
GET /api/brand-series/:slug— Returns series detail including a product list for that series.
This endpoint resolves the series slug to its canonical series record and returns associated products.
15.4 Slug Resolution
Brand slugs are resolved through the brand_slug_history table:
- Lookup
brandsbyslugwithisVisible = true. - If no match, fall back to
brand_slug_history.old_sluglookup. - If historical slug match found, use historical brand record.
Series slugs follow a similar resolution pattern through brand_series_slug_history.
See Catalog Brand - Feature List for brand module capabilities and Brand Series - Feature List for series details.
16. New Arrivals, Popular & Trending
16.1 New Arrivals
- Endpoint:
GET /api/products/new-arrivals - Method:
ProductCustomerService.findNewArrivals() - Behavior: Returns published sellable products created within the last N days.
- Default window: 30 days (configurable via
withinDaysquery parameter, max 365). - Sort:
createdAtdescending (newest first). - Filters: Optional
categoryIdandbrandId. - Cache prefix:
catalog:products:new-arrivals:with keys forwithinDays,categoryId,brandId,page,size. - Admin equivalent:
GET /api/admin/products/new-arrivalsreturns all products (no published/sellable filter).
Flow:
16.2 Popular
- Endpoint:
GET /api/products/popular - Method:
ProductCustomerService.findPopular() - Behavior: Returns products flagged as
isBestSeller = truewith a valid (non-expired) best-seller date window. - Expiry: Products with
isBestSellerExpiresAtin the past are excluded. - Sort:
createdAtdescending. - Filters: Optional
categoryIdandbrandId. - Cache prefix:
catalog:products:popular:. - Admin equivalent:
GET /api/admin/products/popular— sameisBestSellerlogic, no published/sellable filter.
16.3 Trending
- Endpoint:
GET /api/products/trending - Method:
ProductCustomerService.findTrending() - Behavior: Returns products flagged as
isTrending = truewith a valid (non-expired) trending window. - Expiry: Products with
trendingExpiresAtin the past are excluded. - Sort:
createdAtdescending. - Filters: Optional
categoryIdandbrandId. - Cache prefix:
catalog:products:trending:. - Admin equivalent:
GET /api/admin/products/trending— sameisTrendinglogic, no published/sellable filter.
16.4 Cache Strategy
| Endpoint | Cache Prefix | TTL Resolution | Invalidation |
|---|---|---|---|
| New Arrivals | catalog:products:new-arrivals: | CATALOG_PRODUCT_CACHE_TTL_SECONDS > CATALOG_CACHE_TTL_SECONDS > REDIS_CACHE_TTL_SECONDS | Admin create/update/delete |
| Popular | catalog:products:popular: | Same TTL chain | Admin create/update/delete |
| Trending | catalog:products:trending: | Same TTL chain | Admin create/update/delete |
All three endpoints use the same cache-or-fallback pattern as the main product list and featured endpoints. Cache keys incorporate all active query parameters for granular invalidation.
16.5 Best Sellers
The findBestSellers endpoint (GET /api/products/best-sellers) shares the same implementation as findPopular — it returns isBestSeller = true products with expiry validation. The two endpoints are equivalent in behavior but cached under separate prefixes (catalog:products:best-sellers: vs catalog:products:popular:).
17. Release/QA Checklist
- Verify admin CRUD permissions (
Products_*) and swagger endpoints. - Verify list search against title/description/category/tag and similarity behavior.
- Verify slug-history resolution for old product URLs.
- Verify cache invalidation after create/update/delete.
- Verify admin and customer rate-limit behavior in Redis-backed environments.
- Verify new-arrivals endpoint respects
withinDaysboundary and returns products sorted by newest first. - Verify popular endpoint only returns products with valid
isBestSeller+ non-expired window. - Verify trending endpoint only returns products with valid
isTrending+ non-expired window. - Verify average rating appears correctly in list items and detail responses.
- Verify brand-slug and series-id filters correctly scope product results.
- Verify price range filters (
minPrice,maxPrice) enforce inclusive boundary on selling price. - Verify
PRODUCT_INVALID_SORTerror is thrown for unsupported sort values. - Verify all new filter params work on both customer and admin list endpoints.
- Verify
averageRatingisnull(not 0) when no approved reviews exist. - Verify
reviewCountis0(not null) when no approved reviews exist. - Verify discovery routes return correct sets with and without expiry.
- Verify
GET /products/trendingis not caught by/:slugroute matcher. - Verify brand-series filter (
brandSeriesId) uses directseriesIdFK equality. - Verify subcategory filter covers parent + direct children but NOT grandchildren.
18. Product Filter Extensions
18.1 Price Range Filter
minPrice and maxPrice are compared against sp (selling price) in integer paisa.
minPrice=10000 means "sp >= ₹100.00" (10000 paisa).
18.2 Rating Filter
minRating uses a correlated scalar subquery:
SELECT AVG(r.rating) FROM order_reviews r
WHERE r.product_id = product.id AND r.status = 'approved'Only approved reviews count. Products with no approved reviews are excluded when minRating is set.
18.3 Subcategory Filter (2-Level)
parentCategoryId returns products where categoryId is the parent OR any direct child category:
categoryId IN (SELECT id FROM category WHERE id = $parentId OR parent_id = $parentId)Grandchildren categories are NOT traversed in v1 (2-level only).
18.4 Brand Series Filter
brandSeriesId maps directly to products.seriesId FK equality. No subquery needed.
18.5 Trending + Popular Expiry
isTrending and isBestSeller filters check not only the boolean flag but also optional per-product expiry timestamps:
trendingExpiresAt = null→ trending indefinitelytrendingExpiresAt = future date→ trending until that dateisBestSellerExpiresAt— same pattern for popular/bestseller
Expiry is checked at query time. Admin can reset by toggling the flag off.
18.6 Discovery Endpoints
Four dedicated discovery routes return ProductListItemDto[] using the same shape as the main list:
| Route | Signal | Notes |
|---|---|---|
GET /products/new-arrivals | createdAt >= NOW() - withinDays | Default 30 days; withinDays param (1-365) |
GET /products/best-sellers | isBestSeller=true + expiry check | |
GET /products/popular | isBestSeller=true + expiry check | Same signal as best-sellers |
GET /products/trending | isTrending=true + expiry check | Admin-set boolean |
See Also
- API Reference: See Catalog Product - API & Integration Guide Section 7 (Endpoint Reference + Payload Cheatsheet) for complete request/response DTOs.
- Backend Reference: See Catalog Product - Backend Documentation Section 3 (Data Model) for schema details and relationships.
- Brand Module: See Catalog Brand - Feature List for brand list, detail, and slug history capabilities.
- Brand Series Module: See Brand Series - Feature List for series discovery.