Serverless Infrastructure for SaaS
Serverless is the natural fit for SaaS products: you pay per request, scale to zero when idle, and handle traffic spikes without pre-provisioning capacity. We build complete serverless SaaS infrastructure with multi-tenant data isolation, usage metering for billing integration, API key management, and cost controls — so you launch your SaaS with infrastructure that scales from 0 to 10,000 customers without re-architecture.
Need this done for your project?
We implement, you ship. Async, documented, done in days.
Multi-Tenant Architecture
We implement tenant isolation at every layer: API authentication, data access, and resource limits. The isolation model depends on your compliance requirements and customer expectations.
// Silo model — per-tenant DynamoDB tables (high isolation)
// Pool model — shared table with tenant partition key (cost-efficient)
// Bridge model — shared compute, isolated data (balanced)
// Pool model implementation — most common for early SaaS
interface TenantContext {
tenantId: string;
plan: 'free' | 'pro' | 'enterprise';
limits: {
apiCallsPerMonth: number;
storageBytes: number;
maxUsers: number;
};
}
// Middleware extracts tenant context from JWT or API key
export function withTenant(handler: (event: any, tenant: TenantContext) => Promise<any>) {
return async (event: APIGatewayProxyEventV2) => {
const tenantId = event.requestContext.authorizer?.lambda?.tenantId;
if (!tenantId) return { statusCode: 401, body: 'Unauthorized' };
const tenant = await getTenantConfig(tenantId);
// Enforce plan limits
const usage = await getMonthlyUsage(tenantId);
if (usage.apiCalls >= tenant.limits.apiCallsPerMonth) {
return { statusCode: 429, body: 'Monthly API limit reached' };
}
return handler(event, tenant);
};
}
// All DynamoDB queries are scoped to tenant
const items = await dynamo.send(new QueryCommand({
TableName: 'app-data',
KeyConditionExpression: 'PK = :pk',
ExpressionAttributeValues: { ':pk': { S: `TENANT#${tenantId}` } },
}));Every database query includes the tenant partition key. There is no code path that can access another tenant's data. We add integration tests that verify cross-tenant isolation by attempting unauthorized access patterns.
Usage Metering & Billing Integration
We implement usage metering that feeds directly into Stripe Billing or your custom billing system. Every metered event is tracked atomically with the API call.
// Usage metering with DynamoDB atomic counters
export async function recordUsage(
tenantId: string,
metric: 'api_calls' | 'storage_bytes' | 'compute_seconds',
amount: number
): Promise<void> {
const monthKey = new Date().toISOString().slice(0, 7); // 2025-01
await dynamo.send(new UpdateCommand({
TableName: 'usage-meters',
Key: {
PK: `TENANT#${tenantId}`,
SK: `USAGE#${monthKey}#${metric}`,
},
UpdateExpression: 'ADD #count :amount SET #updated = :now',
ExpressionAttributeNames: {
'#count': 'count',
'#updated': 'updatedAt',
},
ExpressionAttributeValues: {
':amount': amount,
':now': new Date().toISOString(),
},
}));
}
// End-of-month Stripe usage report (scheduled Lambda)
export async function reportUsageToStripe(): Promise<void> {
const tenants = await getAllActiveSubscriptions();
const monthKey = new Date().toISOString().slice(0, 7);
for (const tenant of tenants) {
const usage = await getUsage(tenant.id, monthKey, 'api_calls');
await stripe.subscriptionItems.createUsageRecord(
tenant.stripeSubscriptionItemId,
{
quantity: usage.count,
timestamp: Math.floor(Date.now() / 1000),
action: 'set',
}
);
}
}DynamoDB atomic counters ensure accurate usage tracking even under high concurrency. The monthly billing sync runs as a scheduled Lambda, reporting usage to Stripe for automatic invoice generation.
API Key Management
SaaS products need API key authentication for programmatic access. We implement a secure API key system with hashing, rotation, and per-key rate limits.
// API key format: anb_live_xxxxxxxxxxxxxxxxxxxx (32 chars)
// Stored as SHA-256 hash — the plain key is shown once at creation
import { createHash, randomBytes } from 'crypto';
export function generateApiKey(env: 'live' | 'test'): { key: string; hash: string } {
const raw = randomBytes(24).toString('base64url');
const key = `anb_${env}_${raw}`;
const hash = createHash('sha256').update(key).digest('hex');
return { key, hash };
}
// Lambda authorizer validates API key
export const authorizer = async (event: APIGatewayRequestAuthorizerEventV2) => {
const key = event.headers?.['x-api-key'];
if (!key?.startsWith('anb_')) return { isAuthorized: false };
const hash = createHash('sha256').update(key).digest('hex');
const record = await dynamo.send(new GetCommand({
TableName: 'api-keys',
Key: { PK: `KEY#${hash}` },
}));
const item = record.Item;
if (!item || item.revoked) return { isAuthorized: false };
// Update last-used timestamp (fire-and-forget)
dynamo.send(new UpdateCommand({
TableName: 'api-keys',
Key: { PK: `KEY#${hash}` },
UpdateExpression: 'SET lastUsed = :now',
ExpressionAttributeValues: { ':now': new Date().toISOString() },
})).catch(() => {});
return {
isAuthorized: true,
context: {
tenantId: item.tenantId,
plan: item.plan,
keyId: item.keyId,
},
};
};Keys are never stored in plaintext. The SHA-256 hash is the lookup key in DynamoDB. Key rotation creates a new key and marks the old one for expiry after a grace period. We implement per-key rate limits in the authorizer using the same token bucket pattern described above.
Cost Optimization & Scaling
Serverless SaaS costs scale linearly with usage, but there are optimizations that reduce your per-tenant cost by 40–60%.
- Reserved Concurrency — Set maximum concurrency per function to prevent runaway scaling and protect downstream resources
- DynamoDB On-Demand — Use PAY_PER_REQUEST billing during growth phase; switch to provisioned capacity with auto-scaling once traffic patterns stabilize
- S3 Intelligent Tiering — Automatically moves tenant data to cheaper storage classes based on access patterns
- Lambda ARM64 — Graviton2 processors are 20% cheaper at equivalent performance
- API Gateway caching — Cache GET responses at the gateway to reduce Lambda invocations by 50–80% for read-heavy APIs
# Cost model for 1,000-tenant SaaS on serverless
# Assumptions: 100 API calls/tenant/day, 50MB storage/tenant
Lambda: 1,000 * 100 * 30 = 3M invocations/mo
@ 128MB, 200ms avg = ~$1.50/mo
API Gateway: 3M requests @ $1/M = $3.00/mo
DynamoDB: 3M writes + 6M reads = ~$5.00/mo (on-demand)
50GB storage = $12.50/mo
S3: 50GB = $1.15/mo
CloudWatch: Logs + metrics = ~$5.00/mo
Total: ~$28/mo for 1,000 tenants = $0.028/tenant/monthAt this cost structure, even free-tier tenants are profitable. We set up AWS Budgets with per-service cost allocation tags so you can track infrastructure cost per tenant and per feature.
Why Anubiz Engineering
Ready to get started?
Skip the research. Tell us what you need, and we'll scope it, implement it, and hand it back — fully documented and production-ready.