Frontend, Backend, Shared Packages, Service Boundaries & Environment Design
102.1 Introduction
Chapter 101 established the implementation foundation: repository organization, engineering workflow, environments, security gates, and development principles.
Chapter 102 moves one level deeper.
The objective is to define how the actual application should be divided into components and how those components communicate.
A secure AI platform should avoid becoming a single large application where authentication, database access, AI calls, file processing, billing, and administrative functions are mixed together.
Instead, the platform should establish clear boundaries.
The fundamental architecture is:
User
↓
Frontend
↓
API Boundary
↓
Application Services
↓
Security / Policy
↓
Data / AI / Storage Services
↓
External Providers
Every boundary should have an explicit responsibility.
102.2 Architecture Objectives
The core architecture should provide:
- clear separation of responsibilities
- secure service communication
- centralized authentication
- centralized authorization
- controlled database access
- provider-independent AI integration
- isolated file processing
- asynchronous job processing
- centralized observability
- tenant isolation
- configurable security policies
- predictable deployment
- scalable infrastructure
The architecture should also allow individual components to evolve without requiring the entire system to be rewritten.
102.3 Logical System Architecture
The complete logical architecture can be represented as:
┌──────────────────────┐
│ User │
│ Browser / Mobile │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Web Frontend │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ API Gateway / Edge │
└──────────┬───────────┘
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌──────────┐ ┌────────────┐ ┌────────────┐
│ Auth │ │ Policy │ │ API │
│ Service │ │ Engine │ │ Services │
└──────────┘ └────────────┘ └─────┬──────┘
│
┌────────────────────────────────────┼───────────────┐
▼ ▼ ▼
┌────────────┐ ┌──────────┐ ┌──────────┐
│ PostgreSQL │ │ Storage │ │ AI Layer │
└────────────┘ └──────────┘ └────┬─────┘
│
┌─────────────────┼─────────────┐
▼ ▼ ▼
Provider A Provider B Local Model
Cross-cutting services surround the entire system:
Audit Logging
Monitoring
Security Detection
Secrets Management
Rate Limiting
Configuration
Backup
Policy Enforcement
102.4 Frontend Boundary
The frontend is an untrusted client.
Even if the application controls the frontend code, a malicious user can modify requests before they reach the server.
Therefore:
Frontend ≠ Trusted Security Boundary
The frontend may provide:
- user interface
- form validation
- client-side previews
- media editing
- upload interfaces
- progress indicators
- authentication screens
- dashboards
- project management
- AI generation controls
However, the server must independently verify all security-sensitive decisions.
For example:
Frontend:
"User is allowed to edit project 123."
Backend:
"Let me verify that."
The backend must never blindly trust client-provided authorization claims.
102.5 Backend Boundary
The backend represents the primary trusted application boundary.
Responsibilities include:
Authentication
Authorization
Input validation
Business logic
Policy enforcement
Database access
AI orchestration
Storage coordination
Job creation
Audit logging
Usage accounting
The backend should expose stable APIs rather than allowing the frontend to directly communicate with internal infrastructure.
102.6 API Gateway
The API gateway or edge layer acts as the first controlled entry point.
Typical responsibilities include:
TLS termination
Request routing
Rate limiting
Request size limits
Basic filtering
Authentication forwarding
Request IDs
Security headers
Traffic control
The gateway should not become a replacement for application authorization.
For example:
Gateway:
"Request is authenticated."
Application:
"Is this user actually authorized to perform this action?"
Both questions are different.
102.7 Authentication Service
Authentication establishes identity.
Conceptually:
Credentials / Identity Provider
↓
Authentication
↓
User Identity
↓
Session / Token
Authentication should answer:
Who is this requester?
Authorization answers a different question:
What is this requester allowed to do?
This distinction should exist throughout the implementation.
102.8 Authorization Service
Authorization should evaluate:
Subject
Resource
Action
Context
Policy
For example:
Subject:
User A
Resource:
Project B
Action:
edit
Context:
Tenant C
Policy:
User A owns or has permission on Project B
The result is:
ALLOW
or:
DENY
The default should be deny.
102.9 Policy Engine
A policy engine allows security decisions to become consistent.
Instead of embedding authorization logic everywhere:
if user.role === "admin"
the system can evaluate policies through a centralized mechanism.
Conceptually:
Request
↓
Identity
↓
Resource
↓
Action
↓
Policy Engine
↓
Decision
This is particularly useful when the platform introduces:
- organizations
- teams
- roles
- project permissions
- subscription limits
- AI permissions
- administrative access
- sensitive operations
102.10 Application Service Layer
Business operations should live in application services rather than inside HTTP route handlers.
For example:
HTTP Route
↓
Controller
↓
Application Service
↓
Repository / External Service
A generation service might conceptually perform:
validate request
→ verify user
→ verify project access
→ evaluate content policy
→ verify usage limits
→ create generation job
→ enqueue task
→ audit event
The HTTP endpoint should primarily coordinate the request rather than contain the entire business process.
102.11 Repository / Data Access Layer
The data layer should separate business logic from database implementation.
Example:
Application Service
↓
Repository
↓
ORM / Query Layer
↓
PostgreSQL
This provides several advantages:
- easier testing
- centralized database logic
- consistent authorization checks
- easier migrations
- improved observability
- simpler future database changes
102.12 Shared Packages
The platform should avoid duplicating critical logic.
Useful shared packages include:
packages/
├── auth/
├── ai/
├── database/
├── security/
├── storage/
├── validation/
├── observability/
├── config/
├── ui/
└── shared/
Examples:
validation
Contains reusable schemas and validation utilities.
security
Contains security-related controls and utilities.
observability
Contains logging, tracing, metrics, and security-event interfaces.
config
Contains configuration schemas and environment validation.
ai
Contains model interfaces and provider adapters.
102.13 Type Safety
TypeScript should be used consistently across application boundaries where practical.
A shared type can represent an AI generation request:
GenerationRequest
rather than independently defining different versions in frontend and backend.
Conceptually:
Frontend Type
↓
Shared Type
↓
API
↓
Backend Type
However, compile-time types are not a substitute for runtime validation.
A malicious client can send arbitrary JSON.
Therefore:
TypeScript validation
+
Runtime validation
should be used together.
102.14 Runtime Validation
Every external input should be validated.
External input includes:
- HTTP requests
- query parameters
- uploaded files
- webhook payloads
- third-party API responses
- queue messages
- imported documents
- AI outputs
The general pattern is:
Untrusted Data
↓
Parse
↓
Validate
↓
Normalize
↓
Use
Never assume external data matches the expected schema merely because the frontend normally sends correct data.
102.15 AI Boundary
AI systems require their own security boundary.
The application should not allow arbitrary model calls from every part of the codebase.
Instead:
Application
↓
AI Orchestrator
↓
Policy Checks
↓
Model Router
↓
Provider Adapter
↓
Model
The orchestrator can control:
- allowed models
- maximum token usage
- request limits
- provider selection
- safety policy
- user permissions
- cost limits
- logging
- timeout
- retry behavior
102.16 AI Provider Adapters
Provider-specific code should remain isolated.
Example:
packages/ai/
│
├── core/
├── router/
├── providers/
│ ├── provider-a/
│ ├── provider-b/
│ ├── provider-c/
│ └── local/
└── safety/
The application should communicate with a stable internal interface.
Conceptually:
generate(request)
rather than:
callProviderAWithProviderSpecificParameters()
This makes migration and fallback much easier.
102.17 Storage Boundary
Object storage should also be isolated.
The application should not expose raw storage credentials to users.
Instead:
User
↓
API
↓
Authorization
↓
Upload Authorization
↓
Storage Operation
For large files, controlled upload mechanisms can be used so the file does not unnecessarily pass through the application server.
But authorization and file restrictions must still be enforced.
102.18 Media Processing Boundary
Media processing should be isolated from the primary application.
A safe design is:
Upload
↓
Quarantine
↓
Validation
↓
Scanning
↓
Processing Worker
↓
Output Validation
↓
Trusted Storage
This reduces the possibility that a malformed or malicious media file directly interacts with the main application environment.
102.19 Queue Boundary
Long-running operations should become jobs.
Example:
API
↓
Create Job
↓
Queue
↓
Worker
↓
Result
Jobs should have controlled states:
queued
processing
completed
failed
cancelled
A job should not be silently lost.
102.20 Idempotency
Operations that may be retried should be designed to avoid accidental duplication.
For example:
Generate Image
If the client retries the request because of a network timeout, the system should avoid accidentally creating two paid generations when only one was intended.
A conceptual flow is:
Request
↓
Idempotency Key
↓
Check Existing Operation
↓
Existing?
├── Yes → Return Existing Result
└── No → Create Operation
This is especially important for:
- payments
- AI generation
- file processing
- webhooks
- account changes
102.21 Environment Architecture
Configuration should differ by environment.
Example:
Development
Testing
Staging
Production
A conceptual model:
Source Code
↓
Environment Configuration
↓
Validated Configuration
↓
Application
The application should fail startup if required configuration is missing or invalid.
This is preferable to silently running with insecure defaults.
102.22 Configuration Categories
Configuration can be divided into:
Non-sensitive
Examples:
Application URL
Feature flags
Timeout settings
Model names
Logging level
Sensitive
Examples:
Database credentials
AI provider credentials
Signing secrets
Encryption keys
Storage credentials
Payment secrets
Sensitive configuration should be injected securely at runtime.
102.23 Secret Isolation
Secrets should have separate scopes.
For example:
Frontend
→ no provider secret
API
→ API-required secrets
Worker
→ worker-required secrets
Database service
→ database credentials
Deployment system
→ deployment credentials
A worker that only processes images should not automatically receive payment credentials.
This follows least privilege.
102.24 Service-to-Service Authentication
Internal services should not automatically trust one another.
A secure conceptual model is:
Service A
↓
Identity
↓
Authentication
↓
Authorization
↓
Service B
The receiving service should verify that:
- the caller is authentic;
- the caller is allowed to make the requested operation;
- the request is valid;
- the request is within policy.
102.25 Tenant Context
For a multi-user platform, every relevant operation should have tenant context where applicable.
For example:
Request
↓
User Identity
↓
Tenant Identity
↓
Resource
↓
Authorization
Database queries, storage paths, caches, vectors, jobs, and logs should not accidentally mix tenant data.
Tenant isolation should therefore be implemented consistently rather than only in the frontend.
102.26 Request Context
A request context can carry important metadata through services.
Conceptually:
Request Context
├── request_id
├── trace_id
├── user_id
├── tenant_id
├── session_id
├── security_context
└── risk_context
Not every field needs to be exposed to every service.
Sensitive information should be minimized.
The purpose is to make operations traceable without unnecessarily spreading personal data.
102.27 Security Event Architecture
Security events should be distinct from ordinary debug logs.
Examples include:
authentication_failed
authorization_denied
rate_limit_triggered
suspicious_upload
policy_violation
privileged_action
credential_rotation
security_configuration_changed
These events can feed monitoring and detection systems.
102.28 Observability Boundary
The platform should expose three major telemetry categories:
Metrics
Logs
Traces
Security events form an additional important stream.
Example:
Request
├── Metrics
├── Logs
├── Trace
└── Security Events
This allows engineering teams to understand both system health and security behavior.
102.29 Health Checks
Services should provide controlled health information.
Typical categories:
Liveness
Readiness
Dependency health
A liveness check answers:
Is the process running?
A readiness check answers:
Can this service safely receive traffic?
Health endpoints should avoid exposing sensitive internal information.
102.30 Graceful Failure
A distributed AI platform will experience failures.
Examples:
AI provider unavailable
Database temporarily unavailable
Queue unavailable
Storage timeout
Network failure
Worker crash
The system should respond predictably.
For example:
AI Provider Failure
↓
Retry if safe
↓
Fallback provider if policy allows
↓
Queue for later processing
↓
Controlled failure
Retries should have limits.
Unlimited retries can turn a small failure into a major outage.
102.31 Timeout Strategy
Every external operation should have a bounded timeout.
Examples:
Database query
API request
AI inference
Storage operation
Queue operation
Webhook call
Conceptually:
Operation
↓
Timeout
↓
Success / Controlled Failure
A service waiting forever for an external dependency can consume threads, connections, memory, and eventually cause cascading failure.
102.32 Architecture Dependency Rules
The project should define which layers may depend on which others.
A useful rule is:
UI
↓
API
↓
Application Services
↓
Domain / Security
↓
Infrastructure
Infrastructure should not unexpectedly control application policy.
Similarly, UI components should not directly access production databases.
These dependency rules can be enforced through code review and automated tooling where practical.
102.33 Example Request Lifecycle
Consider a user requesting an AI image generation.
The complete request may follow:
1. User submits prompt
↓
2. Frontend validates basic structure
↓
3. API receives request
↓
4. Authentication verifies identity
↓
5. Authorization verifies project access
↓
6. Runtime validation checks input
↓
7. Content policy evaluates request
↓
8. Rate limit is evaluated
↓
9. Usage quota is checked
↓
10. Generation job is created
↓
11. Job enters queue
↓
12. Worker receives job
↓
13. AI orchestrator selects model
↓
14. Provider adapter calls model
↓
15. Output is validated
↓
16. Result is stored
↓
17. Audit event is recorded
↓
18. Job becomes completed
↓
19. Frontend receives result
This lifecycle demonstrates how security and functionality operate together.
102.34 Architecture Anti-Patterns
Several designs should be avoided.
Anti-pattern 1 — API keys in frontend
Browser
↓
Provider API
This can expose provider credentials.
Better:
Browser
↓
Backend
↓
Provider
Anti-pattern 2 — Direct database access from frontend
Browser
↓
Database
This bypasses important application controls.
Better:
Browser
↓
API
↓
Authorization
↓
Database
Anti-pattern 3 — One giant service
A single codebase containing:
Authentication
AI
Billing
Media Processing
Database
Admin
Notifications
with no meaningful boundaries can become difficult to secure and maintain.
A modular monolith can be an excellent starting point while preserving internal boundaries before introducing many independently deployed microservices.
Anti-pattern 4 — Trusting client roles
Never assume:
role=admin
because the client supplied it.
The backend must derive and verify authorization from trusted server-side information.
102.35 Recommended Initial Implementation Strategy
A new platform does not necessarily need dozens of microservices on day one.
A practical starting architecture is:
┌───────────────┐
│ Web Frontend │
└───────┬───────┘
│
▼
┌───────────────┐
│ API / Backend │
│ Modular │
└───────┬───────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
PostgreSQL Storage Queue
│ │
│ ▼
│ Worker
│ │
└──────┬───────┘
▼
AI Orchestrator
│
┌────────┼────────┐
▼ ▼ ▼
Provider Provider Local
This architecture provides strong boundaries without prematurely creating excessive operational complexity.
102.36 Architecture Maturity Path
The platform can evolve gradually.
Stage 1
Modular Monolith
Stage 2
Modular Backend
+
Dedicated Worker
Stage 3
Separate AI Service
+
Dedicated Processing Services
Stage 4
Selective Microservices
+
Service Mesh
+
Advanced Infrastructure
The architecture should evolve according to actual scale and operational requirements rather than adopting complexity simply because it is fashionable.
102.37 Architecture Verification Checklist
Before implementation continues, verify:
[ ] Frontend is treated as untrusted
[ ] API boundary is defined
[ ] Authentication boundary is defined
[ ] Authorization boundary is defined
[ ] Policy engine boundary is defined
[ ] Database access is centralized
[ ] Storage access is controlled
[ ] AI access is centralized
[ ] Provider adapters are isolated
[ ] Worker architecture is defined
[ ] Queue architecture is defined
[ ] Tenant context is defined
[ ] Service authentication is defined
[ ] Runtime validation is defined
[ ] Configuration validation is defined
[ ] Secrets are isolated
[ ] Logging architecture is defined
[ ] Security events are defined
[ ] Health checks are defined
[ ] Timeout policies are defined
[ ] Retry policies are defined
[ ] Failure behavior is defined
102.38 Conclusion
Chapter 102 establishes the core architecture that connects the implementation foundation from Chapter 101 to the actual platform components.
The most important architectural rule is:
Do not allow functionality to bypass security boundaries.
AI requests should pass through AI controls.
Database operations should pass through controlled data access.
File processing should pass through quarantine and validation.
Administrative operations should pass through privileged authorization.
External providers should be accessed through controlled adapters.
The result is an architecture where functionality and security are integrated rather than developed as separate systems.
The next chapter can now focus on the actual backend foundation, including application initialization, module organization, API structure, configuration loading, request lifecycle, error handling, validation, and the first secure backend implementation patterns.
Top comments (0)