Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Codefied-CodePix/Karokar-backend/llms.txt

Use this file to discover all available pages before exploring further.

KaroKar Backend is architected as a domain-driven modular monolith: a single deployable NestJS application whose internals are partitioned into eight self-contained bounded contexts, each owning its own entities, services, repositories, and business rules. This approach was chosen deliberately over microservices — the engineering team is small, the business is growing fast, and distributing services too early would introduce distributed transaction complexity and operational overhead that the current scale does not justify. The modular structure means any domain can be extracted into an independent service later, without redesigning existing entities or breaking other domains.

Why a Modular Monolith? (ADR-001)

The decision to adopt a modular monolith was driven by the need for rapid delivery without sacrificing future architectural flexibility. The core trade-offs: Microservices were rejected because they introduce:
  • Distributed transactions and two-phase commit complexity
  • Network latency between every domain call
  • Service discovery and infrastructure overhead
  • Substantially higher operational costs at small scale
Modular monolith was chosen because it delivers:
  • Faster development and easier onboarding for a small team
  • Simple single-unit deployments with no orchestration layer
  • Strong domain boundaries enforced at the module level
  • A clear future extraction path — domains can become microservices as scale demands
  • Lower infrastructure costs for MVP and early growth phases
The architecture’s success criterion: new domains (approvals, KYC, maintenance, AI recommendations, dynamic pricing) can be added without modifying existing domains, and the system can scale to multiple business models while maintaining clear domain ownership.

The Eight Bounded Contexts

Each domain is a self-contained NestJS module registered in AppModule. Cross-domain communication happens exclusively through domain events — no domain imports a service or repository from another.

Identity

Users, auth, organization membership. Manages users, roles, authentication flows, JWT issuance, permission resolution, and organization structures. Multi-tenancy is anchored here: every other domain carries the organizationId originating from this context.

Fleet

Vehicles, availability, maintenance, suspension. Owns vehicle records, document attachments, real-time availability windows, vehicle status transitions, and suspension workflows. Fleet state is changed only through Fleet domain services.

Booking

Booking lifecycle and state machine. Handles booking requests, drives the full booking state machine (pending → confirmed → active → completed / cancelled), and manages reservation periods. State transitions are owned exclusively by Booking domain services.

Assignment

Employee-vehicle assignment lifecycle. Models corporate employee-to-vehicle allocation and the multi-step handover lifecycle for long-term vehicle assignments, distinct from short-term bookings.

Verification

Generic verification framework for any entity. Provides a reusable verification request / status-tracking mechanism applicable to users, vehicles, or organizations. Designed with hooks for future NADRA and government API integrations.

Notification

Event-driven notifications (email, SMS, push). Listens to domain events emitted by all other contexts and dispatches notifications across email, SMS, and push channels. Has zero awareness of business logic — it only reacts to events.

Audit

Compliance records from domain events. Subscribes to domain events across all contexts and writes immutable audit / change-history records. Business domains must never call audit services directly — all audit records arise from events.

Administration

Vendor and corporate approval workflows. Manages vendor onboarding approval, corporate organization approval, and platform-wide moderation. Surfaces administration actions to Platform Administrators.

Module File Structure

Every domain module follows the same layout, enforced by the NestJS conventions rule (01-nestjs-conventions.mdc):
src/<domain>/
├── <domain>.module.ts
├── domain/
│   ├── entities/          # TypeORM entity classes
│   ├── value-objects/     # Immutable value types
│   └── events/            # Domain event classes
├── application/
│   ├── services/          # Business logic and state machines
│   ├── dtos/              # Request/response DTOs (class-validator)
│   └── use-cases/         # One class per use-case (optional)
├── infrastructure/
│   ├── repositories/      # TypeORM repository implementations
│   └── persistence/       # TypeORM entity-to-domain mappers
└── interfaces/
    └── http/
        ├── controllers/   # Thin NestJS controllers — delegate to services
        └── guards/        # Route-level guards

Naming Conventions

File typeConventionExample
Entity<domain>.entity.tsbooking.entity.ts
Service<domain>.service.tsbooking.service.ts
Repository<domain>.repository.tsbooking.repository.ts
DTOcreate-<domain>.dto.tscreate-booking.dto.ts
Event<domain>-<past-tense>.event.tsbooking-approved.event.ts
Controller<domain>.controller.tsbooking.controller.ts
Module<domain>.module.tsbooking.module.ts

Entity Baseline

All entities extend a shared BaseEntity that provides:
  • id@PrimaryGeneratedColumn('uuid') (UUID v4)
  • createdAt — auto-set timestamp
  • updatedAt — auto-updated timestamp
  • organizationId — tenant isolation anchor (required on every entity)
Database column names are always snake_case via @Column({ name: 'column_name' }).

Non-Negotiable Architectural Rules

These rules are enforced at code-review time and captured in 00-architecture.mdc. Violations are considered blocking defects.
These constraints exist to preserve domain boundaries and tenant isolation. Bypassing them — even “just this once” — degrades the architecture’s extraction path and creates hidden coupling that is expensive to unwind later.
  1. Domains MUST NOT import services or repositories from other domains directly. Any cross-domain data need must be satisfied by domain events or a read-side query within the consuming domain’s own infrastructure layer.
  2. Cross-domain communication MUST happen through domain events only. Use @nestjs/event-emitter with wildcard event names (e.g. booking.confirmed, fleet.vehicle.suspended). The EventEmitter is configured with wildcard: true and delimiter ..
  3. Every entity MUST carry an organizationId for tenant isolation. No entity may exist without a link to its owning organization. This is the enforcement point for multi-tenancy.
  4. Business state transitions MUST only occur through domain services. Controllers, scheduled jobs, and event handlers must never mutate business state directly. They delegate to the appropriate domain service.
  5. Authorization MUST be permission-based. Guards check granular permission strings such as booking.create or employee.manage. Checking user.role === 'CorporateAdmin' directly is prohibited.
  6. Audit records MUST be generated by the Audit Domain subscribing to events. Business domains must never call audit services directly. The Audit domain is the single consumer responsible for compliance records.

Domain Event Bus

The current event infrastructure uses @nestjs/event-emitter running in-process:
EventEmitterModule.forRoot({
  wildcard: true,
  delimiter: '.',
})
This is intentionally simple for the MVP phase. The planned evolution path:
PhaseMechanismNotes
CurrentIn-process EventEmitterZero infrastructure, synchronous
NextTransactional Outbox patternEvents persisted atomically with business data
FutureKafka or AWS SNS/SQSEnables independent service extraction
Because the EventEmitter is in-process, there is no message broker to operate during local development. Event subscribers in the Notification and Audit domains react synchronously within the same request lifecycle. When the Outbox pattern is introduced, this behaviour will change — event handlers will execute asynchronously after the transaction commits.

See Also

  • Introduction — participant model and tech stack overview
  • Quickstart — run the server locally in under five minutes

Build docs developers (and LLMs) love