Skip to main content

Backstage API Architecture (C4 Model)

Complete C4 architecture documentation for the Law Firm Backstage API System, which encompasses both Admin API (provisioning & management) and User API (lawyer/staff portal).

🎯 Overview

The Backstage API is the unified backend system that powers the law firm's internal operations. It consists of two major API surfaces:

  • Admin API: Administrative operations, provisioning, access control, and system management
  • User API: Day-to-day operations for lawyers, paralegals, and staff (cases, clients, documents, time tracking, etc.)

Both APIs share:

  • Authentication infrastructure (Logto)
  • Database layer (PostgreSQL + Redis)
  • Common security model (RBAC + field-level permissions)
  • Unified audit logging
  • Shared infrastructure and deployment

Level 1: System Context

Backstage System in the Larger Ecosystem

Key Actors

ActorRolePrimary APIDescription
Admin UserSystem AdministratorAdmin APIProvisions firms, users, manages access grants, support access
LawyerAttorneyUser APIManages cases, clients, documents, time entries
ParalegalLegal AssistantUser APIAssists with case work, document management, scheduling
StaffSupport StaffUser APIBilling, records management, administrative tasks
ClientExternal User(Future Client API)Views case status, uploads documents (limited access)

External System Integration

SystemPurposeUsed ByCommunication
LogtoAuthentication & IdentityBoth APIsOAuth 2.0 / OIDC
AWS S3Document StorageUser APIAWS SDK
SendGridEmail NotificationsBoth APIsREST API
TwilioSMS NotificationsUser APIREST API
StripePayment ProcessingUser APIREST API
QuickBooksAccounting IntegrationUser APIREST API

Level 2: Container Diagram

Backstage API Containers and Data Stores

Container Descriptions

Frontend Layer

ContainerTechnologyPurpose
Web ApplicationReact 18, TypeScript, Material-UIPrimary interface for lawyers and staff
Mobile AppReact Native, TypeScriptiOS/Android app for mobile access
Admin PortalReact 18, TypeScript, Ant DesignAdministrative interface for system admins

API Layer

ContainerTechnologyPurposePort
API GatewayKong / AWS ALBRequest routing, rate limiting, authentication443
Admin APINestJS, TypeScript, Node.js 20Provisioning, user management, access grants3001
User APINestJS, TypeScript, Node.js 20Cases, clients, documents, time tracking3002
Auth ServiceNode.js, ExpressJWT validation, RBAC resolution, permission checks3010
Notification ServiceNode.js, Bull QueueEmail, SMS, push notifications3020
Document ServiceNode.js, Multer, SharpFile upload, processing, storage3030
Billing ServiceNode.jsTime entry aggregation, invoice generation3040

Data Layer

ContainerTechnologyPurposeAccess Pattern
PostgreSQLPostgreSQL 15, Multi-tenantPrimary data storeRow-level security per firm
RedisRedis 7, Cluster modeCache, sessions, rate limitsTTL-based expiration
ElasticsearchElasticsearch 8Full-text search across documentsAsync index updates

Message Queue

ContainerTechnologyPurpose
AWS SQSAmazon SQS, FIFO queuesAsync job processing, event-driven workflows

Level 3: Component Diagram

Admin API Internal Structure

User API Internal Structure


Level 4: Code Examples

4.1 Admin API - Provision a User (TypeScript)

// admin-api/src/users/users.service.ts
import { Injectable, ConflictException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { LogtoService } from '../logto/logto.service';
import { EventPublisher } from '../events/event-publisher';
import { AuditLogger } from '../audit/audit-logger';

@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private userRepo: Repository<User>,
private logtoService: LogtoService,
private eventPublisher: EventPublisher,
private auditLogger: AuditLogger,
) {}

async provisionUser(dto: ProvisionUserDto, adminUserId: string): Promise<User> {
// 1. Check if user already exists
const existing = await this.userRepo.findOne({
where: { email: dto.email, firmId: dto.firmId },
});
if (existing) {
throw new ConflictException('User already exists in this firm');
}

// 2. Create Logto identity (if inviting)
let logtoUserId: string | null = null;
if (dto.invite) {
logtoUserId = await this.logtoService.inviteUser({
email: dto.email,
name: dto.fullName,
orgId: dto.logtoOrgId,
});
}

// 3. Create local user profile
const user = this.userRepo.create({
firmId: dto.firmId,
logtoUserId,
email: dto.email,
fullName: dto.fullName,
role: dto.role,
isLawyer: dto.credentials?.length > 0,
createdBy: adminUserId,
});
await this.userRepo.save(user);

// 4. Add professional credentials (if lawyer)
if (dto.credentials?.length > 0) {
await this.addCredentials(user.id, dto.credentials);
}

// 5. Publish user.created event
await this.eventPublisher.publish('user.created', {
userId: user.id,
firmId: user.firmId,
role: user.role,
createdBy: adminUserId,
});

// 6. Audit log
await this.auditLogger.log({
action: 'USER_PROVISIONED',
actorId: adminUserId,
resourceType: 'USER',
resourceId: user.id,
metadata: { email: user.email, role: user.role },
});

return user;
}
}

4.2 User API - Create a Case (TypeScript)

// user-api/src/cases/cases.service.ts
import { Injectable, ForbiddenException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { PermissionResolver } from '../auth/permission-resolver';
import { EventPublisher } from '../events/event-publisher';

@Injectable()
export class CasesService {
constructor(
@InjectRepository(Case)
private caseRepo: Repository<Case>,
@InjectRepository(CaseAssignment)
private assignmentRepo: Repository<CaseAssignment>,
private permissionResolver: PermissionResolver,
private eventPublisher: EventPublisher,
) {}

async createCase(dto: CreateCaseDto, userId: string): Promise<Case> {
// 1. Check permission: cases:create
const hasPermission = await this.permissionResolver.check({
userId,
action: 'cases:create',
firmId: dto.firmId,
});
if (!hasPermission) {
throw new ForbiddenException('User lacks cases:create permission');
}

// 2. Create case
const caseEntity = this.caseRepo.create({
firmId: dto.firmId,
clientId: dto.clientId,
caseNumber: await this.generateCaseNumber(dto.firmId),
title: dto.title,
description: dto.description,
caseType: dto.caseType,
status: 'OPEN',
practiceArea: dto.practiceArea,
createdBy: userId,
});
await this.caseRepo.save(caseEntity);

// 3. Assign creator as lead attorney
const assignment = this.assignmentRepo.create({
caseId: caseEntity.id,
userId,
role: 'LEAD_ATTORNEY',
assignedBy: userId,
});
await this.assignmentRepo.save(assignment);

// 4. Publish case.created event
await this.eventPublisher.publish('case.created', {
caseId: caseEntity.id,
firmId: caseEntity.firmId,
clientId: caseEntity.clientId,
createdBy: userId,
});

return caseEntity;
}

private async generateCaseNumber(firmId: string): Promise<string> {
const year = new Date().getFullYear();
const count = await this.caseRepo.count({
where: { firmId, createdAt: Between(new Date(year, 0, 1), new Date(year, 11, 31)) },
});
return `${year}-${String(count + 1).padStart(5, '0')}`;
}
}

4.3 Permission Resolver (Shared Infrastructure)

// shared/auth/permission-resolver.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { RedisService } from '../cache/redis.service';

@Injectable()
export class PermissionResolver {
constructor(
@InjectRepository(AccessGrant)
private grantRepo: Repository<AccessGrant>,
@InjectRepository(RolePermission)
private rolePermRepo: Repository<RolePermission>,
private redis: RedisService,
) {}

async check(params: CheckPermissionParams): Promise<boolean> {
const { userId, action, resourceType, resourceId, firmId } = params;

// 1. Check cache first
const cacheKey = `perm:${userId}:${action}:${resourceType}:${resourceId || 'any'}`;
const cached = await this.redis.get(cacheKey);
if (cached !== null) return cached === 'true';

// 2. Get user's role-based permissions
const rolePerms = await this.rolePermRepo.find({
where: { userId, firmId },
});
const hasRolePermission = rolePerms.some(rp =>
this.matchesScope(rp.scope, action)
);

// 3. Check manual access grants (if resource-specific)
let hasAccessGrant = false;
if (resourceId) {
const grants = await this.grantRepo.findOne({
where: {
userId,
resourceType,
resourceId,
action,
revokedAt: IsNull(),
},
});
hasAccessGrant = !!grants;
}

const hasPermission = hasRolePermission || hasAccessGrant;

// 4. Cache result (5 minutes TTL)
await this.redis.set(cacheKey, String(hasPermission), 300);

return hasPermission;
}

private matchesScope(scope: string, action: string): boolean {
// Check if scope pattern matches action
// e.g., "cases:*" matches "cases:create"
if (scope === action) return true;
if (scope.endsWith(':*')) {
const prefix = scope.slice(0, -2);
return action.startsWith(prefix);
}
return false;
}
}

Data Flow Diagrams

Sequence: User Creates a Case

Sequence: Admin Provisions a User


Deployment Architecture

AWS Infrastructure (Multi-AZ)

Deployment Configuration

ComponentInstance TypeAuto-ScalingAvailability
Admin APIECS Fargate (2 vCPU, 4 GB)2-10 tasksMulti-AZ
User APIECS Fargate (4 vCPU, 8 GB)4-20 tasksMulti-AZ
PostgreSQLRDS r6g.xlargePrimary + StandbyMulti-AZ
RedisElastiCache cache.r6g.largePrimary + 2 replicasMulti-AZ
ElasticsearchES r6g.large.search3-node clusterMulti-AZ

Security Architecture

Multi-Layer Security Model

Security Features

FeatureImplementationPurpose
AuthenticationLogto OAuth 2.0 / OIDCIdentity verification
AuthorizationRBAC + Access GrantsPermission enforcement
Encryption (Transit)TLS 1.3Protect data in transit
Encryption (Rest)AES-256 (RDS, S3)Protect data at rest
Field-Level EncryptionApplication-levelProtect PII (SSN, credit cards)
Row-Level SecurityPostgreSQL RLSMulti-tenant data isolation
Rate LimitingAPI Gateway + RedisPrevent abuse
Audit LoggingCloudWatch LogsCompliance & forensics
Secret ManagementAWS Secrets ManagerSecure credential storage
DDoS ProtectionCloudFront + AWS ShieldAvailability protection

Monitoring & Observability

Observability Stack

Key Metrics

Metric CategoryMetricsAlerting Threshold
API PerformanceResponse time (p50, p95, p99), Error ratep99 > 1s, Error rate > 1%
DatabaseQuery time, Connection pool usage, Replication lagQuery > 500ms, Pool > 80%, Lag > 10s
CacheHit rate, Eviction rate, Memory usageHit rate < 80%, Memory > 90%
InfrastructureCPU, Memory, Disk I/O, NetworkCPU > 80%, Memory > 85%
BusinessCase creation rate, Document uploads, API calls/minAnomaly detection

API Comparison: Admin vs User

Feature Matrix

FeatureAdmin APIUser APIShared
AuthenticationLogto (admin roles)Logto (user roles)
DatabasePostgreSQL (admin schema)PostgreSQL (user schema)
CacheRedisRedis
Audit Logging✅ All operations✅ Critical operations
Rate LimitingHigher limitsStandard limits
Multi-tenancyCross-firm accessSingle-firm scoped

Access Patterns

OperationAdmin APIUser API
Create Law Firm
Provision Users
Manage Access Grants
Support Access (Act-as)
Create/Manage Cases
Upload Documents
Track Time
View InvoicesRead-only

Performance & Scalability

Performance Targets

MetricTargetCurrent
API Response Time (p95)< 200ms~150ms
API Response Time (p99)< 500ms~400ms
Database Query Time (p95)< 100ms~80ms
Cache Hit Rate> 80%~85%
Throughput5,000 req/sec~3,500 req/sec
Concurrent Users10,000+~8,000

Scaling Strategy

ComponentScaling MethodTrigger
APIsHorizontal (ECS tasks)CPU > 70% or Latency > 300ms
DatabaseVertical (instance size) + Read replicasCPU > 75% or Connections > 80%
CacheHorizontal (add nodes)Memory > 80% or Hit rate < 70%
SearchHorizontal (add nodes)CPU > 70% or Query latency > 200ms

Technology Stack Summary

Backend (Admin API + User API)

LayerTechnologyVersion
RuntimeNode.js20 LTS
FrameworkNestJS10.x
LanguageTypeScript5.x
Validationclass-validator, class-transformerLatest
ORMTypeORM0.3.x
API DocsOpenAPI 3.1 (Swagger)Latest

Data Layer

ComponentTechnologyVersion
DatabasePostgreSQL15.x
CacheRedis7.x
SearchElasticsearch8.x
Message QueueAWS SQS-
Object StorageAWS S3-

Infrastructure

ComponentTechnology
Container OrchestrationAWS ECS (Fargate)
Load BalancerAWS Application Load Balancer
CDNAWS CloudFront
DNSAWS Route 53
SecretsAWS Secrets Manager
MonitoringCloudWatch, Prometheus, Grafana
LoggingCloudWatch Logs, Elasticsearch, Kibana
TracingAWS X-Ray

API Documentation

Technical Specifications

Architecture Documentation

Getting Started


Last Updated: October 26, 2024 Maintained By: Platform Engineering Team Version: 1.0.0