Core Concepts › Architecture

Architecture

#The four building blocks

BlockRole in NestLaravelWhere
NxMonorepo & orchestration: project graph, cached/affected test/lint/build, one command surfacenx.json, apps/*/project.json
LaravelThe application runtime. The gateway and every microservice are Laravel appsapps/api, apps/<name>-service
KafkaAsynchronous integration between services (domain events). Services never read each other's databasespackages/laravel-kafka
CLIThe framework layer developers touch: scaffold, generate, develop, upgradepackages/cli (nestlaravel)

#What about NestJS?

The repository this framework grew out of describes its gateway as "NestJS-style", but it contains no NestJS code: the gateway is Laravel (apps/api), and the tooling layer is plain Node.js. That was audited, kept, and made explicit rather than adding NestJS for its own sake:

The "NestJS-style" ideas that are implemented are the module structure (Domain / Application / Infrastructure / Presentation), dependency-injected contracts, and a single gateway in front of the services. If you later want a NestJS BFF for a specific frontend, it plugs in as another consumer of the gateway (or as an Nx app) — nothing here prevents it.

#Interfaces

PUBLIC              apps/api                Sanctum tokens, throttling, CORS allow-list, security headers
INTERNAL            apps/<name>-service     no published ports; HMAC-signed gateway calls only
SERVICE-TO-SERVICE  gateway → service       HTTP + HMAC (method, path+query, body hash, user, tenant, ts, nonce)
                    service → service       Kafka events (envelope below) — never direct DB access
ADMIN               php artisan / Kafka UI  not internet-facing; tenancy bypass is explicit (`withoutTenancy`)

#Request flow

  1. Client calls POST /api/v1/orders/… on the gateway.
  2. Gateway: auth:sanctum → throttle:api → path sanitisation (no .., control chars, encoded separators).
  3. Gateway signs the call (X-Gateway-Timestamp/Nonce/User/Tenant/Signature) with that service's own secret and forwards it; redirects are not followed; the user's bearer token is not forwarded.
  4. Service middleware VerifyGatewaySignature verifies freshness (60 s), one-time nonce, and HMAC; otherwise 401. With no secret configured it answers 503 (fail closed).
  5. The service runs its Action, writes to its own database, and publishes domain events through the outbox (same DB transaction).
  6. messaging:outbox-publish --daemon ships events to Kafka after broker confirmation; other services consume, deduplicate, and react.

#Inside a service

app/Modules/<Name>/
  Domain/          entities, value objects, contracts, domain events   (no framework dependencies)
  Application/     Actions, DTOs, queries                              (controllers call Actions only)
  Infrastructure/  Eloquent repositories, Kafka handlers, providers
  Presentation/    routes, controllers, requests, resources

Cross-module access uses contracts or events, never another module's Eloquent models.

#The event contract

{
  class="tk-s">"event_id": class="tk-s">"uuid",            class="tk-s">"event_type": class="tk-s">"orders.order.created",
  class="tk-s">"version": 1,                  class="tk-s">"occurred_at": class="tk-s">"2026-09-30T00:00:00+00:00",
  class="tk-s">"source": class="tk-s">"orders-service",    class="tk-s">"producer": class="tk-s">"orders-service",
  class="tk-s">"aggregate_id": class="tk-s">"1001",        class="tk-s">"aggregate_type": class="tk-s">"order",
  class="tk-s">"correlation_id": class="tk-s">"uuid",      class="tk-s">"causation_id": null,
  class="tk-s">"tenant_id": class="tk-s">"acme",           class="tk-s">"payload": {}
}

This is the framework's pre-existing envelope (richer than the minimal one in the brief) plus source (alias of producer, the name used by the wider ecosystem) and the optional tenant_id. Key = aggregate_id, so all events for one aggregate are ordered within a partition. See KAFKA.md.

#Data ownership

Every service has its own database and credentials (generate service creates a per-service Postgres role + database and a separate APP_KEY, signing secret and Kafka client/group ids). A service may keep read-models built from events; it may not query another service's tables.

#Dependency direction (enforced by review and tests/Architecture)

Presentation → Application → Domain ← Infrastructure. Nx tags (type:microservice, domain:<name>) are on every project so @nx/enforce-module-boundaries-style rules can be added when the workspace grows TypeScript libraries.

#Known trade-offs / audit notes

Edit this page on GitHub