Multi-Tenant Core API
The middleware eight utility-customer portals stand on - auth, data access, and Oracle CCS integration in one codebase.
- Role
- Primary author, architecture and security owner
- Period
- 2023 - present
- Ownership
- 78% of commits
- Stack
- Fastify 5 · TypeScript (strict) · Zod · MongoDB · AWS SDK v3 · ECS Fargate · Vitest
Problem
Eight utility companies - Delta, MVU, NEP, DelCo, Alex Renew, Carmel, Web Aruba, IPU - each needed a customer portal talking to Oracle Utilities CCS. The legacy server they inherited had authorization gaps and no clean way to vary behavior per tenant. Every new client meant forked code and re-audited security.
The rebuild had one mandate: a single API where a new tenant is configuration, not code, and where authorization failure is impossible to ship by accident.
Constraints
Utility customers pay bills through this thing - it has to be up and it has to be right. Oracle CCS is the system of record, reached over OAuth 2.0 with per-tenant credentials. The team is small, so the architecture had to make the secure path the easy path.
Architecture
Fastify 5 on Node 22, ESM-only, TypeScript strict with no `any`. Zod validates every input at the boundary; responses use one fixed envelope so clients never parse ad-hoc shapes.
Tenant behavior is composed from a base configuration plus per-tenant overrides, assembled by strategy factories - the request path never branches on tenant name. Authorization is enforced as a route-level gate: every endpoint proves the caller owns the account it touches before any data access, which is what kills IDOR as a class.
An 80% coverage gate and OWASP checks run in CI; Bitbucket Pipelines builds to ECR and deploys to ECS Fargate behind a shared ALB.
Outcome
- 8 tenants in production on one codebase - onboarding a tenant is config plus provisioning, not a fork.
- Authorization is structural, not reviewed-in: the route contract enforces ownership checks on every endpoint.
- The integration patterns became the fleet standard other services adopt.