Serverless & Edge Computing

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.

Start a Brief

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/month

At 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

100% async — no calls, no meetings
Delivered in days, not weeks
Full documentation included
Production-grade from day one
Security-first approach
Post-delivery support included

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.