Layered/N-Tier Architecture
Layered architecture organizes code into horizontal layers, where each layer has a specific responsibility and only communicates with adjacent layers. In web development, this manifests as the separation of HTML (structure), CSS (presentation), and JavaScript (behavior)—the fundamental principle of progressive enhancement.
The Three Core Web Layers
Modern web development is built on three distinct layers that work together but remain independent:
Layer 1: HTML
Content & Structure
- Semantic markup
- Accessibility
- Works alone
- Screen reader friendly
Layer 2: CSS
Presentation & Design
- Visual styling
- Layout & spacing
- Responsive design
- Animations
Layer 3: JavaScript
Behavior & Interactivity
- User interactions
- Dynamic updates
- Data fetching
- Enhancement only
Layer 1: HTML - Structure and Content
HTML is the foundation layer. It must work on its own, without CSS or JavaScript.
<!-- LAYER 1: HTML - Content & Structure -->
<!-- Works without CSS or JavaScript -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Product Catalog</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<!-- Semantic HTML provides meaning and structure -->
<header>
<h1>My Shop</h1>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/cart">Cart</a></li>
</ul>
</nav>
</header>
<main>
<article class="product">
<h2>Wireless Headphones</h2>
<img src="headphones.jpg" alt="Black wireless headphones">
<p class="description">Premium noise-cancelling wireless headphones with 30-hour battery life.</p>
<p class="price">$299.99</p>
<!-- Forms work without JavaScript -->
<form method="POST" action="/cart/add">
<input type="hidden" name="productId" value="123">
<label for="quantity">Quantity:</label>
<input type="number" id="quantity" name="quantity" value="1" min="1" max="10">
<button type="submit">Add to Cart</button>
</form>
</article>
</main>
<footer>
<p>© 2024 My Shop. All rights reserved.</p>
</footer>
<!-- JavaScript is the last layer -->
<script src="app.js"></script>
</body>
</html>
/* PRINCIPLES OF HTML LAYER:
- Semantic elements (header, nav, main, article, footer)
- Accessible (works with screen readers)
- Forms function without JavaScript
- Content is readable without CSS
- Progressive enhancement foundation
*/ HTML Layer Principles
- Semantic meaning - Use appropriate elements (<header>, <nav>, <article>)
- Accessibility first - Screen readers rely on HTML structure
- Forms work - Submit to server without JavaScript
- Content readable - Makes sense without styling
- Progressive base - Foundation for enhancement
Layer 2: CSS - Presentation and Visual Design
CSS enhances HTML with visual design. The page remains functional if CSS fails to load.
/* LAYER 2: CSS - Presentation & Visual Design */
/* Enhances HTML, doesn't replace it */
/* CSS Variables for theming */
:root {
--color-primary: #0066cc;
--color-secondary: #333;
--color-bg: #ffffff;
--color-text: #333;
--spacing: 1rem;
}
/* Layout - Mobile First */
body {
font-family: system-ui, sans-serif;
line-height: 1.6;
color: var(--color-text);
background: var(--color-bg);
margin: 0;
padding: 0;
}
header {
background: var(--color-primary);
color: white;
padding: var(--spacing);
}
nav ul {
list-style: none;
padding: 0;
display: flex;
gap: var(--spacing);
}
nav a {
color: white;
text-decoration: none;
}
nav a:hover,
nav a:focus {
text-decoration: underline;
}
/* Product Card Styling */
.product {
border: 1px solid #ddd;
border-radius: 8px;
padding: var(--spacing);
margin: var(--spacing);
}
.product img {
max-width: 100%;
height: auto;
border-radius: 4px;
}
.price {
font-size: 1.5rem;
font-weight: bold;
color: var(--color-primary);
}
button {
background: var(--color-primary);
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
}
button:hover {
opacity: 0.9;
}
/* Responsive Design - Progressive Enhancement */
@media (min-width: 768px) {
.products {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: var(--spacing);
}
}
/* Print Styles - Different layer for different medium */
@media print {
nav, button {
display: none;
}
.product {
page-break-inside: avoid;
}
}
/* Dark Mode - Respects user preference */
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #1a1a1a;
--color-text: #ffffff;
}
}
/* Reduced Motion - Accessibility layer */
@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
transition: none !important;
}
}
/* PRINCIPLES OF CSS LAYER:
- Presentation separated from content
- Progressive enhancement (mobile-first)
- Responsive to screen size, user preferences
- Works independently of JavaScript
- Doesn't break if CSS fails to load
*/ CSS Layer Principles
- Presentation only - No behavior, no content
- Progressive enhancement - Mobile-first, feature queries
- User preferences - Respect prefers-color-scheme, prefers-reduced-motion
- Independent - Can be changed without touching HTML
- Graceful degradation - Fallbacks for unsupported features
Layer 3: JavaScript - Behavior and Enhancement
JavaScript adds interactivity but should never be required for core functionality.
// LAYER 3: JavaScript - Behavior & Interactivity
// Enhances HTML and CSS, doesn't replace them
// Feature Detection
if ('IntersectionObserver' in window) {
enableLazyLoading();
}
if ('fetch' in window && 'FormData' in window) {
enhanceForms();
}
// Progressive Enhancement: Forms work without JS,
// but we enhance them with AJAX when available
function enhanceForms() {
const forms = document.querySelectorAll('form[data-enhance]');
forms.forEach(form => {
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
const button = form.querySelector('button[type="submit"]');
try {
// Disable button during submission
button.disabled = true;
button.textContent = 'Adding...';
// Submit via AJAX
const response = await fetch(form.action, {
method: form.method,
body: formData
});
if (response.ok) {
showNotification('Added to cart!');
updateCartCount();
} else {
throw new Error('Failed to add to cart');
}
} catch (error) {
// Graceful degradation: Fall back to normal submission
console.error('AJAX failed, using standard submission:', error);
form.submit();
} finally {
button.disabled = false;
button.textContent = 'Add to Cart';
}
});
});
}
// Image Lazy Loading Enhancement
function enableLazyLoading() {
const images = document.querySelectorAll('img[data-src]');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
observer.unobserve(img);
}
});
});
images.forEach(img => observer.observe(img));
}
// Client-side filtering (enhancement, not requirement)
function enhanceProductFilters() {
const filterInput = document.querySelector('#filter');
const products = document.querySelectorAll('.product');
if (!filterInput) return;
filterInput.addEventListener('input', (e) => {
const query = e.target.value.toLowerCase();
products.forEach(product => {
const text = product.textContent.toLowerCase();
product.style.display = text.includes(query) ? '' : 'none';
});
});
}
// Cart count update (UI enhancement)
async function updateCartCount() {
try {
const response = await fetch('/api/cart/count');
const { count } = await response.json();
const badge = document.getElementById('cart-count');
if (badge) {
badge.textContent = count;
badge.classList.add('updated');
setTimeout(() => badge.classList.remove('updated'), 300);
}
} catch (error) {
console.error('Failed to update cart count:', error);
}
}
// Show temporary notifications
function showNotification(message) {
const notification = document.createElement('div');
notification.className = 'notification';
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.classList.add('fade-out');
setTimeout(() => notification.remove(), 300);
}, 3000);
}
// Initialize enhancements when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
enhanceForms();
enhanceProductFilters();
});
/* PRINCIPLES OF JAVASCRIPT LAYER:
- Enhances existing functionality
- Feature detection before use
- Graceful degradation on failure
- Site works without JavaScript
- No document.write() or innerHTML replacement of entire page
- Progressive enhancement, not dependency
*/ JavaScript Layer Principles
- Enhancement only - Site works without it
- Feature detection - Test before using features
- Graceful degradation - Fallback to standard behavior
- Unobtrusive - No inline onclick or style attributes
- Progressive - Add capabilities, don't replace core function
Layer Separation and Communication
Layers must remain separate with clear boundaries.
// LAYER SEPARATION: Clear Boundaries
// ❌ BAD: Mixing layers
<button onclick="alert('Hello')" style="color: red">Click</button>
// HTML has JavaScript (onclick) and CSS (style)
// Layers are tangled, hard to maintain
// ✅ GOOD: Separated layers
// HTML - Structure only
<button class="greeting-btn" data-action="greet">Click</button>
// CSS - Presentation only
.greeting-btn {
color: red;
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
}
// JavaScript - Behavior only
document.querySelector('[data-action="greet"]')
.addEventListener('click', () => {
alert('Hello');
});
// BENEFITS OF SEPARATION:
// - Each layer can be modified independently
// - CSS can be changed without touching HTML or JS
// - JavaScript can be disabled, HTML still works
// - Easier to test each layer separately
// - Better caching (CSS/JS in separate files)
// LAYER COMMUNICATION
// JavaScript reads from HTML via attributes
const button = document.querySelector('[data-product-id]');
const productId = button.dataset.productId;
// JavaScript modifies HTML via classes (not inline styles)
button.classList.add('loading');
button.classList.remove('loading');
// CSS responds to state classes
.btn.loading {
opacity: 0.5;
pointer-events: none;
}
.btn.loading::after {
content: '...';
animation: dots 1s infinite;
}
// JavaScript doesn't write CSS
// ❌ element.style.color = 'red'
// ✅ element.classList.add('error') Communication Rules
- HTML provides structure and data attributes
- CSS reads HTML via selectors and classes
- JavaScript reads HTML via DOM queries
- JavaScript modifies HTML via classes, not inline styles
- CSS responds to state via classes
- No mixing: No inline styles, no inline scripts, no styled components mixing concerns
N-Tier Architecture: Server and Client Layers
Beyond HTML/CSS/JS, applications have multiple logical tiers.
// N-TIER ARCHITECTURE
// Multiple layers with clear responsibilities
// TIER 1: Presentation Layer (Browser)
// =======================================
// HTML + CSS + JavaScript
// What the user sees and interacts with
class ProductView {
render(product) {
return `
<article class="product">
<h2>${product.name}</h2>
<img src="${product.image}" alt="${product.name}">
<p class="price">$${product.price}</p>
<button data-product-id="${product.id}">
Add to Cart
</button>
</article>
`;
}
showError(message) {
// Update UI to show error
}
showLoading() {
// Update UI to show loading state
}
}
// TIER 2: Application Layer (Client-side logic)
// =======================================
// Business logic, validation, state management
class ProductController {
constructor(productService, productView) {
this.service = productService;
this.view = productView;
}
async loadProduct(id) {
try {
this.view.showLoading();
// Validate input
if (!id || id < 1) {
throw new Error('Invalid product ID');
}
// Call service layer
const product = await this.service.getProduct(id);
// Update view
this.view.render(product);
} catch (error) {
this.view.showError(error.message);
}
}
async addToCart(productId) {
// Validate
// Call service
// Update view
}
}
// TIER 3: Service Layer (API communication)
// =======================================
// Communicates with backend
class ProductService {
constructor(apiUrl) {
this.apiUrl = apiUrl;
}
async getProduct(id) {
const response = await fetch(`${this.apiUrl}/products/${id}`);
if (!response.ok) {
throw new Error('Failed to fetch product');
}
return response.json();
}
async getProducts(filters) {
const params = new URLSearchParams(filters);
const response = await fetch(`${this.apiUrl}/products?${params}`);
return response.json();
}
}
// TIER 4: Backend API Layer (Server)
// =======================================
// Node.js/Express handling requests
const express = require('express');
const app = express();
// API routes
app.get('/api/products/:id', async (req, res) => {
try {
// Call business logic layer
const product = await productLogic.getProduct(req.params.id);
res.json(product);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// TIER 5: Business Logic Layer (Server)
// =======================================
// Core business rules and validation
class ProductLogic {
constructor(productRepository) {
this.repository = productRepository;
}
async getProduct(id) {
// Validate
if (!id) {
throw new Error('Product ID required');
}
// Get from data layer
const product = await this.repository.findById(id);
if (!product) {
throw new Error('Product not found');
}
// Apply business rules
product.price = this.calculatePrice(product);
product.inStock = product.quantity > 0;
return product;
}
calculatePrice(product) {
// Business logic for pricing
let price = product.basePrice;
if (product.onSale) {
price *= 0.8; // 20% off
}
return price;
}
}
// TIER 6: Data Access Layer (Server)
// =======================================
// Database queries
class ProductRepository {
constructor(db) {
this.db = db;
}
async findById(id) {
return this.db.query(
'SELECT * FROM products WHERE id = ?',
[id]
);
}
async findAll(filters) {
// Build query based on filters
// Execute query
// Return results
}
async create(product) {
// Insert into database
}
async update(id, data) {
// Update database
}
}
// TIER 7: Database Layer
// =======================================
// PostgreSQL, MySQL, MongoDB, etc.
/* BENEFITS OF N-TIER:
- Clear separation of concerns
- Each tier can be tested independently
- Tiers can be scaled independently
- Technology can be swapped per tier
- Multiple teams can work on different tiers
COMMUNICATION FLOW:
User → Presentation → Application → Service → API
API → Business Logic → Data Access → Database
Database → Data Access → Business Logic → API
API → Service → Application → Presentation → User
*/ Common Tiers
- Presentation - UI (HTML/CSS/JS)
- Application - Client-side logic
- Service - API communication
- API - Backend HTTP endpoints
- Business Logic - Core rules and workflows
- Data Access - Database queries
- Database - Data storage
Web-Specific Layering Patterns
// WEB-SPECIFIC LAYERING PATTERNS
// PATTERN 1: CSS Layers (Modern CSS)
// =======================================
@layer reset, base, components, utilities;
@layer reset {
/* CSS Reset - lowest priority */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
}
@layer base {
/* Base styles */
body {
font-family: system-ui;
line-height: 1.6;
}
}
@layer components {
/* Component styles */
.btn {
padding: 0.5rem 1rem;
border-radius: 4px;
}
}
@layer utilities {
/* Utility classes - highest priority */
.hidden {
display: none !important;
}
}
// PATTERN 2: JavaScript Module Layers
// =======================================
// Layer: UI Components
// ui/Button.js
export class Button {
constructor(element) {
this.element = element;
}
disable() {
this.element.disabled = true;
this.element.classList.add('disabled');
}
}
// Layer: Business Logic
// logic/CartManager.js
export class CartManager {
constructor() {
this.items = [];
}
addItem(item) {
// Business rules
if (this.items.length >= 10) {
throw new Error('Cart is full');
}
this.items.push(item);
}
}
// Layer: API Client
// api/ProductAPI.js
export class ProductAPI {
async getProduct(id) {
const response = await fetch(`/api/products/${id}`);
return response.json();
}
}
// Layer: Application (wires everything together)
// app.js
import { Button } from './ui/Button.js';
import { CartManager } from './logic/CartManager.js';
import { ProductAPI } from './api/ProductAPI.js';
class App {
constructor() {
this.cart = new CartManager();
this.api = new ProductAPI();
this.initUI();
}
initUI() {
document.querySelectorAll('.add-to-cart').forEach(btn => {
const button = new Button(btn);
btn.addEventListener('click', async () => {
button.disable();
const productId = btn.dataset.productId;
const product = await this.api.getProduct(productId);
try {
this.cart.addItem(product);
this.showSuccess();
} catch (error) {
this.showError(error.message);
} finally {
button.enable();
}
});
});
}
}
// PATTERN 3: Content Layer Separation
// =======================================
// Layer 1: Content (Markdown/CMS)
// content/products/headphones.md
---
title: Wireless Headphones
price: 299.99
category: audio
---
Premium noise-cancelling wireless headphones...
// Layer 2: Template (HTML structure)
// templates/product.html
<article class="product">
<h1>{{ title }}</h1>
<p class="price">{{ price }}</p>
<div class="content">
{{ content }}
</div>
</article>
// Layer 3: Styling (CSS)
// styles/product.css
.product { /* styles */ }
// Layer 4: Enhancement (JavaScript)
// scripts/product.js
// Add interactivity
/* BENEFITS:
- Content editors work in Markdown
- Designers work in templates/CSS
- Developers work in JavaScript
- Each layer independent
- Easy to modify one without affecting others
*/ Benefits of Layered Architecture
- Separation of concerns - Each layer has one job
- Independent changes - Modify CSS without touching JS
- Progressive enhancement - Natural fit for web development
- Testability - Test layers in isolation
- Team division - Different teams on different layers
- Reusability - Layers can be shared across projects
- Maintainability - Clear boundaries, easier debugging
- Scalability - Scale layers independently
Drawbacks
- Complexity - More files and abstractions
- Performance - Multiple file requests (mitigated by bundling)
- Overhead - Communication between layers adds code
- Rigidity - Strict separation can be limiting
Modern Variations
- CSS-in-JS - Co-locates styles with components (React, Styled Components)
- Utility-first CSS - Tailwind mixes HTML and styling concerns
- Web Components - Encapsulates HTML/CSS/JS together
- Single File Components - Vue's .vue files contain all layers
These approaches trade strict separation for developer convenience and component encapsulation, with trade-offs in progressive enhancement and accessibility.
When to Use Layered Architecture
- Progressive enhancement - Core requirement
- Accessibility - Must work without JavaScript
- SEO - Content must be in HTML
- Large teams - Different specializations
- Long-term maintenance - Needs clear boundaries
- Public websites - Diverse users and devices
Related Patterns
- Progressive Enhancement - Building up from HTML layer
- MVC - Similar separation at application level
- Component-Based - Modern alternative with encapsulation