23 Backend Module Map
Purpose of this page
Define a backend structure that:
- scales cleanly as features grow (paid plans, sharing, selling, AI jobs),
- stays understandable (easy to navigate, test, and extend),
- enforces security patterns (auth + user scoping by default),
- and keeps “business logic” out of controllers (avoid spaghetti).
- We choose NestJS because it provides a proven modular architecture with dependency injection and testing patterns.
- This matters as soon as we add background jobs, uploads, and search—things that quickly turn an unstructured Express app into a mess.
Architectural principles (why this structure)
- Modules own domains Locations logic lives in a Locations module, Items logic in Items, etc.
- Controllers stay thin Controllers parse request → call service → return response. All rules (validation, ownership checks, invariants) live in services.
- A shared “current user” abstraction Every request can access currentUser safely (no repeated parsing).
- Prisma as a single data access layer PrismaService is shared, and domain services call it.
- Background jobs are a first-class citizen Queue processors live alongside the domain they support (e.g., Uploads/Images).
High-level module list (MVP)
AppModule (root)
ConfigModule (env + validation)
PrismaModule (DB access)
AuthModule (register/login/refresh/logout)
UsersModule (minimal: /me)
LocationsModule (CRUD + detail endpoints)
ItemsModule (CRUD + move)
SearchModule (search endpoint)
UploadsModule (signed URLs + photo attach)
RemindersModule (inventory reminder defaults + summaries)
Owns user reminder settings and inventory-facing reminder summaries such as maintenance timing and low-quantity alerts. This module can depend on subscription plan policy resolution for entitlement checks, but reminder-specific logic should stay out of unrelated domains.
SubscriptionsModule (billing, entitlements, and subscription jobs)
Owns plan policy resolution, billing state, and subscription lifecycle jobs. Trial reminder emails and expired-trial downgrade jobs stay here because they are driven by subscription state rather than item state.
HealthModule (readiness/liveness for deployment)
Optional (V1+):
JobsModule / QueueModule (BullMQ integration)
BillingModule (Stripe)
AnalyticsModule (internal events)
SharingModule (households)
SellerModule (purchase/sold/earnings)
AiModule (async AI jobs)
Folder structure (recommended)
1backend/
2src/
3app.module.ts
4main.ts
6config/
- config.module.ts
- env.validation.ts
- configuration.ts
11prisma/
12 prisma.module.ts
13 prisma.service.ts
15common/
16 decorators/
17 current-user.decorator.ts
18 guards/
19 auth.guard.ts
20 interceptors/
21 request-id.interceptor.ts
22 middleware/
23 logger.middleware.ts
24 pipes/
25 validation.pipe.ts
26 utils/
27 pagination.ts
28 errors.ts
29 types/
30 request-with-user.ts
32auth/
33 auth.module.ts
34 auth.controller.ts
35 auth.service.ts
36 dto/
37 register.dto.ts
38 login.dto.ts
39 strategies/
40 jwt.strategy.ts
41 guards/
42 jwt-auth.guard.ts
43 cookies/
44 cookie.service.ts
46users/
47 users.module.ts
48 users.controller.ts
49 users.service.ts
51locations/
52 locations.module.ts
Why this layout vs a flat structure
When you add features (billing, seller dashboard, AI jobs), you avoid “mega files” and keep changes localized.
Key cross-cutting components (MVP)
- ConfigModule
Responsibilities
Load environment variables
Validate required config (fail fast on boot)
Why
Misconfigured auth cookies, DB URLs, or S3 keys can cause subtle failures. Validating config at startup prevents “it works locally but not in prod” issues. 2. PrismaModule
Responsibilities
Provide Prisma client
Handle connection lifecycle
Optionally: transaction helpers
53 locations.controller.ts
54 locations.service.ts
55 dto/
56 create-location.dto.ts
57 update-location.dto.ts
59items/
60 items.module.ts
61 items.controller.ts
62 items.service.ts
63 dto/
64 create-item.dto.ts
65 update-item.dto.ts
66 move-item.dto.ts
68search/
69 search.module.ts
70 search.controller.ts
71 search.service.ts
73uploads/
74 uploads.module.ts
75 uploads.controller.ts
76 uploads.service.ts
77 dto/
78 presign-upload.dto.ts
80health/
81 health.module.ts
82 health.controller.ts
84jobs/ (V1+)
85 jobs.module.ts
86 queues/
87 processors/
Why
Single DB access point improves consistency and testing. 3. common/ layer
Current user
CurrentUser decorator + request typing
Global validation
One ValidationPipe in main.ts with:
- whitelist true
- forbidNonWhitelisted true
Request ID + logging
Add request ID to logs and error reports
Why
These are the difference between a “toy MVP” and a backend that scales without chaos.
Domain module responsibilities (what lives where)
AuthModule
Responsibilities
Register user (hash password)
Login (verify password, issue tokens)
Refresh (rotate refresh tokens)
Logout (clear cookies)
Why not outsource to a provider
We want full control (no BaaS lock-in) and a portable security model.
UsersModule
Responsibilities
/me endpoint minimal profile
Why
Keeps auth concerns separate from user profile concerns.
LocationsModule
Responsibilities
Create/update/delete locations with sibling uniqueness
List locations (flat)
Location detail response including:
- breadcrumb
- child locations
- items summary
Why service layer is essential
Complexity: tree traversal for breadcrumbs, deletion blocking rules, and ownership checks.
ItemsModule
Responsibilities
CRUD items
Move item endpoint
CSV bulk import (with column mapping)
Optional: items list endpoint for sync
Why dedicated move endpoint
It's a clear, trackable action and enables audit history later.
CSV Import Service
The CsvImportService handles bulk import from CSV data:
- Parses CSV with proper quote handling
- Maps columns to item fields based on user configuration
- Validates data and enforces plan limits (item count, location count)
- Creates items in transactions with tag resolution
- Returns detailed import results with error reporting per row
Feature-gated: requires dataImport feature (PRO plan or above).
SearchModule
Responsibilities
Unified search endpoint
Ranking heuristics
Potential future switch to dedicated search engine (Meilisearch)
Why separate module
Search becomes its own “mini product” over time.
UploadsModule
Responsibilities
Generate signed upload URL
Validate file type/size
Store object key on item
Why separate
Uploads involve security + storage concerns and shouldn't clutter ItemsModule.
HealthModule
Responsibilities
/health liveness endpoint /ready readiness endpoint (DB reachable)
Why
Hosting providers use these for auto-restarts and deploy validation.
Error handling strategy (MVP)
Standardize domain errors
We use a consistent error shape (from Page 16).
Domain services throw typed errors (AppError)
A global exception filter translates them into HTTP responses.
Why
This makes the frontend simpler and reduces one-off error handling.
Testing strategy mapping (how structure supports testing)
Unit test services with Prisma mocked
Integration test controllers with in-memory DB or test DB
Auth tests validate cookie issuance and refresh rotation
Modular structure makes this realistic.
Decisions (locked for MVP)
Modular NestJS structure by domain
Controllers thin, services own business logic
Shared PrismaService
Global validation pipe + consistent error format
Dedicated Uploads module for S3 signed URLs