Professional Stack for a Nodejs based Website in microservice architecture. Includes all tools needed for monitoring.
Find a file
2026-06-15 13:38:51 +02:00
README.md Dateien nach „/“ hochladen 2026-06-15 13:38:51 +02:00

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

  1. Contact: fill the form on /kontaktcontact.received lands in RabbitMQ → nestjs-notify logs two German mails (office + sender copy) as JSON (jsonTransport, nothing actually leaves the machine).
  2. Booking: /termine shows ~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 → activity event → refetch). Double-booking is impossible: the claim is one atomic findOneAndUpdate.
  3. Events: /veranstaltungen has two seeded German demo events with capacity enforcement (atomic $expr update, duplicate registration blocked by a unique eventId+email index).
  4. Staff: log in on /admin; nestjs-auth issues a 15-minute RS256 JWT, nestjs-admin validates 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 and nest start --watch pick 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, then docker compose restart <service> — the entrypoint re-installs because the lockfile changed (or docker compose exec <service> npm install <pkg>).
  • Monorepo note: the repo is an npm-workspaces/turborepo layout (packages/contracts, logger, …), but each app's package.json is deliberately self-contained, because the runtime npm install runs 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 -d re-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 role claim is already in the token).
  • The contact form has only toy spam scoring; add Turnstile/hCaptcha.
  • The backend network is not internal: true because runtime npm install needs egress. Once you build production images (the commented COPY + npm ci + build + start:prod block in every Containerfile), flip it to internal: 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-client metrics. 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):

  • Impressum and Datenschutzerklärung are 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}