Layered Architecture

Organizing code into layers — like presentation, business logic, and data access — where each layer only talks to the one next to it.

What is it?

As an app grows, it typically ends up doing three broad kinds of work: showing things to the user (screens, API responses), deciding what should happen based on business rules (is this order allowed? what's the discount?), and reading or writing data somewhere durable (a database, a file, another service). If those three kinds of work are free to call each other in any direction — a screen component directly running a SQL query, a database function containing pricing rules — the codebase turns into a web where nobody can change one thing without tracing through the entire system.

Layered architecture organizes the codebase into a stack of layers, commonly: a presentation layer (UI or API endpoints), a business logic layer (the actual rules and decisions of the application, also called the domain or service layer), and a data access layer (reading and writing to storage). The core rule is that each layer only talks to the layer directly below (or above) it — the presentation layer never touches the database directly; it goes through business logic, which goes through data access.

This isn't just a filing convention. It means if you need to change how data is stored — switch databases, add caching — you only need to touch the data access layer, because nothing above it depends on the storage details. And if you need to change how something is displayed, you never risk breaking a business rule, because the presentation layer doesn't contain any.

Explain like I'm 10

A company's org chart: the front-desk staff (presentation) don't decide company policy or personally handle the filing cabinets — they escalate to managers (business logic), who decide what should happen and instruct records staff (data access) to look something up or file something.

Examples

Before: layers bypassed

// api/getUserProfile.js  (presentation layer)
app.get("/profile/:id", async (req, res) => {
  const row = await db.query("SELECT * FROM users WHERE id = ?", [req.params.id]);
  const discount = row.isVip ? 0.2 : 0; // business rule, sitting in the API handler
  res.json({ name: row.name, discount });
});

The presentation layer talks directly to the database and contains a business rule (the VIP discount). Changing the database schema or the discount rule both require editing this same file.

After: layers respected

// dataAccess/userRepository.js
export async function getUserById(id) {
  return db.query("SELECT * FROM users WHERE id = ?", [id]);
}

// business/userService.js
export async function getUserProfile(id) {
  const user = await getUserById(id);
  const discount = user.isVip ? 0.2 : 0;
  return { name: user.name, discount };
}

// api/getUserProfile.js  (presentation layer)
app.get("/profile/:id", async (req, res) => {
  const profile = await getUserProfile(req.params.id);
  res.json(profile);
});

The API handler no longer knows SQL exists, and the repository no longer knows what a 'discount' is. Each layer can change independently — swap the database, or change the discount rule — without touching the other two.

How it works

Layers are typically arranged so that dependencies flow in one direction: presentation depends on business logic, business logic depends on data access — but never the reverse, and never skipping a layer. A useful mental check: if you can point to code in the data access layer that would need to change because the UI changed, or code in the presentation layer that contains a business rule, a layer has been skipped or its responsibility has leaked into the wrong place.

Presentation Layer   (API routes / UI)
          |
          v
  Business Logic Layer (rules, decisions)
          |
          v
  Data Access Layer    (database, files, external APIs)

Why does it exist?

Layering exists so that a change with one kind of cause — a UI redesign, a new business rule, a database migration — stays contained to one part of the codebase. Without layers, those three kinds of change get smeared across the same files, and every change risks touching logic it has nothing to do with. It also makes the codebase predictable: any engineer can guess where a given piece of logic lives just from knowing what kind of concern it is.

When to use it

Layering pays off once an application has any real business logic worth protecting — anything beyond the simplest CRUD passthroughs. It's one of the first structural decisions worth making deliberately in a growing codebase, and it scales down easily (three folders and a convention) as well as up (formal boundaries enforced by tooling).

When not to use it

For a trivial CRUD script with no real business rules — where the 'business logic' would just be 'call the database and return the result' — inserting a full business logic layer adds ceremony with no payoff. It's also possible to over-layer: forcing a rigid three-layer split on a tiny module can add indirection without adding any real protection against change.

Common mistakes

  • Letting the presentation layer skip business logic and call the data access layer directly 'just this once' — these exceptions accumulate until the layering is meaningless.

  • Putting business rules inside the data access layer (e.g., a repository function that applies a discount) because it was convenient at the time.

  • Creating layers in name only — three folders that still freely import each other in every direction, giving the appearance of structure without its benefit.

Practice exercises

  1. Easy:

    Take a function that fetches a product from a database and applies a sale-price calculation in the same block, and split it into a data access function and a business logic function.

  2. Medium:

    Design the three layers (presentation, business logic, data access) for a 'submit a support ticket' feature. List what each layer is responsible for and what it's explicitly not allowed to do.

  3. Hard:

    Review a real API endpoint you've written or can find in an open-source project. Identify any place a layer is being skipped or a responsibility has leaked into the wrong layer, and propose a fix.

Interview questions

What is layered architecture?

An approach to organizing a codebase into layers — commonly presentation, business logic, and data access — where each layer has a distinct responsibility and only communicates with the adjacent layer, keeping dependencies flowing in one direction.

Why shouldn't the presentation layer talk directly to the database?

Because it couples the UI or API to storage details and skips wherever business rules are supposed to live, making it easy for business logic to leak into presentation code and for database changes to ripple into unrelated places.

What's a sign that a layered architecture isn't actually being respected?

Business rules appearing inside the data access layer, or the presentation layer directly querying storage — both mean a layer's boundary is being bypassed even though the folders exist.