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
- identity-serviceown Prisma schema
- tenant-serviceown Prisma schema
- ticket-serviceown Prisma schema
- feedback-serviceown Prisma schema
- notification-serviceown Prisma schema
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