All projects

Multi-Tenant Client Feedback & Support Portal

A service-owned NestJS microservices platform with two Next.js frontends for multi-tenant customer feedback and support ticketing.

Overview

A service-owned microservices platform: an edge-only API gateway authenticates and rate-limits requests, derives tenant context from verified JWT claims, and forwards requests to the owning service. Five domain services — identity, tenant, ticket, feedback, and notification — each own their own data and Prisma schema.

Problem statement

Organizations need a way to run isolated, secure feedback and support workflows for their own customers without data leaking across tenants, while still scaling each domain independently.

Objectives

  • Enforce strict tenant isolation at the database and API layer
  • Decompose the domain into independently deployable services
  • Support real-time-ish, eventually-consistent projections between services

Key features

  • Subdomain-based tenant routing
  • JWT-verified, tenant-scoped API gateway with rate limiting and correlation IDs
  • Ticket lifecycle management with attachments and member projections
  • Feedback forms with local analytics projections
  • Event-driven notifications across services

Architecture

Two Next.js frontends
API gateway — edge-only, no database. Verifies JWT, rate-limits, derives tenant context.
  • identity-serviceown Prisma schema
  • tenant-serviceown Prisma schema
  • ticket-serviceown Prisma schema
  • feedback-serviceown Prisma schema
  • notification-serviceown Prisma schema
RabbitMQ — transactional outbox relays publish with confirms; consumers dedupe by inbox event ID.
Labels taken from the project notes; not a full deployment diagram.

Six services: api-gateway (edge-only, no database), identity-service, tenant-service, ticket-service, feedback-service, and notification-service — each owning its own Prisma schema. Domain mutations write transactional outbox records; RabbitMQ relays publish with confirms; consumers use unique inbox event IDs before acknowledging. Cross-service reads are served from local, eventually-consistent projections rather than cross-service SQL joins. Refresh tokens are issued as secure HttpOnly cookies; browser auth state is memory-only.

Challenges

  • Keeping tenant isolation airtight across five independently-owned services
  • Coordinating eventually-consistent projections without introducing stale or conflicting state
  • Securing WebSocket authentication without exposing JWTs in URL query parameters

Solutions

  • Derived tenant IDs only from verified JWT claims and rejected conflicting client-supplied tenant headers
  • Used a transactional outbox/inbox pattern with RabbitMQ for reliable, idempotent event delivery
  • Required WebSocket auth via handshake cookies or short-lived handshake exchanges instead of query-param JWTs