Skip to content

Repository files navigation

PropFlow

CI Unit coverage Docs ADRs

TypeScript NestJS PostgreSQL RabbitMQ Apache Kafka Claude Docker Kubernetes

A property maintenance management platform built as a NestJS monorepo with a microservices, event-driven architecture — RabbitMQ for work distribution, Kafka for the audit stream, LLM-powered triage, a transactional outbox, JWT auth, and full test coverage from unit to full-stack e2e.

This repository doubles as a living study project: every architectural decision is documented as an ADR with its trade-offs, and each phase of the roadmap focuses on one production concern (messaging, observability, resilience, AI integration).

Domain

Property managers handle a constant stream of maintenance requests: a tenant reports a leaking tap, the request must be triaged, assigned to a contractor, tracked to completion, and everyone involved must be notified along the way. PropFlow models that flow:

  • Work Orders — the core aggregate: maintenance requests and their lifecycle (open → assigned → in_progress → completed), with async LLM triage (category + urgency).
  • Properties — buildings and units that work orders belong to.
  • Notifications — reacts to domain events (e.g. work-order.created) asynchronously.
  • Audit — projects the Kafka event stream into a queryable activity feed.

Architecture

flowchart LR
    Client([Client]) --> GW[API Gateway]
    GW --> WO[Work Orders Service]
    GW --> PR[Properties Service]
    GW --> AU[Audit Service]
    WO -->|domain events| MQ[(RabbitMQ)]
    WO -->|audit stream| KF[(Kafka)]
    MQ -->|created events| WO
    WO -.->|triage| AI[Anthropic API]
    MQ --> NT[Notifications Service]
    KF --> AU
    WO --> DB1[(PostgreSQL)]
    PR --> DB2[(PostgreSQL)]
    AU --> DB3[(PostgreSQL)]
Loading

Each service owns its data (database-per-service). Services communicate synchronously through the gateway for queries and asynchronously through domain events for side effects — no service calls another service's database directly.

Tech stack

Layer Stack Why / decisions
Runtime & language Node.js TypeScript Strict mode, monorepo path aliases
Framework NestJS Monorepo mode, one app per service — ADR-0001
Data PostgreSQL TypeORM Database-per-service, reviewed migrations — ADR-0003
Messaging RabbitMQ Apache Kafka Queue for fan-out, log for replayable history — ADR-0002, ADR-0004
Reliability Transactional outbox Idempotent consumers Dual-write closed, at-least-once end to end — ADR-0007
AI Claude Async LLM triage, structured outputs, best-effort — ADR-0006
Auth JWT Verified once at the edge, roles per route, actor in every event — ADR-0008
Observability Prometheus Pino RED metrics, JSON logs, correlation ids via ALS — ADR-0005
Testing Jest Supertest 70+ unit tests, e2e per service, full-stack composition suite — coverage report
Infra & CI Docker Kubernetes GitHub Actions Compose for dev, manifests + NetworkPolicies in k8s/
API & docs OpenAPI AsyncAPI Mermaid Material for MkDocs Swagger at /api/docs, AsyncAPI rendered, hosted docs site

Getting started

# infrastructure (PostgreSQL + RabbitMQ + Kafka)
docker compose up -d

# install & run (one terminal per service)
npm install
npm run start:dev work-orders-service    # :3001
npm run start:dev notifications-service  # :3002
npm run start:dev properties-service     # :3003
npm run start:dev audit-service          # :3004
npm run start:dev api-gateway            # :3000 — public entry point (/api/*)

# tests
npm test          # unit
npm run test:e2e  # end-to-end

RabbitMQ management UI: http://localhost:15672 (propflow / propflow).

Every business route requires a JWT. Log in with a demo user (see .env.example to customize):

curl -s -X POST localhost:3000/api/auth/login \
  -H 'content-type: application/json' \
  -d '{"email":"manager@propflow.dev","password":"propflow"}'
# -> { "accessToken": "...", "role": "manager" }  — send as Authorization: Bearer <token>

Live demo

With the stack up (and ANTHROPIC_API_KEY set for the work-orders service), one command drives the whole platform through the gateway and narrates each step:

npm run demo

It authenticates, opens a work order whose tenant understates the priority as medium, and shows the LLM re-evaluating it to emergency — then runs the state machine and prints the audit feed with every event attributed (human vs. system (AI)). It's the fastest way to see auth, the state machine, the transactional outbox, both brokers, the audit projection and AI triage working end to end.

PropFlow live demo

Recorded with VHS from a real run — regenerate with vhs scripts/demo.tape.

Sample run (text)
PropFlow — live demo  (http://localhost:3000/api)
    OK  gateway healthy

==> 1. Authenticate (role: manager)
    OK  JWT issued (eyJhbGciOiJIUzI1NiIs...)
    OK  request without a token -> 401 (auth is enforced)

==> 2. Register a property
    OK  propertyId c6e716a1-425e-4e8f-ac01-683e5a0418db

==> 3. Open a work order — tenant reports priority: medium
    "strong smell of gas in the kitchen and the boiler is dead in winter"
    OK  workOrderId df0a1cd0-5255-4efb-836e-897fd02b0d15  (returns immediately — triage is async, off the write path)

==> 4. AI triage (LLM -> outbox -> Kafka), waiting...
    OK  classified in ~4s
    category   hvac
    urgency    emergency  (the LLM raised it from the tenant's "medium")
    reasoning  A strong gas smell poses an active danger of fire or explosion, and combined with no heat in winter this is a life-safety emergency regardless of the tenant's stated priority.

==> 5. Lifecycle transitions (guarded by the state machine)
    OK  assign -> 200
    OK  in_progress -> 200
    OK  invalid in_progress->open -> 409 (rejected, as it should be)

==> 6. Activity feed — audit projection of the Kafka log
    work-order.started     by manager@propflow.dev
    work-order.assigned    by manager@propflow.dev
    work-order.triaged     by system (AI)
    work-order.created     by manager@propflow.dev

==> What just happened
    1 request -> state + event committed atomically (outbox), never lost
    outbox relay -> RabbitMQ (notifications) + Kafka (audit), at-least-once
    AI triage ran async and re-evaluated the priority, attributed as 'system'
    every action is auditable end to end, with who did it

done.

Roadmap

Each phase is a self-contained increment with tests and documentation.

  • Phase 0 — Foundations: monorepo scaffold, Docker Compose (PostgreSQL + RabbitMQ), CI, ADR structure
  • Phase 1 — Work Orders service: REST API, PostgreSQL + data modelling, validation, unit + e2e tests
  • Phase 2 — Event-driven core: domain events over RabbitMQ, Notifications consumer, retries + dead-letter queue
  • Phase 3 — Properties service + API Gateway: service composition, inter-service communication patterns
  • Phase 4 — Observability: structured logging, correlation ids, Prometheus metrics, liveness/readiness probes
  • Phase 5 — Kafka: event streaming for an audit/activity feed; RabbitMQ vs Kafka in practice
  • Phase 6 — AI integration: LLM-powered triage of maintenance requests (urgency + category classification)
  • Phase 7 — Production hardening: outbox pattern, idempotent consumers, Kubernetes manifests
  • Phase 8 — Authentication & authorization: JWT at the edge, role-based routes, identity propagated into the audit trail

Documentation

Hosted docs: mhayk.github.io/propflow — same content, with rendered diagrams, navigation and search.

  • Services map — each service's mission, its world in a diagram, and its explicit non-responsibilities
  • Design patterns — catalog of the patterns used (architectural, messaging, application) and the ones deliberately not used
  • Running on GCP — a deployment study mapping the stack onto Google Cloud (GKE, Cloud SQL, Pub/Sub, Managed Kafka, Claude on Vertex AI) and how little code moves
  • API reference — every endpoint with its required role; interactive OpenAPI (Scalar) at /api/docs when running, rendered statically here
  • Postman collection — import and call every endpoint; login auto-saves the token, create-requests chain the ids
  • Event catalog — the async contract: envelope, every event with producers/consumers, broker topology, delivery guarantees; formal AsyncAPI spec validated in CI and rendered on the docs site
  • Architecture Decision Records — every significant decision and its trade-offs
  • Sequence flows — how actors and services interact, flow by flow (auth, write path, outbox relay, event fan-out, AI triage, retries/DLQ, composition, activity feed)
  • Study notes — deep dives on the concepts each phase exercises
  • Known limitations & planned work — the deliberate gaps and what would close each (technician/assignee registry, refresh tokens, tracing, …)
  • Unit coverage report — line-by-line, republished on every push; the badge above tracks it. Unit scope only: the gateway's HTTP surface is exercised by the e2e suites, which don't count here

About

Property maintenance platform — NestJS microservices, event-driven architecture (RabbitMQ/Kafka), TypeScript, PostgreSQL. Every decision documented as an ADR.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages