Shop It Docs
Developer ResourcescatalogProduct

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
  • Customer controller: apps/api/src/modules/catalog/customer/product/product-customer.controller.ts
    • Base path: /api/products
  • Mobile composition: apps/api/src/modules/mobile/mobile.module.ts
    • Same customer controller additionally exposed as /api/mobile/products

3. Admin Feature Matrix

FeatureBehavior
List productsFilter/search/sort/paginate with permission Products_READ
Get product by idNumeric :id (ParseIntPipe)
Create productValidates pricing/category/tags and writes product + product_tag
Update productSupports partial updates, status transition validation, slug-history capture
Delete productHard delete by id with cache invalidation
Audit activityCreate/update/delete activity records when actor context is present

4. Customer/Mobile Feature Matrix

FeatureBehavior
Product listPublished + sellable products only
Featured products listReturns products linked through featured rows that are active and in date window
Product by slugResolves current slug first, then product_slug_history fallback
Product availability by slugReturns live stock availability for cart actions (in_stock, low_stock, out_of_stock)
SearchTitle/description/category/tag ILIKE + title similarity threshold
PaginationSupported for list and featured list
Filter by brandIdPublished sellable products for a specific brand
Filter by parentCategoryIdProducts in parent category + all direct children (2-level max)
Filter by price range (minPrice, maxPrice)Integer paisa comparison on sp column
Filter by isBestSellerAdmin-curated bestseller flag
Filter by minRatingMinimum average approved review rating (1-5)
Filter by brandSeriesIdProducts linked to a specific brand series via seriesId FK
Filter by isTrendingAdmin-set trending flag with optional per-product expiry
Sort by ratingSorted by average approved review score
Sort by isBestSellerBestseller products first
New Arrivals/products/new-arrivals — products created in last N days
Best Sellers/products/best-sellersisBestSeller=true with expiry check
Popular/products/popular — same signal as best-sellers (isBestSeller=true)
Trending/products/trendingisTrending=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 = published and isSellable = true.
  • Slug changes persist previous slug in product_slug_history.
  • Tag IDs are de-duplicated before persistence and must all exist.
  • averageRating and reviewCount are 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 /:slug route in the controller to prevent route ambiguity.
  • isBestSeller expiry (isBestSellerExpiresAt) and isTrending expiry (trendingExpiresAt) are set at product create/update using popularDays and trendingDays admin DTO fields. 0 or null = 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 via product_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

productKindAvailability behavior
physicalStock-tracked via product_inventory. Returns in_stock, low_stock, or out_of_stock based on inventory row.
digitalAlways available when status=published and isSellable=true. Returns canAddToCart: true, stockStatus: in_stock, availableQuantity: null, maxPurchasableQuantity: 99.
care_packageSame 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 PrefixUsed 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/availability
  • GET /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 PrefixUsed 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

errorCodeTypical UX Handling
PRODUCT_NOT_FOUNDShow not-found page/toast and stop detail flow
PRODUCT_CATEGORY_NOT_FOUNDPrompt admin to choose valid category
PRODUCT_TAG_NOT_FOUNDPrompt admin to fix tag selection
PRODUCT_SP_EXCEEDS_MRPInline pricing validation error
PRODUCT_INVALID_STATUS_TRANSITIONBlock invalid status update with guidance
PRODUCT_INVALID_SORTFallback UI to default sort option
PRODUCT_CREATE_FAILEDGeneric admin retry/error banner
RATE_LIMIT_EXCEEDEDBackoff + retry after delay

11. Integration Flows

11.1 Product Creation Flow

An admin creates a product with category and tag associations.

  1. Admin calls POST /api/admin/products with title, description, pricing (sp, mrp), category, and tag IDs.
  2. System validates sp <= mrp and all category/tags exist.
  3. System creates product and associates tags via product_tag table.
  4. System captures product slug in product_slug_history on creation.
  5. Admin calls PATCH /api/admin/products/:id to transition status to published.

11.2 Customer Discovery Flow

A customer discovers and searches products via storefront.

  1. Customer calls GET /api/products to browse published + sellable products.
  2. Customer applies search query via GET /api/products?search=....
  3. System performs ILIKE on title/description and similarity scoring.
  4. Customer filters by category via GET /api/products?categoryId=....
  5. Customer filters by brand via GET /api/products?brandSlug=... or GET /api/products?brandId=....
  6. Customer filters by brand series via GET /api/products?seriesId=....
  7. Customer filters by product type via GET /api/products?productType=....
  8. Customer calls GET /api/products/featured to see featured products.
  9. Customer calls GET /api/products/:slug to view product detail.
  10. System resolves current slug first, then falls back to product_slug_history for legacy URLs.

11.2.1 Brand-Based Discovery

Products can be discovered by navigating a brand or brand series:

  1. Customer calls GET /api/brands to browse brands (e.g. Dell, Lenovo, HP).
  2. Customer calls GET /api/brands/:slug/series to see brand series (e.g. XPS Series under Dell).
  3. Customer calls GET /api/products?brandSlug=dell to list products filtered by brand.
  4. Customer calls GET /api/products?seriesId=5 to list products in a specific series.
  5. Customer calls GET /api/brand-series/:slug to see series detail with nested product list.
  6. Customer calls GET /api/products/:slug to 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.

  1. Product starts as draft status.
  2. Admin publishes product: draft -> published.
  3. Admin unlists product: published -> unlisted.
  4. Admin archives product: unlisted -> archived.
  5. On any slug change, system writes previous slug to product_slug_history for backward compatibility.

12. Brand Linkage

Products can be optionally associated with a brand:

  • Admin CRUD includes optional brandId field — nullable FK to brand.id.
  • Brand appears in product detail responses as brand (nested brand object) or brandId (flat reference).
  • Brand-to-product linkage is enforced at the application layer; the FK on product.brand_id is nullable.
  • See Catalog Brand - Feature List for brand module capabilities.

Warranty information extends brand-product linkage:

  • warranty_info entries are scoped to a brand via warranty_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:

ParameterTypeDescription
searchstringPartial match on title, slug, description, category name, tag name. Also uses pg_trgm similarity scoring on title.
categoryIdintegerExact match by category ID.
parentCategoryIdintegerMatch product category or any direct child category (2-level hierarchy).
brandIdintegerExact match by brand ID.
seriesIdintegerExact match by brand series ID (products.series_id).
productKindenumphysical, digital, or care_package.
productTypestringExact match on free-form product type string.
tagIdintegerProducts that have the specified tag (exists subquery on product_tag).
statusenumdraft, published, unlisted, or archived.
isSellablebooleanFilter by sellable flag.
isBestSellerbooleanFilter to best-seller products.
isTrendingbooleanFilter trending products (applies expiry check when true).
minPriceintegerInclusive minimum selling price (paisa).
maxPriceintegerInclusive maximum selling price (paisa).
minRatingintegerMinimum 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:

ParameterTypeDescription
searchstringILIKE on title, description, category name, tag name + similarity scoring.
categoryIdintegerExact match by category ID.
parentCategoryIdintegerMatch product category or direct child (2-level hierarchy).
brandIdintegerExact match by brand ID.
brandSeriesIdintegerExact match by brand series ID.
productKindenumphysical, digital, or care_package.
productTypestringExact match on product type string.
tagIdintegerProducts with the specified tag.
isBestSellerbooleanFilter to best-seller products.
isTrendingbooleanFilter trending products (applies expiry check when true).
minPriceintegerInclusive minimum selling price.
maxPriceintegerInclusive maximum selling price.
minRatingintegerMinimum 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:

ParameterTypeDefaultMaxDescription
pageinteger1-Page number (1-indexed).
sizeinteger20100Items per page.
paginationbooleantrue-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 all order_reviews rows where status = 'approved' for the product. Uses AVG(r.rating) with COALESCE to return 0 when 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
}
  • averageRating is null when no approved reviews exist for the product.
  • The rating sort 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" when totalReviews > 0, "No reviews yet" otherwise.
  • averageRating: Numeric average (falls back to 0 when 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:

  1. Customer navigates brands via GET /api/brands (visible brands only).
  2. Customer selects a brand (e.g. Dell) obtaining the brand slug.
  3. Customer calls product list with ?brandSlug=dell.
  4. System resolves the slug to the brand ID via the brands table.
  5. Products matching products.brand_id = resolvedId are 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:

  1. Customer navigates brand series via GET /api/brands/:slug/series (visible series only).
  2. Customer selects a series (e.g. XPS Series), obtaining the series ID.
  3. Customer calls product list with ?seriesId=5.
  4. Products matching products.series_id = 5 are 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:

  1. Lookup brands by slug with isVisible = true.
  2. If no match, fall back to brand_slug_history.old_slug lookup.
  3. 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.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 withinDays query parameter, max 365).
  • Sort: createdAt descending (newest first).
  • Filters: Optional categoryId and brandId.
  • Cache prefix: catalog:products:new-arrivals: with keys for withinDays, categoryId, brandId, page, size.
  • Admin equivalent: GET /api/admin/products/new-arrivals returns all products (no published/sellable filter).

Flow:

  • Endpoint: GET /api/products/popular
  • Method: ProductCustomerService.findPopular()
  • Behavior: Returns products flagged as isBestSeller = true with a valid (non-expired) best-seller date window.
  • Expiry: Products with isBestSellerExpiresAt in the past are excluded.
  • Sort: createdAt descending.
  • Filters: Optional categoryId and brandId.
  • Cache prefix: catalog:products:popular:.
  • Admin equivalent: GET /api/admin/products/popular — same isBestSeller logic, no published/sellable filter.
  • Endpoint: GET /api/products/trending
  • Method: ProductCustomerService.findTrending()
  • Behavior: Returns products flagged as isTrending = true with a valid (non-expired) trending window.
  • Expiry: Products with trendingExpiresAt in the past are excluded.
  • Sort: createdAt descending.
  • Filters: Optional categoryId and brandId.
  • Cache prefix: catalog:products:trending:.
  • Admin equivalent: GET /api/admin/products/trending — same isTrending logic, no published/sellable filter.

16.4 Cache Strategy

EndpointCache PrefixTTL ResolutionInvalidation
New Arrivalscatalog:products:new-arrivals:CATALOG_PRODUCT_CACHE_TTL_SECONDS > CATALOG_CACHE_TTL_SECONDS > REDIS_CACHE_TTL_SECONDSAdmin create/update/delete
Popularcatalog:products:popular:Same TTL chainAdmin create/update/delete
Trendingcatalog:products:trending:Same TTL chainAdmin 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 withinDays boundary 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_SORT error is thrown for unsupported sort values.
  • Verify all new filter params work on both customer and admin list endpoints.
  • Verify averageRating is null (not 0) when no approved reviews exist.
  • Verify reviewCount is 0 (not null) when no approved reviews exist.
  • Verify discovery routes return correct sets with and without expiry.
  • Verify GET /products/trending is not caught by /:slug route matcher.
  • Verify brand-series filter (brandSeriesId) uses direct seriesId FK 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.

isTrending and isBestSeller filters check not only the boolean flag but also optional per-product expiry timestamps:

  • trendingExpiresAt = null → trending indefinitely
  • trendingExpiresAt = future date → trending until that date
  • isBestSellerExpiresAt — 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:

RouteSignalNotes
GET /products/new-arrivalscreatedAt >= NOW() - withinDaysDefault 30 days; withinDays param (1-365)
GET /products/best-sellersisBestSeller=true + expiry check
GET /products/popularisBestSeller=true + expiry checkSame signal as best-sellers
GET /products/trendingisTrending=true + expiry checkAdmin-set boolean

See Also