Files
flyer-crawler.projectium.com/docs/adr/0047-project-file-and-folder-organization.md
Torben Sorensen e14c19c112
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 16m0s
linting docs + some fixes go claude and gemini
2026-01-09 22:38:57 -08:00

20 KiB

ADR-047: Project File and Folder Organization

Date: 2026-01-09

Status: Proposed

Effort: XL (Major reorganization across entire codebase)

Context

The project has grown organically with a mix of organizational patterns:

  • By Type: Components, hooks, middleware, utilities, types all in flat directories
  • By Feature: Routes, database modules, and partial feature directories
  • Mixed Concerns: Frontend and backend code intermingled in src/

Current pain points:

  1. Flat services directory: 75+ files with no subdirectory grouping
  2. Monolithic types.ts: 750+ lines, unclear when to add new types
  3. Flat components directory: 43+ components at root level
  4. Incomplete feature modules: Features contain only UI, not domain logic
  5. No clear frontend/backend separation: Both share src/ root

As the project scales, these issues compound, making navigation, refactoring, and onboarding increasingly difficult.

Decision

We will adopt a domain-driven organization with clear separation between:

  1. Client code (React, browser-only)
  2. Server code (Express, Node-only)
  3. Shared code (Types, utilities used by both)

Within each layer, organize by feature/domain rather than by file type.

Design Principles

  • Colocation: Related code lives together (components, hooks, types, tests)
  • Explicit Boundaries: Clear separation between client, server, and shared
  • Feature Ownership: Each domain owns its entire vertical slice
  • Discoverability: New developers can find code by thinking about features, not file types
  • Incremental Migration: Structure supports gradual transition from current layout

Target Directory Structure

src/
├── client/                      # React frontend (browser-only code)
│   ├── app/                     # App shell and routing
│   │   ├── App.tsx
│   │   ├── routes.tsx
│   │   └── providers/           # React context providers
│   │       ├── AppProviders.tsx
│   │       ├── AuthProvider.tsx
│   │       ├── FlyersProvider.tsx
│   │       └── index.ts
│   │
│   ├── features/                # Feature modules (UI + hooks + types)
│   │   ├── auth/
│   │   │   ├── components/
│   │   │   │   ├── LoginForm.tsx
│   │   │   │   ├── RegisterForm.tsx
│   │   │   │   └── index.ts
│   │   │   ├── hooks/
│   │   │   │   ├── useAuth.ts
│   │   │   │   ├── useLogin.ts
│   │   │   │   └── index.ts
│   │   │   ├── types.ts
│   │   │   └── index.ts
│   │   │
│   │   ├── flyer/
│   │   │   ├── components/
│   │   │   │   ├── FlyerCard.tsx
│   │   │   │   ├── FlyerGrid.tsx
│   │   │   │   ├── FlyerUploader.tsx
│   │   │   │   ├── BulkImporter.tsx
│   │   │   │   └── index.ts
│   │   │   ├── hooks/
│   │   │   │   ├── useFlyersQuery.ts
│   │   │   │   ├── useFlyerUploadMutation.ts
│   │   │   │   └── index.ts
│   │   │   ├── types.ts
│   │   │   └── index.ts
│   │   │
│   │   ├── shopping/
│   │   │   ├── components/
│   │   │   ├── hooks/
│   │   │   ├── types.ts
│   │   │   └── index.ts
│   │   │
│   │   ├── recipes/
│   │   │   ├── components/
│   │   │   ├── hooks/
│   │   │   └── index.ts
│   │   │
│   │   ├── charts/
│   │   │   ├── components/
│   │   │   └── index.ts
│   │   │
│   │   ├── voice-assistant/
│   │   │   ├── components/
│   │   │   └── index.ts
│   │   │
│   │   ├── user/
│   │   │   ├── components/
│   │   │   ├── hooks/
│   │   │   └── index.ts
│   │   │
│   │   ├── gamification/
│   │   │   ├── components/
│   │   │   ├── hooks/
│   │   │   └── index.ts
│   │   │
│   │   └── admin/
│   │       ├── components/
│   │       ├── hooks/
│   │       ├── pages/              # Admin-specific pages
│   │       └── index.ts
│   │
│   ├── pages/                   # Route page components
│   │   ├── HomePage.tsx
│   │   ├── MyDealsPage.tsx
│   │   ├── UserProfilePage.tsx
│   │   └── index.ts
│   │
│   ├── components/              # Shared UI components
│   │   ├── ui/                  # Primitive components (design system)
│   │   │   ├── Button.tsx
│   │   │   ├── Card.tsx
│   │   │   ├── Input.tsx
│   │   │   ├── Modal.tsx
│   │   │   ├── Badge.tsx
│   │   │   └── index.ts
│   │   │
│   │   ├── layout/              # Layout components
│   │   │   ├── Header.tsx
│   │   │   ├── Footer.tsx
│   │   │   ├── Sidebar.tsx
│   │   │   ├── PageLayout.tsx
│   │   │   └── index.ts
│   │   │
│   │   ├── feedback/            # User feedback components
│   │   │   ├── LoadingSpinner.tsx
│   │   │   ├── ErrorMessage.tsx
│   │   │   ├── Toast.tsx
│   │   │   ├── ConfirmDialog.tsx
│   │   │   └── index.ts
│   │   │
│   │   ├── forms/               # Form components
│   │   │   ├── FormField.tsx
│   │   │   ├── SearchInput.tsx
│   │   │   ├── DatePicker.tsx
│   │   │   └── index.ts
│   │   │
│   │   ├── icons/               # Icon components
│   │   │   ├── ChevronIcon.tsx
│   │   │   ├── UserIcon.tsx
│   │   │   └── index.ts
│   │   │
│   │   └── index.ts
│   │
│   ├── hooks/                   # Shared hooks (not feature-specific)
│   │   ├── useDebounce.ts
│   │   ├── useLocalStorage.ts
│   │   ├── useMediaQuery.ts
│   │   └── index.ts
│   │
│   ├── services/                # Client-side services (API clients)
│   │   ├── apiClient.ts
│   │   ├── logger.client.ts
│   │   └── index.ts
│   │
│   ├── lib/                     # Third-party library wrappers
│   │   ├── queryClient.ts
│   │   ├── toast.ts
│   │   └── index.ts
│   │
│   └── styles/                  # Global styles
│       ├── globals.css
│       └── tailwind.css
│
├── server/                      # Express backend (Node-only code)
│   ├── app.ts                   # Express app setup
│   ├── server.ts                # Server entry point
│   │
│   ├── domains/                 # Domain modules (business logic)
│   │   ├── auth/
│   │   │   ├── auth.service.ts
│   │   │   ├── auth.routes.ts
│   │   │   ├── auth.controller.ts
│   │   │   ├── auth.repository.ts
│   │   │   ├── auth.types.ts
│   │   │   ├── auth.service.test.ts
│   │   │   ├── auth.routes.test.ts
│   │   │   └── index.ts
│   │   │
│   │   ├── flyer/
│   │   │   ├── flyer.service.ts
│   │   │   ├── flyer.routes.ts
│   │   │   ├── flyer.controller.ts
│   │   │   ├── flyer.repository.ts
│   │   │   ├── flyer.types.ts
│   │   │   ├── flyer.processing.ts      # Flyer-specific processing logic
│   │   │   ├── flyer.ai.ts              # AI integration for flyers
│   │   │   └── index.ts
│   │   │
│   │   ├── user/
│   │   │   ├── user.service.ts
│   │   │   ├── user.routes.ts
│   │   │   ├── user.controller.ts
│   │   │   ├── user.repository.ts
│   │   │   └── index.ts
│   │   │
│   │   ├── shopping/
│   │   │   ├── shopping.service.ts
│   │   │   ├── shopping.routes.ts
│   │   │   ├── shopping.repository.ts
│   │   │   └── index.ts
│   │   │
│   │   ├── recipe/
│   │   │   ├── recipe.service.ts
│   │   │   ├── recipe.routes.ts
│   │   │   ├── recipe.repository.ts
│   │   │   └── index.ts
│   │   │
│   │   ├── gamification/
│   │   │   ├── gamification.service.ts
│   │   │   ├── gamification.routes.ts
│   │   │   ├── gamification.repository.ts
│   │   │   └── index.ts
│   │   │
│   │   ├── notification/
│   │   │   ├── notification.service.ts
│   │   │   ├── email.service.ts
│   │   │   └── index.ts
│   │   │
│   │   ├── ai/
│   │   │   ├── ai.service.ts
│   │   │   ├── ai.client.ts
│   │   │   ├── ai.prompts.ts
│   │   │   └── index.ts
│   │   │
│   │   └── admin/
│   │       ├── admin.routes.ts
│   │       ├── admin.controller.ts
│   │       ├── admin.service.ts
│   │       └── index.ts
│   │
│   ├── middleware/              # Express middleware
│   │   ├── auth.middleware.ts
│   │   ├── validation.middleware.ts
│   │   ├── errorHandler.middleware.ts
│   │   ├── rateLimit.middleware.ts
│   │   ├── fileUpload.middleware.ts
│   │   └── index.ts
│   │
│   ├── infrastructure/          # Cross-cutting infrastructure
│   │   ├── database/
│   │   │   ├── pool.ts
│   │   │   ├── migrations/
│   │   │   └── seeds/
│   │   │
│   │   ├── cache/
│   │   │   ├── redis.ts
│   │   │   └── cacheService.ts
│   │   │
│   │   ├── queue/
│   │   │   ├── queueService.ts
│   │   │   ├── workers/
│   │   │   │   ├── email.worker.ts
│   │   │   │   ├── flyer.worker.ts
│   │   │   │   └── index.ts
│   │   │   └── index.ts
│   │   │
│   │   ├── jobs/
│   │   │   ├── cronJobs.ts
│   │   │   ├── dailyAnalytics.job.ts
│   │   │   └── index.ts
│   │   │
│   │   └── logging/
│   │       ├── logger.ts
│   │       └── index.ts
│   │
│   ├── config/                  # Server configuration
│   │   ├── database.config.ts
│   │   ├── redis.config.ts
│   │   ├── auth.config.ts
│   │   └── index.ts
│   │
│   └── utils/                   # Server-only utilities
│       ├── imageProcessor.ts
│       ├── geocoding.ts
│       └── index.ts
│
├── shared/                      # Code shared between client and server
│   ├── types/                   # Shared TypeScript types
│   │   ├── entities/            # Domain entities
│   │   │   ├── flyer.types.ts
│   │   │   ├── user.types.ts
│   │   │   ├── shopping.types.ts
│   │   │   ├── recipe.types.ts
│   │   │   └── index.ts
│   │   │
│   │   ├── api/                 # API contract types
│   │   │   ├── requests.ts
│   │   │   ├── responses.ts
│   │   │   ├── errors.ts
│   │   │   └── index.ts
│   │   │
│   │   └── index.ts
│   │
│   ├── schemas/                 # Zod validation schemas
│   │   ├── flyer.schema.ts
│   │   ├── user.schema.ts
│   │   ├── auth.schema.ts
│   │   └── index.ts
│   │
│   ├── constants/               # Shared constants
│   │   ├── categories.ts
│   │   ├── errorCodes.ts
│   │   └── index.ts
│   │
│   └── utils/                   # Isomorphic utilities
│       ├── formatting.ts
│       ├── validation.ts
│       └── index.ts
│
├── tests/                       # Test infrastructure
│   ├── setup/
│   │   ├── vitest.setup.ts
│   │   └── testDb.setup.ts
│   │
│   ├── fixtures/
│   │   ├── mockFactories.ts
│   │   ├── sampleFlyers/
│   │   └── index.ts
│   │
│   ├── utils/
│   │   ├── testHelpers.ts
│   │   └── index.ts
│   │
│   ├── integration/             # Integration tests
│   │   ├── api/
│   │   └── database/
│   │
│   └── e2e/                     # End-to-end tests
│       └── flows/
│
├── scripts/                     # Build and utility scripts
│   ├── seed.ts
│   ├── migrate.ts
│   └── generateTypes.ts
│
└── index.tsx                    # Client entry point

Domain Module Structure

Each server domain follows a consistent structure:

domains/flyer/
├── flyer.service.ts        # Business logic
├── flyer.routes.ts         # Express routes
├── flyer.controller.ts     # Route handlers
├── flyer.repository.ts     # Database access
├── flyer.types.ts          # Domain-specific types
├── flyer.service.test.ts   # Service tests
├── flyer.routes.test.ts    # Route tests
└── index.ts                # Public API

Domain Index Pattern

Each domain exports a clean public API:

// server/domains/flyer/index.ts
export { FlyerService } from './flyer.service';
export { flyerRoutes } from './flyer.routes';
export type { FlyerWithItems, FlyerCreateInput } from './flyer.types';

Client Feature Module Structure

Each client feature follows a consistent structure:

client/features/flyer/
├── components/
│   ├── FlyerCard.tsx
│   ├── FlyerCard.test.tsx
│   ├── FlyerGrid.tsx
│   └── index.ts
├── hooks/
│   ├── useFlyersQuery.ts
│   ├── useFlyerUploadMutation.ts
│   └── index.ts
├── types.ts                # Feature-specific client types
└── index.ts                # Public API

Import Path Aliases

Configure TypeScript and bundler for clean imports:

// tsconfig.json paths
{
  "paths": {
    "@/client/*": ["src/client/*"],
    "@/server/*": ["src/server/*"],
    "@/shared/*": ["src/shared/*"],
    "@/tests/*": ["src/tests/*"]
  }
}

// Usage examples
import { Button, Card } from '@/client/components/ui';
import { useFlyersQuery } from '@/client/features/flyer';
import { FlyerService } from '@/server/domains/flyer';
import type { Flyer } from '@/shared/types/entities';

Migration Strategy

Given the scope of this reorganization, migrate incrementally:

Phase 1: Create Directory Structure

  1. Create client/, server/, shared/ directories
  2. Set up path aliases in tsconfig.json
  3. Update build configuration (Vite)

Phase 2: Migrate Shared Code

  1. Move types to shared/types/
  2. Move schemas to shared/schemas/
  3. Move shared utils to shared/utils/
  4. Update imports across codebase

Phase 3: Migrate Server Code

  1. Create server/domains/ structure
  2. Move one domain at a time (start with auth or user)
  3. Move each service + routes + repository together
  4. Update route registration in app.ts
  5. Run tests after each domain migration

Phase 4: Migrate Client Code

  1. Create client/features/ structure
  2. Move components into features
  3. Move hooks into features or shared hooks
  4. Move pages to client/pages/
  5. Organize shared components into categories

Phase 5: Cleanup

  1. Remove empty old directories
  2. Update all remaining imports
  3. Update CI/CD paths if needed
  4. Update documentation

Naming Conventions

Item Convention Example
Domain directory lowercase flyer/, shopping/
Feature directory kebab-case voice-assistant/
Service file domain.service.ts flyer.service.ts
Route file domain.routes.ts flyer.routes.ts
Repository file domain.repository.ts flyer.repository.ts
Component file PascalCase.tsx FlyerCard.tsx
Hook file camelCase.ts useFlyersQuery.ts
Type file domain.types.ts flyer.types.ts
Test file *.test.ts(x) flyer.service.test.ts
Index file index.ts index.ts

File Placement Guidelines

Where does this file go?

If the file is... Place it in...
Used only by React client/
Used only by Express/Node server/
TypeScript types used by both shared/types/
Zod schemas shared/schemas/
React component for one feature client/features/{feature}/components/
React component used across features client/components/
React hook for one feature client/features/{feature}/hooks/
React hook used across features client/hooks/
Business logic for a domain server/domains/{domain}/
Database access for a domain server/domains/{domain}/{domain}.repository.ts
Express middleware server/middleware/
Background job worker server/infrastructure/queue/workers/
Cron job definition server/infrastructure/jobs/
Test factory/fixture tests/fixtures/

Consequences

Positive

  • Clear Boundaries: Frontend, backend, and shared code are explicitly separated
  • Feature Discoverability: Find all code for a feature in one place
  • Parallel Development: Teams can work on domains independently
  • Easier Refactoring: Domain boundaries make changes localized
  • Better Onboarding: New developers navigate by feature, not file type
  • Scalability: Structure supports growth without becoming unwieldy

Negative

  • Large Migration Effort: Significant one-time cost (XL effort)
  • Import Updates: All imports need updating
  • Learning Curve: Team must learn new structure
  • Merge Conflicts: In-flight PRs will need rebasing

Mitigation

  • Use automated tools (e.g., ts-morph) to update imports
  • Migrate one domain/feature at a time
  • Create a migration checklist and track progress
  • Coordinate with team to minimize in-flight work during migration phases
  • Consider using feature flags to ship incrementally

Key Differences from Current Structure

Aspect Current Target
Frontend/Backend Mixed in src/ Separated in client/ and server/
Services Flat directory (75+ files) Grouped by domain
Components Flat directory (43+ files) Categorized (ui, layout, feedback, forms)
Types Monolithic types.ts Split by entity in shared/types/
Features UI-only Full vertical slice (UI + hooks + types)
Routes Separate from services Co-located in domain
Tests Co-located + tests/ Co-located + tests/ for fixtures
  • ADR-034 - Repository Pattern (affects domain structure)
  • ADR-035 - Service Layer (affects domain structure)
  • ADR-044 - Frontend Features (this ADR supersedes it)
  • ADR-045 - Test Fixtures (affects tests/ directory)