Skip to content

About

No description, website, or topics provided.

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

13 Commits

Folders and files

Repository files navigation

FitBinder

License: MIT Node >=24 TypeScript 5.7 NestJS 11 Angular 22 PostgreSQL + TypeORM PRs welcome

A NestJS microservices monorepo for managing personal trainers, trainees, training packages, appointments, and trainee health history — built around Bounded Context (BC) boundaries, one database per context, and inter-service communication over RabbitMQ / TCP.

Each Bounded Context is an independently deployable NestJS application that owns its data exclusively; nothing reaches across a context boundary except through a well-defined @MessagePattern contract. This keeps the system safe to scale, split, or hand off to separate teams without a rewrite.


Table of Contents


Architecture

graph TB
    subgraph Client
        WEB["Angular Web App<br/>:4200"]
    end

    subgraph Services["HTTP APIs"]
        AUTH["auth<br/>:3001"]
        IAM["iam<br/>:3006"]
        TRAINER["trainer-bc<br/>:3002"]
        TRAINEE["trainee-bc<br/>:3003"]
        PACKAGE["package-bc<br/>:3004"]
        APPT["appointment-bc<br/>:3005"]
    end

    MQ[["RabbitMQ<br/>(inter-service transport)"]]
    REDIS[("Redis<br/>sessions + CSRF")]

    WEB --> AUTH & IAM & TRAINER & TRAINEE & PACKAGE & APPT

    AUTH -- "validate credentials" --> IAM
    AUTH --> REDIS

    TRAINER -. TCP/RMQ .-> IAM
    TRAINEE -. TCP/RMQ .-> IAM
    PACKAGE -. TCP/RMQ .-> IAM
    APPT -. TCP/RMQ .-> IAM

    AUTH & IAM & TRAINER & TRAINEE & PACKAGE & APPT -.-> MQ

    IAM --> DBIAM[("fit_binder_iam")]
    TRAINER --> DBTRAINER[("fit_binder_trainer")]
    TRAINEE --> DBTRAINEE[("fit_binder_trainee")]
    PACKAGE --> DBPACKAGE[("fit_binder_package")]
    APPT --> DBAPPT[("fit_binder_appointment")]
Loading

Key design decisions

  • Database-per-context — every BC owns a physical PostgreSQL database; there are no cross-database joins. Cross-context data is referenced by UUID and resolved over the network.
  • auth is stateless and DB-less — it holds no data of its own. It issues/validates JWTs, manages CSRF, and keeps session state in Redis; all identity data (users, password hashes) lives in iam, reached over TCP.
  • Pluggable transport — services talk to each other over RabbitMQ by default (TRANSPORT=rmq), with a TCP fallback (TRANSPORT=tcp) for local development without a broker.
  • JSON:API responses — every HTTP response is normalized by a global TransformInterceptor.
  • Shared contracts, not shared runtime — libs/contracts exposes framework-agnostic TypeScript types consumed by both the Angular frontend and the NestJS backend, while libs/common (which pulls in Nest/TypeORM) never leaks into the browser bundle.

Tech Stack

Layer Technology
Backend framework NestJS 11 on Fastify
Language TypeScript 5.7
Database / ORM PostgreSQL + TypeORM (one schema per Bounded Context)
Inter-service comm. RabbitMQ (amqp-connection-manager) with TCP fallback via @nestjs/microservices
Cache / sessions Redis (ioredis) — auth sessions, CSRF tokens
Auth JWT (@nestjs/jwt, passport-jwt), bcrypt password hashing
Validation class-validator / class-transformer
API docs OpenAPI via @nestjs/swagger, rendered with Scalar + Swagger UI
Security hardening @fastify/helmet, @fastify/rate-limit, CORS, CSRF
Frontend Angular 22 (standalone APIs) + Tailwind CSS 4
Testing Jest (backend), Vitest (frontend)
Monorepo tooling Nx + pnpm workspaces (backend), Nest CLI monorepo mode, ESLint 9 (flat config), Prettier, dependency-cruiser
Process management PM2 (ecosystem.config.js) for production

Monorepo Structure

fit-binder/
├── apps/
│   ├── auth/               # Stateless auth: JWT, sessions (Redis), CSRF   HTTP :3001 | TCP :4001
│   │   ├── package.json     # Scoped runtime deps for this app
│   │   ├── project.json     # Nx targets: build / serve / test / test:e2e
│   │   └── src/
│   ├── iam/                 # Identity system-of-record: users, credentials HTTP :3006 | TCP :4006
│   ├── trainer-bc/         # Trainer Bounded Context                       HTTP :3002 | TCP :4002
│   ├── trainee-bc/         # Trainee + health history Bounded Context      HTTP :3003 | TCP :4003
│   ├── package-bc/         # Training package + purchase Bounded Context   HTTP :3004 | TCP :4004
│   ├── appointment-bc/     # Appointment + session Bounded Context         HTTP :3005 | TCP :4005
│   └── web/                # Angular 22 standalone frontend (NOT an Nx project) HTTP :4200
│
├── libs/
│   ├── common/              # BaseEntity, base CRUD operations, decorators, guards (backend only)
│   ├── config/               # Centralized, validated configuration (backend only)
│   ├── database/            # TypeORM data sources + migrations (backend only)
│   └── contracts/           # Framework-agnostic TS types shared by web + backend
│   (each lib also carries its own package.json + project.json)
│
├── nx.json                   # Nx workspace config: target defaults, plugins, caching
├── pnpm-workspace.yaml        # apps/* + libs/* as pnpm workspace packages (apps/web excluded)
├── ecosystem.config.js      # PM2 process definitions for production
├── CLAUDE.md                 # AI/agent contribution guide (architecture + conventions)
└── CODE_OF_CONDUCT.md        # Full coding conventions reference

Workspace Management

FitBinder's backend (apps/* except web, plus libs/*) is an Nx workspace on top of a pnpm workspace — the same pattern used across our other NestJS monorepos. Every backend app and lib is its own workspace package with its own package.json (scoped dependencies) and project.json (Nx project targets), instead of one monolithic root manifest:

apps/trainer-bc/
├── package.json     # "trainer-bc" — only the deps this BC actually imports, plus @lib/* as workspace:*
├── project.json     # Nx targets: build, serve, test, test:e2e (lint is auto-inferred)
└── src/
  • pnpm-workspace.yaml declares apps/* + libs/* as workspace packages, explicitly excluding apps/web (see below).
  • nx.json wires up @nx/eslint and @nx/jest, which auto-infer lint and test targets for every project from the existing root ESLint flat config and each project's jest.config.js — no per-project ESLint config duplication needed.
  • build and serve targets are thin nx:run-commands wrappers around the existing Nest CLI monorepo build (nest-cli.json, "monorepo": true — unchanged), so Nx adds task orchestration, dependency-aware ordering, and local caching on top of the exact same nest build <app> / nest start <app> --watch commands used before.
  • Cross-project awareness means nx run-many --target=build --all builds every BC (in the right order) in one command, and nx affected --target=test only re-tests what actually changed — this is what makes the workspace "ready to scale": adding BC #7 means adding one more apps/<name> project, not touching a shared config file every other team depends on.
pnpm exec nx build trainer-bc          # build a single project
pnpm exec nx run-many --target=build --all   # build everything, respecting the dependency graph
pnpm run affected:test                 # only test what changed since the base branch
pnpm run graph                         # visualize the project graph

All the original pnpm start:dev:<app> / pnpm build:<app> scripts still work exactly as before — they call the same underlying Nest CLI commands the Nx targets wrap, so nothing about day-to-day development changes unless you opt into the nx … commands above.

apps/web is deliberately excluded from the Nx/pnpm workspace graph. There is no project.json, no shared root tsconfig reference — it is a plain, default Angular CLI app with its own package.json, angular.json, and dependency tree, wired in only through convenience scripts at the root (pnpm start:web → pnpm --dir apps/web start). This keeps the Angular toolchain (and its release cadence) fully decoupled from the NestJS/TypeORM backend; the only thing the two sides share is libs/contracts, consumed by apps/web via a TypeScript path mapping in apps/web/tsconfig.json, not a package dependency.


Bounded Contexts

Service Owns HTTP TCP Database
auth Login/refresh/logout, CSRF (no DB — Redis only) 3001 4001 —
iam Users, credentials, (planned) roles/permissions 3006 4006 fit_binder_iam
trainer-bc Trainers 3002 4002 fit_binder_trainer
trainee-bc Trainees + body composition / health records 3003 4003 fit_binder_trainee
package-bc Training packages + purchases 3004 4004 fit_binder_package
appointment-bc Appointments + training sessions 3005 4005 fit_binder_appointment

Every BC's data is private — no service reads another BC's database directly. Cross-BC lookups go through @MessagePattern handlers exposed on that BC's TCP/RabbitMQ client, injected via the AppMicroservice enum from @lib/common.


Getting Started

Prerequisites

  • Node.js ≥ 24 (see .nvmrc → nvm use)
  • pnpm
  • PostgreSQL (one database per Bounded Context)
  • RabbitMQ (default inter-service transport) — or set TRANSPORT=tcp to run without one
  • Redis (required by auth for sessions/CSRF)

Installation

# Install root (backend) dependencies
pnpm install

# Install frontend dependencies
pnpm --dir apps/web install

Environment

cp .env.example .env

Fill in database credentials, RabbitMQ/Redis connection details, and JWT secrets. Each service reads its own *_DB_*, *_MODULE_HTTP_PORT, and *_MODULE_MICROSERVICE_PORT variables — see .env.example for the full reference.


Running the Backend

Development (watch mode)

# Run a single service
pnpm start:dev:auth
pnpm start:dev:iam
pnpm start:dev:trainer-bc
pnpm start:dev:trainee-bc
pnpm start:dev:package-bc
pnpm start:dev:appointment-bc

# Run every backend service concurrently
pnpm start:dev:all

Build

# Build a single service
pnpm build:auth        # or build:iam / build:trainer-bc / build:trainee-bc / ...

# Build all services
pnpm build:all

Running the Frontend

# Angular dev server on :4200
pnpm start:web

# equivalently, from apps/web directly
pnpm --dir apps/web start

# Production build
pnpm build:web

# Unit tests (Vitest)
pnpm test:web

The Angular dev server proxies /api/<bc>/* to each BC's HTTP port automatically via apps/web/proxy.conf.json — no manual CORS setup needed in local development.


Production Deployment

Backend services are built with nest build and run under PM2 using ecosystem.config.js, which defines one process per BC plus auth, each loading its config from .env:

pnpm build:all
pm2 start ecosystem.config.js

Or run a single compiled service directly:

node dist/apps/auth/main.js

API Documentation

Every service exposes OpenAPI docs at two endpoints once it is running:

Service Scalar (recommended) Swagger UI
auth http://localhost:3001/auth/v1/api-docs /auth/v1/classic-docs
iam http://localhost:3006/iam/v1/api-docs /iam/v1/classic-docs
trainer-bc http://localhost:3002/trainer/v1/api-docs /trainer/v1/classic-docs
trainee-bc http://localhost:3003/trainee/v1/api-docs /trainee/v1/classic-docs
package-bc http://localhost:3004/package/v1/api-docs /package/v1/classic-docs
appointment-bc http://localhost:3005/appointment/v1/api-docs /appointment/v1/classic-docs

Database & Migrations

Each BC owns a dedicated PostgreSQL database. Migrations always go through the TypeORM CLI — timestamps are never hand-written, to keep ordering correct.

# Generate a migration (example: trainer-bc)
pnpm migration:generate:trainer --name=AddTrainerStatus

# Run migrations
pnpm migration:run:iam
pnpm migration:run:trainer
pnpm migration:run:trainee
pnpm migration:run:package
pnpm migration:run:appointment

# Run all pending migrations across every BC
pnpm migration:run:all

# Revert the last migration for a BC
pnpm migration:revert:trainer
BC Database Env prefix
iam fit_binder_iam IAM_DB_*
trainer-bc fit_binder_trainer TRAINER_DB_*
trainee-bc fit_binder_trainee TRAINEE_DB_*
package-bc fit_binder_package PACKAGE_DB_*
appointment-bc fit_binder_appointment APPOINTMENT_DB_*

auth has no row in this table — it is intentionally database-less; its state lives in Redis.


Testing & Code Quality

# Unit tests (backend, Jest)
pnpm test
pnpm test:watch
pnpm test:cov

# Lint (auto-fix, strict — see eslint.config.mjs)
pnpm lint

# Format
pnpm format

The project also ships a dependency-cruiser configuration to enforce architectural boundaries (e.g. no BC importing another BC's internals) and a .coderabbit.yaml for automated PR review.


Contributing

This codebase follows a strict, documented set of conventions so that every Bounded Context stays structurally identical and easy to onboard into:

  • CLAUDE.md — architecture overview and AI/agent contribution guide.
  • CODE_OF_CONDUCT.md — the full convention reference: naming, entity/DTO/service/controller layering, Swagger documentation rules, and file structure.

Adding a new entity or CRUD resource? Always scaffold it with the implement-entity workflow rather than hand-rolling the layers:

/implement-entity

Pull requests are welcome — please make sure pnpm lint and pnpm test pass before submitting, and follow the conventions above so reviews stay fast.


License

Licensed under the MIT License.

About

No description, website, or topics provided.

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages