Last updated: Apr 29, 2026
3-Layered Architecture
3-layered architecture divides an application into three horizontal layers — presentation, business logic, and data access — each with a distinct responsibility. The goal is isolation: changes to how data is stored should not ripple into how HTTP requests are handled, and vice versa.
How It Works
┌──────────────────────────────────────┐
│ Presentation │ ← Controllers, routes, UI
├──────────────────────────────────────┤
│ Business Logic │ ← Domain rules, validation, orchestration
├──────────────────────────────────────┤
│ Data Access │ ← Repositories, ORM, external APIs
└──────────────────────────────────────┘
▲ each layer calls only the one below
Each layer only knows about the layer directly below it, never above. The presentation layer calls business logic, business logic calls data access, but data access never reaches up into business logic, and business logic never imports anything from presentation. This one-way dependency chain keeps each layer replaceable without disturbing the others.
Presentation Layer
The presentation layer handles all interaction with the outside world — HTTP requests, CLI commands, UI rendering. It receives input, delegates work to the business logic layer, and formats the result for the caller. No domain rules live here.
import { OrderService } from "../services/OrderService";
async function createOrderHandler(req: Request): Promise<Response> {
const { customerId, items } = await req.json();
const order = await OrderService.create(customerId, items);
return new Response(JSON.stringify(order), { status: 201 });
}
The handler knows how to read a request and write a response, but the decision of whether the order is valid belongs entirely to OrderService.
Business Logic Layer
The business logic layer contains the domain rules, validation, and orchestration that make the application do what it actually does. It has no knowledge of HTTP, HTML, or SQL — it works with plain objects and calls the data access layer to persist results.
import { OrderRepository } from "../repositories/OrderRepository";
type OrderItem = { productId: string; quantity: number; unitPrice: number };
export class OrderService {
static async create(customerId: string, items: OrderItem[]) {
if (items.length === 0) {
throw new Error("Order must contain at least one item");
}
const total = items.reduce((sum, i) => sum + i.quantity * i.unitPrice, 0);
const order = { customerId, items, total, status: "created" as const };
return OrderRepository.save(order);
}
}
In stricter setups, OrderService depends on a repository interface rather than the concrete OrderRepository class. This inverts the dependency so the service can be tested with an in-memory stub instead of a real database.
Data Access Layer
The data access layer owns all persistence and external service communication — SQL queries, ORM calls, third-party API requests. It exposes simple read/write operations that the business logic layer consumes without knowing the underlying storage mechanism.
import { db } from "../db";
type Order = {
customerId: string;
items: { productId: string; quantity: number; unitPrice: number }[];
total: number;
status: "created";
};
export class OrderRepository {
static async save(order: Order) {
const [row] = await db.query(
`INSERT INTO orders (customer_id, items, total, status)
VALUES ($1, $2, $3, $4)
RETURNING *`,
[order.customerId, JSON.stringify(order.items), order.total, order.status],
);
return row;
}
static async findById(id: string) {
const [row] = await db.query(`SELECT * FROM orders WHERE id = $1`, [id]);
return row ?? null;
}
}
Trade-offs
| Benefit | Cost |
|---|---|
| Clear separation makes code navigable | Adds indirection for simple CRUD |
| Each layer testable in isolation | Interface boilerplate between layers |
| Swap infrastructure without touching logic | Risk of “pass-through” layers that add no value |
| Teams can work on layers independently | Cross-cutting concerns (auth, logging) don’t fit neatly |
The 3-layered architecture is the foundation that more sophisticated patterns — hexagonal architecture, clean architecture, DDD’s four-layer model — build on.