|
|
||
|---|---|---|
| README.md | ||
Kanzlei Schreiber & Kollegen — Microservices Demo Stack
A runnable dummy implementation of lawfirm-microservices-stack.md: a German
law-firm website (Next.js) backed by NestJS microservices, MongoDB, RabbitMQ
and a full observability stack — all orchestrated with docker compose, in the
style of the three provided base-image examples (nodejs / nextjs / nestjs).
1. Architecture overview
Browser
│
▼
nginx (:80) ──────────────► nextjs-web (SSR site, port 3000)
│ /api/* (strip /api)
▼
kong (db-less, :8000)
├── /booking ──► nestjs-booking slots, appointments, calendar, events
├── /contact ──► nestjs-contact inquiries (spam scoring, GDPR TTL)
├── /auth ──► nestjs-auth staff login, RS256 JWT + JWKS
└── /admin ──► nestjs-admin dashboard (JWT-guarded via JWKS)
│ /socket.io/*
└─────────────────────────► socketio-gateway live activity broadcasts
booking / contact ──publish──► RabbitMQ (topic exchange lawfirm.events)
├──► nestjs-notify dummy e-mails (nodemailer jsonTransport)
└──► socketio-gateway 'activity' event to all browsers
Data layer: mongodb (one DB per service) · redis · elasticsearch · minio · vault (dev mode)
Backups: mongodump sidecar, nightly, 14-day retention
Observability: prometheus · grafana · loki+promtail · tempo · uptime-kuma
Compose layout follows the md: compose.yml at the root includes
infra/compose.app.yml and infra/compose.observability.yml.
Base-image pattern (from your three examples)
images/nodejs, images/nextjs, images/nestjs are compose services that
build an image and exit immediately (entrypoint: ["true"], restart: "no").
Every app service references them via
build:
additional_contexts:
nestjs: service:nestjs
and starts FROM nestjs in its own Containerfile. The shared entrypoint.sh
runs npm install when node_modules is missing, validates NODE_ENV
(development | production, otherwise exit 5) and then exec "$@".
In dev, source is bind-mounted to /src with a named volume for
node_modules; the watch-mode CMD gives hot reload everywhere.
2. How to run
Prerequisites: Docker Engine ≥ 25 with the compose plugin, ~6 GB free RAM (elasticsearch + the observability stack are the heavy part), ports 80 free.
cp .env.example .env # adjust passwords if you like
docker compose up -d --build
The first start is slow (several minutes): every app container runs
npm install at boot (that's the base-image pattern). Watch progress with
docker compose logs -f nextjs-web nestjs-booking. The stack is ready when
curl http://localhost/health returns 200 and the site renders.
| URL | What |
|---|---|
| http://localhost | Website (Kontakt, Termine, Kalender, Veranstaltungen) |
| http://localhost/admin | Staff login — credentials = STAFF_EMAIL / STAFF_PASSWORD from .env |
| http://127.0.0.1:15672 | RabbitMQ management (user/pass from .env) |
| http://127.0.0.1:3001 | Grafana (admin / GRAFANA_ADMIN_PASSWORD) — Loki/Prometheus/Tempo pre-provisioned |
| http://127.0.0.1:9090 | Prometheus |
| http://127.0.0.1:3002 | uptime-kuma (configure monitors on first visit) |
| http://127.0.0.1:9001 | MinIO console |
| http://127.0.0.1:8200 | Vault UI (dev mode, token dev-only-token) |
All admin UIs bind to 127.0.0.1 only; just port 80 is public.
Useful commands:
docker compose ps # health of everything
docker compose logs -f nestjs-notify # watch the dummy mails being "sent"
docker compose down # stop
docker compose down -v # stop + wipe data (mongo, node_modules, …)
Trying the flows
- Contact: fill the form on
/kontakt→contact.receivedlands in RabbitMQ →nestjs-notifylogs two German mails (office + sender copy) as JSON (jsonTransport, nothing actually leaves the machine). - Booking:
/termineshows ~40 seeded weekday slots. Open the page in two browsers, book in one — the slot disappears in the other within a second (rabbitmq → socketio-gateway →activityevent → refetch). Double-booking is impossible: the claim is one atomicfindOneAndUpdate. - Events:
/veranstaltungenhas two seeded German demo events with capacity enforcement (atomic$exprupdate, duplicate registration blocked by a uniqueeventId+emailindex). - Staff: log in on
/admin;nestjs-authissues a 15-minute RS256 JWT,nestjs-adminvalidates it against the JWKS endpoint and aggregates booking + contact data; every dashboard view is audit-logged.
3. Dev workflow
- Hot reload everywhere.
apps/<service>is bind-mounted into the container; Next.js dev server andnest start --watchpick up edits instantly. No rebuilds while developing. - Debugging: every NestJS service exposes the Node inspector on 9229
inside the container (
start:debug). Map it per service if needed. - Adding a dependency: edit the app's
package.json, thendocker compose restart <service>— the entrypoint re-installs because the lockfile changed (ordocker compose exec <service> npm install <pkg>). - Monorepo note: the repo is an npm-workspaces/turborepo layout
(
packages/contracts,logger, …), but each app'spackage.jsonis deliberately self-contained, because the runtimenpm installruns in the app folder, not the repo root. The shared packages document the intended structure; wiring them in for real means installing from the repo root in the base image or vendoring at build time. - Resetting demo data:
docker compose down -v && docker compose up -dre-seeds slots, events and the staff user.
4. Service communication
| Path | Mechanism |
|---|---|
| Browser → services | REST via nginx (/api prefix stripped) → kong (routing, rate limits: contact 10/min, auth 20/min, 10 MB body cap) |
| booking/contact → notify, gateway | RabbitMQ topic exchange lawfirm.events; routing keys booking.created, contact.received, event.registered; notify consumes a durable queue with a dead-letter exchange (lawfirm.dlx), gateway uses an exclusive fan-out queue |
| admin → booking/contact | Plain HTTP in this demo; the md specifies grpc here (commented in the code where the call happens) |
| auth → everyone | Stateless: services fetch /.well-known/jwks.json and verify RS256 signatures locally (jose remote JWKS with caching) |
| Live updates → browser | socketio-gateway re-broadcasts every rabbitmq message as a socket.io activity event; the booking page refetches on receipt |
Correlation IDs are injected at nginx (X-Request-Id) and logged by every
service via pino, so a request can be followed across logs in Loki.
5. Production considerations (read before deploying)
Security — must fix:
- Vault runs in dev mode (in-memory, known root token). Production needs real storage, auto-unseal, and services pulling secrets from Vault instead of env vars.
- nestjs-auth generates its RS256 keypair at boot — every restart
invalidates all tokens. Load the key from Vault and rotate via
kid. - Password hashing is Node's built-in scrypt; the md specifies argon2 (native dep, omitted to keep container installs fast).
- The admin login keeps the JWT in memory; production wants httpOnly cookies
- CSRF protection, refresh-token rotation, and casl role checks (the
roleclaim is already in the token).
- CSRF protection, refresh-token rotation, and casl role checks (the
- The contact form has only toy spam scoring; add Turnstile/hCaptcha.
- The
backendnetwork is notinternal: truebecause runtimenpm installneeds egress. Once you build production images (the commentedCOPY + npm ci + build + start:prodblock in every Containerfile), flip it tointernal: true. - TLS: an nginx 443 block with certbot paths is sketched in
infra/nginx/default.conf— enable it with real certificates.
Operational:
- Mail is
jsonTransport(logged, never sent). Point nodemailer at real SMTP, and add the md's bullmq reminder jobs (redis is already running). - elasticsearch, minio and redis are provisioned but mostly unused by the dummy code (the md uses them for back-office search, document storage and calendar caching/bullmq). Remove them if you don't need them yet — elasticsearch alone wants ~1 GB.
- Backups: the mongodump sidecar dumps nightly into a named volume with 14-day retention — copy those dumps off-host (e.g. to MinIO/S3).
- Prometheus only scrapes itself; the per-service targets are prepared but
commented until the services expose
prom-clientmetrics. Tracing (packages/telemetry) is a stub awaiting real OpenTelemetry wiring. - CI: the md plans GitHub Actions building production images; the commented production stage in every Containerfile is the starting point.
Legal (it's a German law-firm site):
ImpressumandDatenschutzerklärungare placeholders — they must be reviewed by a lawyer before going live (§ 5 DDG, DSGVO).- GDPR retention is only sketched (2-year TTL on inquiries); define real retention for appointments, registrations and mail logs, plus a process for data-subject requests.
6. Repo map
compose.yml root: includes the two infra compose files
images/{nodejs,nextjs,nestjs}/ base images (Containerfile, compose.yml, entrypoint.sh)
infra/compose.app.yml site, services, kong, nginx, data layer, backups
infra/compose.observability.yml prometheus, grafana, loki, promtail, tempo, uptime-kuma
infra/{nginx,kong,prometheus,loki,promtail,tempo,grafana}/ configs
apps/nextjs-web the website (App Router, Tailwind 4)
apps/nestjs-{auth,booking,contact,notify,admin} the five services
apps/socketio-gateway rabbitmq → socket.io bridge
packages/{contracts,logger,telemetry,tsconfig,eslint-config}