Reference: B2B SaaS API
A reference architecture for a multi-tenant B2B SaaS API on Node.js 24 - patterns you can copy and adapt. Annotated for NestJS 11, Prisma, Redis, OpenTelemetry, and Kubernetes.
Search across all documentation pages
A reference architecture for a multi-tenant B2B SaaS API on Node.js 24 - patterns you can copy and adapt. Annotated for NestJS 11, Prisma, Redis, OpenTelemetry, and Kubernetes.
Quick-reference recipe card - architecture at a glance.
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ ALB/Ingress│────▶│ orders-api │────▶│ Postgres 16 │
│ + WAF │ │ Nest+Fastify │ │ (tenant_id) │
└─────────────┘ └──────┬───────┘ └─────────────┘
│
┌──────▼───────┐ ┌─────────────┐
│ Redis │────▶│ order-worker│
│ cache+queue │ │ BullMQ │
└──────────────┘ └─────────────┘
│
┌──────▼───────┐
│ OTel Collector│
└──────────────┘Stack (2026-07):
| Layer | Choice |
|---|---|
| Runtime | Node.js 24.18.0 LTS |
| Framework | NestJS 11, Fastify adapter |
| ORM | Prisma 6 |
| Cache/queue | Redis 7, BullMQ |
| Observability | OpenTelemetry SDK → Grafana Tempo |
| Deploy | EKS, Argo CD, HPA |
src/
├── modules/
│ ├── orders/
│ │ ├── orders.controller.ts
│ │ ├── orders.service.ts
│ │ └── orders.repository.ts
│ ├── tenants/
│ └── webhooks/
├── common/
│ ├── guards/tenant.guard.ts
│ ├── interceptors/logging.interceptor.ts
│ └── prisma/prisma.service.ts
└── main.ts// src/common/guards/tenant.guard.ts
@Injectable()
export class TenantGuard implements CanActivate {
canActivate(ctx: ExecutionContext): boolean {
const req = ctx.switchToHttp().getRequest();
const tenantId = req.headers["x-tenant-id"] as string;
if (!tenantId) throw new UnauthorizedException("Missing tenant");
req.tenantId = tenantId;
return true;
}
}// src/modules/orders/orders.service.ts - tenant-scoped queries
@Injectable()
export class OrdersService {
constructor(private prisma: PrismaService) {}
list(tenantId: string) {
return this.prisma.order.findMany({
where: { tenantId },
take: 100,
});
}
}# k8s/deployment.yaml (excerpt)
resources:
requests: { cpu: "500m", memory: "512Mi" }
limits: { cpu: "2", memory: "768Mi" }
env:
- name: NODE_OPTIONS
value: "--max-old-space-size=512"
- name: OTEL_SERVICE_NAME
value: orders-apihttp_request_duration_p95/health/ready checks Postgres + Redisprisma migrate deploy Job before Rollout| Strategy | This reference |
|---|---|
Row-level tenant_id | Yes - simplest for <500 tenants |
| Schema per tenant | Escalate at compliance request |
| DB per tenant | Enterprise tier only |
tenantId from guard - enforced in code review@Public() only on /health/*// Trace propagation into BullMQ job data
await this.queue.add("order.created", payload, {
headers: { traceparent: getCurrentTraceparent() },
});/v1/orderstenantId required on every job payload.PrismaService with lifecycle hooks.main.ts from day one.Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 19, 2026