[SAMPLE] Modern Web Architecture: Design Patterns for Scalable Apps
Modern Web Architecture: Design Patterns for Scalable Apps
Building web applications that scale requires deliberate architectural decisions made early in the development process. This guide covers the major architectural patterns, their tradeoffs, and practical guidance for choosing the right approach.
The Architectural Spectrum
Web architecture exists on a spectrum from simple to complex. The key is choosing the right level of complexity for your current needs while leaving room to evolve.
Simple <------------------------------------------> Complex
Static Site -> SSR Monolith -> Modular Monolith -> Microservices
The golden rule: Start with the simplest architecture that meets your requirements. You can always decompose later; premature distribution creates unnecessary complexity.
Monolithic Architecture
What It Is
A single deployable unit containing all application logic: routing, business logic, data access, and background jobs.
When to Choose It
- Early-stage startups and MVPs
- Small teams (1-5 developers)
- Applications with tightly coupled domains
- When rapid iteration matters more than independent scaling
Modern Monolith Example (Next.js)
src/
app/ # Routes and pages
api/ # API endpoints
[locale]/ # i18n pages
server/
usecases/ # Business logic
repositories/ # Data access
actions/ # Server Actions
client/
components/ # UI components
hooks/ # React hooks
database/ # Schema definitions
This structure keeps everything in one deployable unit while maintaining clean separation of concerns internally.
Advantages
- Simple deployment (one artifact)
- Easy to debug (single process)
- No network latency between components
- Straightforward local development
Disadvantages
- Scaling is all-or-nothing
- A single bug can bring down the entire system
- Deployment requires redeploying everything
- Can become unwieldy as the codebase grows
Modular Monolith
What It Is
A monolith organized into well-defined modules with explicit boundaries. Each module owns its data and exposes a public API. Modules communicate through defined interfaces rather than reaching directly into each other's internals.
Structure
src/
modules/
auth/
public-api.ts # Exported interface
internal/ # Private implementation
auth.module.ts # Module registration
content/
public-api.ts
internal/
content.module.ts
billing/
public-api.ts
internal/
billing.module.ts
shared/ # Cross-cutting concerns
Key Rules
- Modules never access each other's database tables directly
- Communication happens through the public API only
- Shared code is limited to truly cross-cutting concerns
- Each module can be extracted into a service later if needed
This is often the ideal architecture for medium-sized teams. You get the operational simplicity of a monolith with the organizational benefits of service boundaries.
Microservices Architecture
What It Is
An application decomposed into independently deployable services, each owning its own data store and communicating over the network.
When to Choose It
- Large teams (multiple autonomous squads)
- Different scaling requirements per component
- Polyglot requirements (different languages per service)
- Regulatory requirements for data isolation
Communication Patterns
Synchronous (Request/Response)
Client -> API Gateway -> Service A -> Service B
<- Response
<- Aggregated Response
- REST or gRPC
- Simple to understand
- Creates coupling between services
- Cascading failures are a risk
Asynchronous (Event-Driven)
Service A -> Message Queue -> Service B
-> Service C
- Kafka, RabbitMQ, or SQS
- Services are decoupled
- Better resilience
- Harder to debug and trace
The Hidden Costs
Microservices introduce significant operational complexity:
- Service discovery: How do services find each other?
- Distributed tracing: How do you debug across services?
- Data consistency: No more database transactions across boundaries
- Deployment orchestration: Coordinating releases across services
- Network reliability: Retries, circuit breakers, timeouts
Do not adopt microservices unless you have the team and infrastructure to support them.
Serverless Architecture
What It Is
Application logic deployed as individual functions that execute in response to events. The cloud provider manages all infrastructure.
Platforms
| Platform | Provider | Language Support |
|---|---|---|
| Lambda | AWS | Node.js, Python, Go, Java, Rust |
| Cloud Functions | GCP | Node.js, Python, Go, Java |
| Azure Functions | Azure | Node.js, Python, C#, Java |
| Cloudflare Workers | Cloudflare | JavaScript, Rust (via Wasm) |
Best Use Cases
- Event-driven processing (file uploads, webhooks)
- APIs with unpredictable traffic patterns
- Scheduled tasks and cron jobs
- Prototypes and MVPs with uncertain traffic
Limitations
- Cold start latency (especially JVM languages)
- Execution time limits (15 minutes on Lambda)
- Vendor lock-in
- Difficult to test locally
- Complex state management
JAMstack / Edge-First Architecture
What It Is
Pre-rendered static content served from a CDN, enhanced with JavaScript and APIs. Modern frameworks like Next.js, Nuxt, and Astro blur the line between static and dynamic.
The Rendering Spectrum
| Strategy | When to Use |
|---|---|
| Static Generation (SSG) | Content that rarely changes (blog, docs) |
| Incremental Static Regen (ISR) | Content that changes periodically (product pages) |
| Server-Side Rendering (SSR) | Personalized or real-time content |
| Client-Side Rendering (CSR) | Highly interactive dashboards behind auth |
Edge Computing
Run server logic at CDN edge nodes close to users:
User (Tokyo) -> Edge Node (Tokyo) -> Origin (US) if needed
Response in ~20ms instead of ~200ms
Platforms: Vercel Edge Functions, Cloudflare Workers, Deno Deploy
Caveat: Edge runtimes have a limited API surface. No filesystem access, limited Node.js APIs, and database connections require connection pooling or HTTP-based databases.
Data Layer Patterns
Repository Pattern
Abstract data access behind an interface:
interface IContentRepository {
findById(id: string): Promise<Content | null>;
create(data: CreateInput): Promise<Content>;
}
class DrizzleContentRepository implements IContentRepository {
async findById(id: string) {
return db.query.contents.findFirst({
where: eq(contents.id, id),
});
}
}Benefits: testability, swappable implementations, clear data access boundaries.
CQRS (Command Query Responsibility Segregation)
Separate read and write models when read and write patterns diverge significantly:
Write Path: Command -> Validate -> Write to DB -> Emit Event
Read Path: Query -> Read from Optimized View/Cache -> Return
Use when reads and writes have different performance characteristics or when you need denormalized read models.
Event Sourcing
Store every state change as an immutable event rather than the current state. Current state is derived by replaying events. Powerful for audit trails and complex business logic, but adds significant complexity.
Caching Strategies
Cache Layers
Browser Cache -> CDN Cache -> Application Cache -> Database
(seconds) (minutes) (minutes-hours) (source of truth)
Strategies
| Strategy | Description | Use Case |
|---|---|---|
| Cache-Aside | App checks cache, falls back to DB | General purpose |
| Read-Through | Cache handles DB reads transparently | Simple read-heavy workloads |
| Write-Through | Writes go to cache and DB simultaneously | Consistency-critical data |
| Write-Behind | Writes go to cache, async flush to DB | High write throughput |
Security Architecture
Defense in Depth
WAF / DDoS Protection
API Gateway / Rate Limiting
Auth / Authorization
Input Validation
Application Logic
Every layer provides protection. Never rely on a single layer.
Choosing the Right Architecture
Decision Matrix
| Factor | Monolith | Modular Monolith | Microservices | Serverless |
|---|---|---|---|---|
| Team size | 1-5 | 3-15 | 10+ | 1-10 |
| Deployment | Simple | Simple | Complex | Simple |
| Scaling | Vertical | Vertical | Independent | Automatic |
| Complexity | Low | Medium | High | Medium |
| Operational cost | Low | Low | High | Variable |
| Time to market | Fast | Fast | Slow | Fast |
The Evolution Path
Most successful applications follow this progression:
1. Monolith (validate the idea)
2. Modular Monolith (organize as you grow)
3. Extract critical services (scale what needs scaling)
4. Microservices (only if team and traffic demand it)
Do not skip steps. Each stage builds the understanding needed for the next.
Conclusion
Good architecture is not about following trends. It is about making deliberate decisions that match your team's capabilities, your application's requirements, and your business constraints. Start simple, measure real bottlenecks, and evolve intentionally.