Happy House - Ecommerce Docs
Developer ResourcesShipping

Shipping Backend Documentation

Backend architecture, data model, services, cache, and operational behavior for the Shipping module.

Shipping - Backend Documentation

1. Documentation Evidence

AreaFiles InspectedVerified Details
Module wiringapps/api/src/modules/shipping/ module filesAdmin + customer leaves, mobile registration
Controllersadmin/shipping-admin.controller.ts, customer/shipping-quote-customer.controller.tsRoutes, permissions, rate limits
Servicesshipping-admin.service.ts, shipping-admin-bulk.service.ts, shipping-quote-customer.service.ts, shipping-serviceability.service.tsUpsert, bulk transaction, availability flags
Schemapackages/db/src/schema/shipping/shipping-rate.tsdistrict_id unique, fee bigint
Cachecache-invalidation.tags.tsshipping domain

2. Backend Scope and Boundaries

Owns

  • The flat per-district delivery fee (shipping_rate).
  • Admin coverage, upsert, delete and bulk pricing.
  • The customer quote and the batched ShippingServiceabilityService.

Does Not Own

  • The address table. Shipping takes a districtId and never reads customer_address — that is a module boundary, not an implementation detail.
  • Order snapshots. An order must snapshot the quoted fee; repricing a district never changes what an existing order was charged (see address/backend §2).

Source of Truth

ConcernSource of TruthNotes
Rateshipping_rate.district_id unique row
Serviceabilityconfigured && activeComputed; cached behind one key
Fee truth for ordersOrder snapshotNever the live rate

3. Module Composition

ModuleTypePathControllersProvidersExportsResponsibility
ShippingAdminModuleLeafadmin/ShippingAdminControllerAdmin + bulk servicesCoverage/upsert/delete/bulk
ShippingQuoteCustomerModuleLeafcustomer/ShippingQuoteCustomerControllerQuote + serviceabilityServiceabilityQuote
Mobile compositionmobile.module.tsQuote mounted under /api/mobile/shipping

4. File and Directory Map

apps/api/src/modules/shipping/
  admin/
    shipping-admin.controller.ts
    shipping-admin.service.ts
    shipping-admin-bulk.service.ts
    dto/
  customer/
    shipping-quote-customer.controller.ts
    shipping-quote-customer.service.ts
    shipping-serviceability.service.ts   # batched, one cache key
    dto/
packages/db/src/schema/shipping/
  shipping-rate.ts

Key files:

FilePurposeKey ExportsNotes
admin/shipping-admin-bulk.service.tsBulk pricingShippingAdminBulkServiceOne transaction; skipped-district report
customer/shipping-serviceability.service.tsAvailability for many districtsShippingServiceabilityServiceBatched, one cache key

5. Data Model

5.1 Schema Source

packages/db/src/schema/shipping/shipping-rate.ts

5.2 Tables

shipping_rate

ColumnTypeNullableDefaultIndex/ConstraintRelationNotes
idserialNoPKInternal
public_iduuid v7NogeneratedUNIQUEExposed id
district_idintegerNoUNIQUEdistrict.idOne rate per district — this is what makes PUT an upsert
feebigintNoCHECK >= 0Minor units; 0 = free
is_activebooleanNoPause keeps the fee
created_at / updated_attimestamptzNonow()

There is deliberately no currency column — the system is single-currency (NPR), and a per-district currency would be dead weight.

5.3 Relationship Diagram

6. Services and Responsibilities

6.1 ShippingAdminService

MethodCalled ByReadsWritesSide EffectsErrors
listDistricts()GET /districtsdistrict + rate (left join)
getDistrictRate()GET /:districtId/ratedistrict + rateSHIPPING_DISTRICT_NOT_FOUND
upsertRate()PUTdistrictshipping_rateactivity, cacheSHIPPING_DISTRICT_NOT_FOUND, SHIPPING_FEE_NEGATIVE
deleteRate()DELETEraterate row removedactivity, cacheSHIPPING_RATE_NOT_FOUND

6.2 ShippingAdminBulkService

MethodCalled ByReadsWritesSide EffectsErrors
bulkUpsertRates()POST /rates/bulkdistricts by scope unionmany rate rows, one transactionactivity, cacheSHIPPING_BULK_SCOPE_EMPTY, SHIPPING_BULK_NO_CHANGE_REQUESTED, SHIPPING_BULK_LIMIT_EXCEEDED, district/province not-found

Bulk semantics: scopes union and de-duplicate; a status-only bulk is a plain UPDATE (never an upsert) so it cannot write a stale fee over a concurrent price change; districts without a fee under a status-only call are skipped and named in the report.

6.3 ShippingQuoteCustomerService / ShippingServiceabilityService

Quote resolves one district and answers { fee, serviceable }. Serviceability is batched for many districts (the address book's list reads) and sits behind one cache key in the shipping cache domain — a single admin edit invalidates all of it, so no address list can serve a stale availability.

7. Runtime Flows

7.1 Quote

7.2 Bulk upsert

8. Cache

DomainRevalidation tagsRedis patterns
shippingshipping:* tagsshipping:*

Every admin write (upsert, delete, bulk) invalidates the domain after commit; the quote and the address book's serviceability read sit behind the same key, so one edit refreshes every surface.

9. Jobs and Workers

None — shipping is synchronous. No queue, no outbox involvement.

10. Security and Authorization

  • Admin: JwtAuthGuard + RoleGuard; permissions Shipping_READ / Shipping_UPDATE / Shipping_DELETE.
  • Permissions come from one list: packages/db/src/authorization/permission-catalog.ts. The API's PermissionCode union and the seed's role grants both derive from it, and permissions-catalog.spec.ts fails the build if they diverge. Adding a module means adding it there, then running permissions:sync and db:seed — never a manual grant. The seed grants every module's permissions to admin except System_*; superadmin bypasses the check.
  • Customer quote: JwtAuthGuard (not @Public()) — quoting is a signed-in step, and the account-keyed throttle is only meaningful for an authenticated caller.
  • Rate limits: ADMIN_READ 30/min, ADMIN_WRITE 10/min, ADMIN_BULK_WRITE 5/min (one request can reprice all 77 districts), CUSTOMER_READ 60/min keyed on the account.