Back to docs hub·docs/architecture/23-backend-module-map.md
Reference document

23 Backend Module Map

Last updated Mar 23, 2026

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)

  1. Modules own domains Locations logic lives in a Locations module, Items logic in Items, etc.
  2. Controllers stay thin Controllers parse request → call service → return response. All rules (validation, ownership checks, invariants) live in services.
  3. A shared “current user” abstraction Every request can access currentUser safely (no repeated parsing).
  4. Prisma as a single data access layer PrismaService is shared, and domain services call it.
  5. 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/

  1. config.module.ts
  2. env.validation.ts
  3. 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)

  1. 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