Architecture
Overview
Housr API v2 is a Laravel 12 application using the nwidart/laravel-modules package to organise code into self-contained domain modules. Each module is a mini-Laravel app with its own controllers, models, services, routes, migrations, and tests.
Directory Structure
Housr-API2/
├── app/ # Shared application code
│ ├── Auth/Guards/ # Custom auth guards (LegacyJwtGuard)
│ ├── Http/
│ │ ├── Controllers/ # Root-level controllers (Contact, DemoUser, Outbound)
│ │ ├── Middleware/ # Global middleware
│ │ └── Requests/ # Root-level form requests
│ ├── Models/ # Shared models (User, FeatureFlag, GlobalVariable, etc.)
│ ├── Providers/ # Service providers (App, LegacyAuth)
│ ├── Services/ # Shared services (Firebase, User, Notification, Contact)
│ └── Traits/ # Shared traits (IsHousrTeamMember)
├── Modules/ # Domain modules (see below)
├── config/ # Laravel + module configuration
├── database/ # Root migrations, factories, seeders
├── routes/ # Root API routes (contact, demo user, outbound)
├── tests/ # Root-level tests + base test classes
├── docker/ # Docker build context
├── deploy/ # Nginx, Supervisor, PHP-FPM config
└── modules_statuses.json # Module enable/disable flagsModule Structure
Each module under Modules/ follows this layout:
Modules/ModuleName/
├── app/
│ ├── Http/
│ │ ├── Controllers/ # Module controllers
│ │ ├── Requests/ # FormRequest validation classes
│ │ ├── Resources/ # API Resources (JSON transformation)
│ │ ├── Middleware/ # Module-specific middleware
│ │ └── Integrations/ # Saloon connectors (e.g., Oakmans)
│ ├── Models/ # Eloquent models
│ ├── Services/ # Business logic services
│ ├── Actions/ # Single-purpose action classes
│ ├── Enums/ # PHP enums
│ ├── Exceptions/ # Custom exceptions
│ ├── Filters/ # Spatie Query Builder custom filters
│ ├── Sorts/ # Spatie Query Builder custom sorts
│ ├── Queries/ # Complex query builders
│ ├── Factories/ # Non-Eloquent factory classes
│ ├── Policies/ # Authorization policies
│ ├── Traits/ # Module-specific traits
│ ├── Interfaces/ # Contracts/interfaces
│ ├── Contracts/ # Alternative name for interfaces
│ ├── Providers/
│ │ ├── ModuleServiceProvider.php
│ │ ├── RouteServiceProvider.php
│ │ └── EventServiceProvider.php
│ ├── Mail/ # Mailable classes
│ ├── Jobs/ # Queue jobs
│ ├── Console/Commands/ # Artisan commands
│ ├── Listeners/ # Event listeners
│ └── Widgets/ # Widget classes (Screens/Properties)
├── config/
│ └── config.php # Module configuration
├── database/
│ ├── migrations/ # Module migrations
│ ├── factories/ # Eloquent model factories
│ └── seeders/ # Database seeders
├── resources/views/ # Blade templates (emails, etc.)
├── routes/
│ └── api.php # Module API routes
└── tests/
├── Feature/ # Feature tests
└── Unit/ # Unit testsRequest Flow
The standard request lifecycle:
HTTP Request
→ Middleware (CORS, ForceJson, PropagateContext, Auth)
→ Route (module RouteServiceProvider)
→ Controller
→ FormRequest (validation)
→ Service (business logic)
→ Model (data access)
→ Resource (JSON transformation)
→ JSON ResponseKey Architectural Decisions
Service Layer Pattern
Controllers are thin — they accept a FormRequest, delegate to a Service class, and return a Resource. Services contain the business logic and are injected via constructor dependency injection.
Controller → FormRequest (validates) → Service (processes) → Resource (formats)Modules Own Their Routes
Each module has a RouteServiceProvider that registers its own routes with a prefix. For example:
- Properties: Routes prefixed with
/properties - Bills: Routes prefixed with
/api/bills - Events: Routes prefixed with
/events
The root routes/api.php handles only cross-cutting concerns (contact forms, demo users, health checks).
Shared vs Module Models
- Shared models (
app/Models/):User,FeatureFlag,GlobalVariable,University,PropertyOperator, etc. These represent tables from the shared v1 database. - Module models (
Modules/*/app/Models/): Domain-specific models owned by that module. For exampleHouse,V2Contract,Event,RideshareProfile.
Region Handling (US vs UK)
The config('app.USA') flag (from USA env var) toggles region-specific behaviour throughout the codebase:
- Different database query logic (UK uses MySQL 8 spatial features, US uses MySQL 5.7 compatible queries)
- Different features enabled/disabled (bills pricing, lease terms, building details)
- Different API error documentation URLs
Lazy Loading Prevention
Model::preventLazyLoading() is enabled in non-production environments to catch N+1 query issues during development.
Observability
The PropagateContextMiddleware attaches a correlation_id (generated per request) and trace_id (from X-Trace-ID header or generated) to Laravel’s Context. These propagate into all log entries for request tracing.
Strict Types
Architecture rules enforced via Pest:
- All
App\classes must usedeclare(strict_types=1) - No
die,dd, ordumpcalls - PHP and security presets are applied