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:
WishlistCustomerControllerWishlistCustomerService- DTO contracts under
wishlist-customer/dto
Dependencies:
DatabaseModulefor DrizzleDATABASEinjection token@nomor/dbwishlistItemsandproductstables
3. Data Model (Drizzle / PostgreSQL)
3.1 Primary table
Table: wishlist_item
| Column | Type | Constraints |
|---|---|---|
id | serial | primary key |
user_id | uuid | not null, FK -> customers.id, on delete cascade |
product_id | integer | not null, FK -> products.id, on delete cascade |
added_at | timestamp | not null, default now |
3.2 Indexes
| Index | Type | Columns |
|---|---|---|
wishlist_item_user_product_idx | unique | user_id, product_id |
wishlist_item_user_id_idx | btree | user_id |
wishlist_item_product_id_idx | btree | product_id |
3.3 Response join tables
The WishlistItemResponseDto is populated by LEFT JOINing:
| Table | Alias | Columns used |
|---|---|---|
product | products | title, slug, thumbnail_url, product_kind, mrp, sp |
product_type | productTypes | name |
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:
- Query
productsforid == productIdandisSellable == true. - If no row: throw
NotFoundExceptionwithWISHLIST_PRODUCT_NOT_FOUND. - Query
wishlist_itemfor duplicate (userId,productId). - If duplicate: throw
BadRequestExceptionwithWISHLIST_ALREADY_EXISTS. - Insert row into
wishlist_item. - Query the inserted wishlist item joined with
products,productTypes, andorder_reviews(AVG rating) to build the expanded response. - Return
WishlistItemResponseDtoprojection with all product fields.
Note:
discountis derived asmrp - sp(not stored).averageRatingandreviewCountare subqueries againstorder_reviewsfiltered tostatus = 'approved'.
4.2 getItems(userId, query)
Execution steps:
- Normalize pagination via
PaginationUtil.normalize. - Query total count by
userId. - Query rows by
userIdjoined withproducts(LEFT JOIN),productTypes(LEFT JOIN), and subqueries againstorder_reviewsforAVG(rating)andCOUNT(*)(both filtered tostatus = 'approved'). - Apply limit/offset only when pagination is enabled.
- Map each row to
WishlistItemResponseDtoincluding product fields and computeddiscount. - 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:
- Select rows matching (
userIdANDproductId IN dto.productIds). - If zero rows: throw
NotFoundExceptionwithWISHLIST_ITEM_NOT_FOUND. - Delete matched row IDs.
- Return
{ deletedCount }.
5. Controller Contracts
Controller: WishlistCustomerController
| Method | Path | DTO in | DTO out |
|---|---|---|---|
| POST | /wishlist/items | AddWishlistItemDto | WishlistItemResponseDto (includes productTitle, productSlug, productThumbnail, productType, productKind, unitMrp, unitSp, discount, averageRating, reviewCount) |
| GET | /wishlist/items | WishlistQueryDto | WishlistItemResponseDto[] (same expanded shape) |
| DELETE | /wishlist/items | DeleteWishlistItemsDto | DeleteWishlistItemsResponseDto |
Response envelope pattern:
ResponseDto<T>- Pagination metadata added using
PaginationUtil.buildMetadata(...)only whenpagination=true.
6. Runtime Invariants
- User isolation invariant: all DB operations include
userIdfilters. - Sellable product invariant: add only for
isSellable=trueproducts. - 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
| Exception | HTTP | errorCode | Trigger |
|---|---|---|---|
NotFoundException | 404 | WISHLIST_PRODUCT_NOT_FOUND | Add requested product missing or unsellable |
BadRequestException | 400 | WISHLIST_ALREADY_EXISTS | Duplicate add attempt |
NotFoundException | 404 | WISHLIST_ITEM_NOT_FOUND | Delete 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
productsandproductTypeswith LEFT JOIN, and compute rating aggregates via correlated subqueries onorder_reviews(AVG,COUNTfiltered tostatus = 'approved'). - Indexes support common access paths (
user_idlist and uniqueness checks,product_idon order_reviews). discountis computed in-query asmrp - sp(not stored).averageRatingandreviewCountuse correlated subqueries to avoid GROUP BY on the main result set.- List ordering by
added_atleverages user filtered dataset size.
11. File Map
apps/api/src/modules/wishlist/wishlist.module.tsapps/api/src/modules/wishlist/wishlist-customer/wishlist-customer.module.tsapps/api/src/modules/wishlist/wishlist-customer/wishlist-customer.controller.tsapps/api/src/modules/wishlist/wishlist-customer/wishlist-customer.service.tsapps/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
deletedCountfor matched rows. - Delete endpoint returns
WISHLIST_ITEM_NOT_FOUNDwhen no rows match.
13. See Also
- Feature Guide: Wishlist Module Feature List
- API Reference: Wishlist API Reference