Overview
Project SaaS is a B2B, multi-tenant task and ticket management system designed to simulate the kind of backend architecture used in real production environments. The project prioritises correctness, tenant safety, resilience, and maintainability over flashy UI features.
The system allows multiple organisations to use the same platform while keeping their data isolated through tenant-aware design. It also supports JWT authentication, role-based access control, optimistic concurrency, background workers, outbox-driven event processing, and notification delivery (disabled on the live app to keep it free).
Login flow
Authentication is handled through a secure login flow using JWT-based access control. The frontend submits credentials, the API validates the user within the correct tenant context, and the system returns the token and user session data needed to access protected routes.
Login flow: user submits credentials → API validates company and user → JWT/token issued → frontend stores session → protected app access.
Post ticket flow
Ticket creation is designed around reliability. The client sends the new ticket request, the API validates the tenant and payload, stores the ticket in PostgreSQL, writes an outbox event in the same transaction, and then allows the background worker to process notifications or other side effects asynchronously.

Ticket flow: frontend submits ticket → API validates auth and tenant → database saves ticket + outbox event → worker processes event → notifications created.
Goals of the project
This project was built as a portfolio-grade backend system to demonstrate production-style engineering practices rather than only basic CRUD screens.
- Demonstrate real-world backend architecture
- Show multi-tenant system design and tenant isolation
- Apply SOLID principles in a structured codebase
- Support resilient workflows with retries and background processing
- Use the Outbox pattern for reliable event delivery
- Provide a strong portfolio project for backend-oriented roles
High-level architecture
The platform is split into clear system components so that each part has a focused responsibility and the architecture can grow without becoming tightly coupled.
Frontend
Next.js / React
Dashboards and protected application routes
Backend API
ASP.NET Core Web API
Authentication, tenancy, users, tickets, companies
Worker
ASP.NET Core BackgroundService
Outbox processing, retries, notifications, jobs
Database
PostgreSQL
Shared schema with OrganisationId tenant scopingCommunication between the frontend and backend uses REST over HTTP, while real-time notifications are designed around SignalR/WebSockets. Internal background side effects are handled asynchronously through the outbox pipeline.
Core architectural decisions
Multi-tenancy
Every tenant-scoped record contains an OrganisationId. This allows a single shared database schema while still enforcing strong isolation between companies. Tenant identity is derived from authenticated user context and propagated through the request pipeline.
Authentication and roles
The system uses JWT-based authentication and role-based authorisation. Admin users can manage company data, users, and ticket assignment, while regular users operate only within the permissions allowed by their role and tenant scope.
Reliability through Outbox
Ticket writes and domain events are stored atomically in the same database transaction. A background worker later processes unhandled outbox messages and performs side effects such as notification creation and downstream processing. This avoids losing events when a request succeeds but a side effect fails.
Optimistic concurrency
Ticket updates use row-version based optimistic concurrency. This prevents accidental overwrites when multiple users modify the same work item and allows the API to return a proper conflict response instead of silently replacing someone else’s changes.
Project structure
The backend follows a clean, layered structure so that domain logic, infrastructure, contracts, and background processing stay separated.
/Controllers
/Application
/Domain
/Infrastructure
/Contracts
/Common
/Background
/Docs
ProjectSaas.Api
ProjectSaas.Worker (not nunning on the live app)Main domain model
The system is centred around tenant-scoped entities that support business workflows, reliability, and auditability.
Core entities
C# / DomainOrganisation
User
Ticket
TicketAttachment
Notification
OutboxMessage
IdempotencyKeyIndexes are applied on common query paths such as organisation + status, organisation + assignee, and organisation + created date to keep filtering and listing efficient as tenant data grows.
Ticket lifecycle
The ticket lifecycle is designed to be both safe and reliable. Business writes happen synchronously, while secondary effects are processed asynchronously.
Ticket flow
System1. User creates or updates a ticket
2. API validates auth, tenant, role, and request rules
3. Ticket is persisted in PostgreSQL
4. Outbox message is written in the same transaction
5. API returns success immediately
6. Worker polls unprocessed outbox messages
7. Worker dispatches event handlers
8. Notifications and other side effects are executed
9. Outbox message is marked as processedThis approach improves availability because ticket creation does not depend on notification delivery or optional downstream services being available in the same request.
API surface
The system exposes a practical set of endpoints covering authentication, companies, users, tickets, and notifications.
Main endpoints
RESTAuth
POST /api/auth/login
POST /api/auth/refresh
POST /api/auth/logout
GET /api/auth/me
Companies
POST /api/companies
GET /api/companies/me
PATCH /api/companies/me
DELETE /api/companies/me
Users
GET /api/users
POST /api/users
GET /api/users/{userId}
PATCH /api/users/{userId}
DELETE /api/users/{userId}
Tickets
GET /api/tickets
POST /api/tickets
GET /api/tickets/{ticketId}
PATCH /api/tickets/{ticketId}
DELETE /api/tickets/{ticketId}
POST /api/tickets/{ticketId}/assign
POST /api/tickets/{ticketId}/complete
Notifications
GET /api/notifications
GET /api/notifications/unread-count
PATCH /api/notifications/{notificationId}/read
PATCH /api/notifications/read-allReliability and resilience
Idempotency
Idempotency keys are used on write-sensitive endpoints such as ticket creation, assignment, and completion. This protects the system from duplicate writes caused by retries or repeated client requests.
Retry and failure handling
The worker processes outbox messages in batches and records retry counts and errors for failed events. This makes the asynchronous pipeline more observable and prevents silent data loss.
Graceful degradation
Core ticket operations remain available even if a background side effect fails. This is important in distributed systems where notifications, enrichment, or downstream integrations may become temporarily unavailable.
Example of the design approach
The codebase separates responsibilities so controllers stay thin, services handle business rules, and infrastructure focuses on persistence and external concerns.
Example service responsibilities
ConceptualControllers
HTTP input/output, routing, status codes
Application services
Business rules, tenant scoping, validation orchestration
Domain
Core entities and invariants
Infrastructure
EF Core, persistence, auth plumbing, integrations
Worker
Outbox polling, retries, event dispatchingWhy this project matters
Project SaaS is intentionally designed to show the kind of engineering decisions expected in real backend work: multi-tenant safety, authentication and session management, robust API design, reliable asynchronous processing, and maintainable separation of concerns.
Rather than focusing only on CRUD screens, the project demonstrates how production-oriented backend systems are structured, tested, and evolved over time.
Tech stack
- ASP.NET Core Web API
- PostgreSQL
- Entity Framework Core
- JWT authentication
- SignalR for notifications
- BackgroundService worker
- Outbox pattern
- Next.js frontend