Shop It Docs
Developer Resourceswishlist

Wishlist Backend Documentation

Wishlist backend module composition, schema, service contracts, invariants, and operational notes.

Wishlist Module - Backend Documentation

1. Backend Scope and Boundaries

Wishlist backend owns:

  • customer wishlist persistence
  • product sellability validation for add operations
  • user-scoped list and bulk delete operations

Wishlist backend does not own:

  • stock reservation
  • cart mutation
  • order/payment side effects
  • queue workers or outbox jobs

2. Module Composition

WishlistModule composes:

  • WishlistCustomerModule

WishlistCustomerModule owns:

  • WishlistCustomerController
  • WishlistCustomerService
  • DTO contracts under wishlist-customer/dto

Dependencies:

  • DatabaseModule for Drizzle DATABASE injection token
  • @nomor/db wishlistItems and products tables

3. Data Model (Drizzle / PostgreSQL)

3.1 Primary table

Table: wishlist_item

ColumnTypeConstraints
idserialprimary key
user_iduuidnot null, FK -> customers.id, on delete cascade
product_idintegernot null, FK -> products.id, on delete cascade
added_attimestampnot null, default now

3.2 Indexes

IndexTypeColumns
wishlist_item_user_product_idxuniqueuser_id, product_id
wishlist_item_user_id_idxbtreeuser_id
wishlist_item_product_id_idxbtreeproduct_id

3.3 Response join tables

The WishlistItemResponseDto is populated by LEFT JOINing:

TableAliasColumns used
productproductstitle, slug, thumbnail_url, product_kind, mrp, sp
product_typeproductTypesname
order_reviews(subquery)AVG(rating), COUNT(*) filtered status = 'approved'

3.4 Relational mapping

  • wishlist item -> customer (many-to-one)
  • wishlist item -> product (many-to-one)

4. Service Contracts

4.1 addItem(userId, dto)

Execution steps:

  1. Query products for id == productId and isSellable == true.
  2. If no row: throw NotFoundException with WISHLIST_PRODUCT_NOT_FOUND.
  3. Query wishlist_item for duplicate (userId, productId).
  4. If duplicate: throw BadRequestException with WISHLIST_ALREADY_EXISTS.
  5. Insert row into wishlist_item.
  6. Query the inserted wishlist item joined with products, productTypes, and order_reviews (AVG rating) to build the expanded response.
  7. Return WishlistItemResponseDto projection with all product fields.

Note: discount is derived as mrp - sp (not stored). averageRating and reviewCount are subqueries against order_reviews filtered to status = 'approved'.

4.2 getItems(userId, query)

Execution steps:

  1. Normalize pagination via PaginationUtil.normalize.
  2. Query total count by userId.
  3. Query rows by userId joined with products (LEFT JOIN), productTypes (LEFT JOIN), and subqueries against order_reviews for AVG(rating) and COUNT(*) (both filtered to status = 'approved').
  4. Apply limit/offset only when pagination is enabled.
  5. Map each row to WishlistItemResponseDto including product fields and computed discount.
  6. Return { items, totalCount, page, size, pagination }.

Query pattern: SELECT w.*, p.title AS productTitle, p.slug AS productSlug, p.thumbnail_url, pt.name AS productType, p.product_kind, p.mrp AS unitMrp, p.sp AS unitSp, (p.mrp - p.sp) AS discount, (SELECT AVG(r.rating) FROM order_reviews r WHERE r.product_id = w.product_id AND r.status = 'approved') AS averageRating, (SELECT COUNT(*) FROM order_reviews r WHERE r.product_id = w.product_id AND r.status = 'approved') AS reviewCount FROM wishlist_item w LEFT JOIN product p ON w.product_id = p.id LEFT JOIN product_type pt ON p.product_type_id = pt.id WHERE w.user_id = ? ORDER BY w.added_at DESC

4.3 removeItems(userId, dto)

Execution steps:

  1. Select rows matching (userId AND productId IN dto.productIds).
  2. If zero rows: throw NotFoundException with WISHLIST_ITEM_NOT_FOUND.
  3. Delete matched row IDs.
  4. Return { deletedCount }.

5. Controller Contracts

Controller: WishlistCustomerController

MethodPathDTO inDTO out
POST/wishlist/itemsAddWishlistItemDtoWishlistItemResponseDto (includes productTitle, productSlug, productThumbnail, productType, productKind, unitMrp, unitSp, discount, averageRating, reviewCount)
GET/wishlist/itemsWishlistQueryDtoWishlistItemResponseDto[] (same expanded shape)
DELETE/wishlist/itemsDeleteWishlistItemsDtoDeleteWishlistItemsResponseDto

Response envelope pattern:

  • ResponseDto<T>
  • Pagination metadata added using PaginationUtil.buildMetadata(...) only when pagination=true.

6. Runtime Invariants

  • User isolation invariant: all DB operations include userId filters.
  • Sellable product invariant: add only for isSellable=true products.
  • Duplicate invariant: one wishlist row per (userId, productId).
  • Delete idempotency-in-practice:
    • first delete may succeed
    • repeated delete with no remaining rows returns not-found domain error

7. Error Contract

ExceptionHTTPerrorCodeTrigger
NotFoundException404WISHLIST_PRODUCT_NOT_FOUNDAdd requested product missing or unsellable
BadRequestException400WISHLIST_ALREADY_EXISTSDuplicate add attempt
NotFoundException404WISHLIST_ITEM_NOT_FOUNDDelete request with zero matched rows

8. Caching, Queues, and Side Effects

  • Caching: none in wishlist module.
  • Queues/workers: none.
  • Outbox events: none.
  • External service calls: none.

Operational implication:

  • wishlist writes are immediate DB transactions without deferred processors.

9. Security and AuthZ

  • Controller protected by JwtAuthGuard.
  • User identity sourced from @CurrentUser("id").
  • No admin role or permission guard for this module.

10. Performance Notes

  • Read queries join products and productTypes with LEFT JOIN, and compute rating aggregates via correlated subqueries on order_reviews (AVG, COUNT filtered to status = 'approved').
  • Indexes support common access paths (user_id list and uniqueness checks, product_id on order_reviews).
  • discount is computed in-query as mrp - sp (not stored).
  • averageRating and reviewCount use correlated subqueries to avoid GROUP BY on the main result set.
  • List ordering by added_at leverages user filtered dataset size.

11. File Map

  • apps/api/src/modules/wishlist/wishlist.module.ts
  • apps/api/src/modules/wishlist/wishlist-customer/wishlist-customer.module.ts
  • apps/api/src/modules/wishlist/wishlist-customer/wishlist-customer.controller.ts
  • apps/api/src/modules/wishlist/wishlist-customer/wishlist-customer.service.ts
  • apps/api/src/modules/wishlist/wishlist-customer/dto/*
  • packages/db/src/schema/wishlist/wishlist-items.schema.ts

12. Release/QA Checklist

  • Unique index exists for (user_id, product_id).
  • Add endpoint validates sellable product.
  • Duplicate add returns WISHLIST_ALREADY_EXISTS.
  • List endpoint returns newest-first items.
  • Delete endpoint returns deletedCount for matched rows.
  • Delete endpoint returns WISHLIST_ITEM_NOT_FOUND when no rows match.

13. See Also