Skip to content

About

Production-ready multi-tenant SaaS backend engine built with Java 21 & Spring Boot 3. Features: multi-tenancy, RBAC, feature flags, usage metering, billing, audit log.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

17 Commits

Folders and files

Repository files navigation

SaaS Platform Engine

A production-ready multi-tenant SaaS backend framework built with Java 21 & Spring Boot 3.

CI Java Spring Boot License: MIT

🌐 Live Demo

https://saas-platform-engine-production.up.railway.app

Demo credentials:

  • Email: admin@demo.com
  • Password: Demo1234!

What Is This?

Every SaaS product needs the same plumbing before it can ship its first feature: tenant isolation, authentication, roles, billing, audit trails, feature flags and usage limits. SaaS Platform Engine is that plumbing, done properly β€” a fork-and-go Spring Boot backend where multi-tenancy is enforced at the query level, permissions are declarative annotations, and metering/auditing happen off the request thread via AOP. It's built for backend developers who want to start a SaaS from a foundation that already behaves like a production system, not a tutorial.

Architecture

                                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                β”‚                 Spring Boot App                β”‚
 HTTP Request                   β”‚                                                β”‚
──────────────▢ TenantFilter ──▢│ JwtAuthFilter ──▢ Controller ──▢ Service ──▢ JPA│──▢ PostgreSQL
   (header /      resolves      β”‚  verifies RS256    @Requires-     @Transactionalβ”‚    (row-level
    subdomain)    tenant into   β”‚  JWT, overrides    Permission                  β”‚     tenant_id
                  ThreadLocal   β”‚  tenant from claim  @TrackUsage                β”‚     isolation)
                                β”‚                     @FeatureFlagRequired       β”‚
                                β”‚                          β”‚                     β”‚
                                β”‚            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”‚
                                β”‚            β–Ό             β–Ό             β–Ό       β”‚
                                β”‚      AuditService  MeteringService  FlagCache ─┼──▢ Redis
                                β”‚      (async pool)  (async pool +               β”‚    (flags:* cache,
                                β”‚            β”‚        @Scheduled agg.)           β”‚     refresh:* tokens)
                                β”‚            β–Ό             β–Ό                     β”‚
                                β”‚       audit_logs   usage_records/summaries    β”‚
                                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Features

  • Multi-tenancy β€” row-level isolation (tenant_id on every table), tenant resolved from X-Tenant-ID header, JWT claim or subdomain; pluggable TenancyStrategy (shared-schema / schema-per-tenant)
  • Auth β€” RS256 JWT access tokens (15 min) + single-use rotating refresh tokens in Redis (7 days)
  • RBAC β€” system roles (OWNER/ADMIN/MEMBER/SUPER_ADMIN), tenant-scoped custom roles (PRO+), declarative @RequiresPermission("billing:manage") enforcement via AOP
  • Billing β€” plans, subscriptions, invoices, Stripe checkout/portal/webhooks with signature verification; automatic mock mode without credentials
  • Audit log β€” every action captured asynchronously with before/after snapshots, IP and user agent; paginated, filterable API
  • Feature flags β€” per-tenant flags with percentage rollout, Redis cache (60s TTL) with write-through invalidation, @FeatureFlagRequired gate that 404s disabled endpoints
  • Usage metering β€” @TrackUsage AOP: synchronous plan-limit check (429 on breach), async usage recording, per-minute aggregation job
  • Onboarding β€” one transaction creates tenant + owner + FREE subscription and returns tokens immediately
  • Super admin β€” cross-tenant management: list, inspect usage, override plan, suspend
  • Demo dashboard β€” single-file dark SPA with live usage rings, flag toggles, billing and a built-in API explorer
  • Ops β€” Flyway migrations, Testcontainers test suite, multi-stage Docker build, GitHub Actions CI, Railway deployment

Key Technical Decisions

1. Row-level multi-tenancy over schema-per-tenant (default). Shared schema with a tenant_id column keeps operations cheap: one connection pool, one migration run, trivial cross-tenant analytics for the platform operator. The trade-off is that isolation lives in code, so the rule here is structural: repositories only expose finder methods that require a tenantId parameter, and the tenant always comes from the verified JWT β€” a spoofed X-Tenant-ID header can never switch an authenticated caller's tenant. SeparateSchemaTenancy exists behind the same TenancyStrategy interface for customers who contractually need physical separation.

2. RS256 (asymmetric) JWT instead of HS256. With HS256 every service that verifies tokens can also mint them β€” one leaked shared secret compromises the platform. With RS256 the private key never leaves the auth component; API gateways, sidecar services or a future microservice split verify with the public key only. For a SaaS designed to grow past one deployable, asymmetric is the correct default even while the app is still a monolith.

3. AOP for audit, metering and permissions. @RequiresPermission, @TrackUsage and @TrackAudit keep cross-cutting policy out of business logic: a controller method declares what it needs, aspects decide how it's enforced. Audit and metering writes are handed to dedicated executors β€” a slow audit insert can never add latency to a user request, and losing one audit row on crash is an accepted trade-off documented in code.

4. Redis cache for feature flags β€” bounded staleness, explicit invalidation. Flag reads happen on hot paths, so they're served from Redis (flags:{tenant}:{key}, 60s TTL) instead of hitting PostgreSQL per request. Writes delete the cache key, so toggles apply immediately on the instance that made the change and within 60 seconds everywhere else β€” a deliberate consistency/performance trade-off. If Redis is down, evaluation falls back to the database: flags degrade in latency, not in correctness.

Quick Start

git clone https://github.com/addictcode/saas-platform-engine.git
cd saas-platform-engine
docker compose up --build

Open http://localhost:8080 and click Try Demo (admin@demo.com / Demo1234!).

API Reference

Method Path Auth Description
POST /onboarding/start πŸ”“ Create tenant + owner, returns tokens
GET /onboarding/check-slug/{slug} πŸ”“ Check slug availability
POST /auth/register πŸ”“ Join an existing tenant
POST /auth/login πŸ”“ Login, returns access + refresh tokens
POST /auth/refresh πŸ”“ Rotate refresh token
POST /auth/logout βœ… Revoke refresh token
GET /api/v1/tenant any Current tenant info
PATCH /api/v1/tenant settings:manage Update tenant settings
GET /api/v1/tenant/usage billing:read Usage vs plan limits
GET /api/v1/users users:read List users
POST /api/v1/users/invite users:manage Invite user (seat-limited)
GET /api/v1/users/me any Current profile
PATCH /api/v1/users/{id} users:manage Update user
DELETE /api/v1/users/{id} users:manage Deactivate user
POST /api/v1/users/{id}/roles users:manage Assign role
GET /api/v1/roles users:read List roles
POST /api/v1/roles users:manage Create custom role (PRO+)
POST /api/v1/roles/{id}/permissions users:manage Set role permissions
GET /api/v1/flags any List feature flags
POST /api/v1/flags flags:manage Create flag
PATCH /api/v1/flags/{key} flags:manage Toggle / rollout %
DELETE /api/v1/flags/{key} flags:manage Delete flag
GET /api/v1/beta/dashboard any Flag-gated example (404 while off)
GET /api/v1/billing/subscription billing:read Current subscription
GET /api/v1/billing/invoices billing:read Invoice history
POST /api/v1/billing/checkout billing:manage Start upgrade checkout
POST /api/v1/billing/portal billing:manage Billing portal link
GET /api/v1/plans any Plan catalogue
GET /api/v1/usage any Usage report
GET /api/v1/data any Metered demo endpoint (429 past limit)
GET /api/v1/audit audit:read Audit log (paginated, filterable)
GET /admin/tenants admin:super List all tenants
GET /admin/tenants/{id} admin:super Tenant details + usage
PATCH /admin/tenants/{id}/plan admin:super Override plan
POST /admin/tenants/{id}/suspend admin:super Suspend tenant
POST /webhook/stripe signature Stripe webhook

Create your own tenant:

curl -X POST http://localhost:8080/onboarding/start \
  -H 'Content-Type: application/json' \
  -d '{"companyName":"Acme Inc","email":"you@acme.dev","password":"Sup3rSecret!","fullName":"Jane Doe"}'

Login and call the API:

TOKEN=$(curl -s -X POST http://localhost:8080/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"admin@demo.com","password":"Demo1234!"}' | jq -r .accessToken)

curl http://localhost:8080/api/v1/tenant/usage -H "Authorization: Bearer $TOKEN"

Toggle a feature flag (takes effect immediately):

curl -X PATCH http://localhost:8080/api/v1/flags/beta_dashboard \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"enabled":true,"rolloutPct":25}'

Project Structure

com.saasengine
β”œβ”€β”€ config/          Security, CORS, Redis, JPA, async executors, Stripe gateway selection
β”œβ”€β”€ multitenancy/    TenantContext (ThreadLocal), TenantFilter, interceptor, isolation strategies
β”œβ”€β”€ auth/            JWT (RS256) issuing/verification, refresh token rotation, login/register
β”œβ”€β”€ onboarding/      Tenant provisioning, slug generation
β”œβ”€β”€ domain/
β”‚   β”œβ”€β”€ tenant/      Tenant aggregate
β”‚   β”œβ”€β”€ user/        Users, invitations, seat limits
β”‚   β”œβ”€β”€ rbac/        Roles, permissions, grants
β”‚   β”œβ”€β”€ subscription/ Plans and subscriptions
β”‚   β”œβ”€β”€ billing/     Invoices, checkout, Stripe gateway (real + mock)
β”‚   β”œβ”€β”€ audit/       Async audit trail
β”‚   β”œβ”€β”€ featureflags/ Flags with Redis cache + rollout
β”‚   └── metering/    Usage records, limits, aggregation job
β”œβ”€β”€ api/             REST controllers (v1)
β”œβ”€β”€ admin/           Cross-tenant super admin API
└── shared/          Exceptions, @RequiresPermission/@TrackUsage/@TrackAudit + aspects

Configuration

Variable Default Description
SPRING_DATASOURCE_URL jdbc:postgresql://localhost:5432/saasengine PostgreSQL JDBC URL
SPRING_DATASOURCE_USERNAME / _PASSWORD saasengine Database credentials
SPRING_DATA_REDIS_HOST / _PORT localhost:6379 Redis (or REDIS_URL in prod profile)
SPRING_PROFILES_ACTIVE β€” docker / prod
STRIPE_API_KEY empty Empty β‡’ billing mock mode
STRIPE_WEBHOOK_SECRET empty Webhook signature verification
ALLOWED_ORIGINS * CORS allowlist (prod profile)
APP_JWT_ACCESS_TOKEN_EXPIRY 900 Access token TTL, seconds
APP_JWT_REFRESH_TOKEN_EXPIRY 604800 Refresh token TTL, seconds
APP_TENANCY_STRATEGY shared-schema shared-schema / separate-schema

Running Tests

./mvnw test        # requires Docker (Testcontainers spins up PostgreSQL + Redis)
Suite Covers
OnboardingIntegrationTest Tenant provisioning, duplicate email β†’ 409, slug availability, validation β†’ 422
MultiTenancyIntegrationTest Cross-tenant isolation, spoofed X-Tenant-ID header is ignored, 401 without token
RbacIntegrationTest MEMBER blocked (403), OWNER allowed, ADMIN grant unlocks abilities, super admin gate
MeteringIntegrationTest Usage recording, FREE limit β†’ 429, upgrade lifts limit
FeatureFlagIntegrationTest CRUD, 404-gating, immediate cache invalidation on toggle
JwtServiceTest Claims round-trip, expiry, wrong key, tampering
FeatureFlagServiceTest Cache hit skips DB, invalidation on update, deterministic rollout buckets
SlugServiceTest Slugification, collision suffixing

Roadmap

  • Email verification & password reset flows
  • OAuth2 social login (Google/GitHub) per tenant
  • Outbound webhook system with retries and HMAC signing
  • Usage analytics dashboard (time-series rollups)
  • Terraform provider for tenant provisioning

License

MIT

About

Production-ready multi-tenant SaaS backend engine built with Java 21 & Spring Boot 3. Features: multi-tenancy, RBAC, feature flags, usage metering, billing, audit log.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages