Layered Architecture Design Pattern
Early in my career, I worked on a Node.js app where route handlers contained SQL queries, business logic, and HTML rendering — all in the same function. C...
23 Mar 2024

Early in my career, I worked on a Node.js app where route handlers contained SQL queries, business logic, and HTML rendering — all in the same function. Changing a database column meant grepping through route files. Adding a validation rule meant touching the same files. Everything was tangled.
Layered Architecture untangles it by dividing your application into horizontal layers, each with a single responsibility. Higher layers call lower layers. Lower layers never call up.
Think of it like a kitchen brigade. The head chef (controller) takes the order, passes it to the sous chef (business logic), who tells the prep cook (data access) what to get from the pantry. The prep cook doesn't talk to customers. The head chef doesn't chop vegetables.
The Standard Layers
Presentation Layer. Handles user interaction. In a web app, this is your routes and controllers. It receives HTTP requests, calls the business layer, and formats responses. No business logic here.
Business Logic Layer. The rules. Validation, calculations, workflows, authorization decisions. This layer is the core of your application. It should work the same whether it's called by an HTTP controller, a CLI command, or a message queue consumer.
Data Access Layer. Talks to the database. Queries, inserts, updates, deletes. Exposes clean methods like findUserById() and createOrder(). No SQL in the business layer. No business rules in the data layer.
How I Structure It in Practice
/src
/controllers — Parse requests, call services, return responses
/services — Business logic, validation, orchestration
/repositories — Database queries, ORM calls
/models — Data structures, domain entities
A request flows top-down: Controller -> Service -> Repository -> Database. Each layer only talks to the one directly below it.
Why This Works
- Testability. You can unit test services without an HTTP server. You can test repositories without business rules.
- Swappability. Replace Postgres with MongoDB? Change the repository layer. The service layer doesn't care.
- Onboarding. New developers know where to look. Business bug? Check services. Query problem? Check repositories.
When It Breaks Down
- Cross-cutting concerns. Logging, auth, and caching don't fit neatly into one layer. You end up using middleware or aspect-oriented patterns alongside layered architecture.
- Performance. Strict layering can force you through unnecessary abstractions. Sometimes a controller needs to run a raw SQL query for performance. Pragmatism should win over purity.
- Distributed systems. Layers assume a single deployable unit. In a microservices world, each service might have its own layers, but the communication between services doesn't follow the layered model.
The benefit: Clear separation of concerns. Easy to understand, easy to test, easy to maintain. It's the default architecture for good reason — it works for most applications.
The cost: Can lead to "pass-through" layers where a service method does nothing but call the repository. For simple CRUD, layered architecture adds files without adding value. And strict layering can fight performance when you need to bypass abstractions.
I use layered architecture as my default for backend applications. It's simple, proven, and scales well for teams. When the app outgrows it, I refactor specific bounded contexts into more specialized architectures (hexagonal, CQRS) while keeping the rest layered.
Keep reading
- Event Sourcing Checklist: When It Makes Sense & Common Pitfalls
- Error Boundary React Design Pattern
- Context API React Design Pattern
- Higher-Order Component (HOC) React Design Pattern
- Container Component React Design Pattern
- Best Practices for Structuring Express.js Applications with Prisma Design Pattern