Design Layered Backend Architecture
Purpose
Define a repeatable backend service structure that keeps HTTP concerns, business logic, and data access cleanly separated.
When to use
Use this skill when you are:
- •Starting a new backend service or module
- •Refactoring an existing codebase toward clearer boundaries
- •Reviewing architecture for maintainability and testability
- •Defining team-wide conventions for “where code goes”
Inputs
- •Primary service responsibilities and domain boundaries
- •The external interfaces (HTTP endpoints, jobs, events)
- •Persistence model and integration points
- •Existing constraints (framework, ORM/query layer, deployment model)
Outputs
- •A layering blueprint with clear responsibilities
- •A directory/module layout that supports the blueprint
- •A request lifecycle map (how a request flows through layers)
- •A list of architectural invariants to enforce in code review
Core model
A typical layered service uses four layers:
- •
Routes
- •Define paths/methods
- •Register middleware
- •Delegate to controllers
- •MUST NOT implement business logic
- •
Controllers
- •Validate and normalize inputs
- •Translate domain results to HTTP responses
- •Map errors to status codes and error shapes
- •SHOULD be thin and focused on HTTP
- •
Services
- •Implement business rules and orchestration
- •Call repositories and other services
- •MUST NOT depend on HTTP objects
- •
Repositories
- •Encapsulate persistence logic
- •Implement queries, transactions, and mappings
- •SHOULD hide ORM/query details from services
Architectural invariants
- •Each layer MUST have a single primary responsibility.
- •Dependencies MUST flow inward:
- •routes → controllers → services → repositories
- •Services MUST be unit-testable without HTTP or database.
- •Data access SHOULD be testable via repository tests or integration tests.
Decision checklist
Use these decisions to avoid ambiguous boundaries.
- •
Do we need a repository layer?
- •MAY be skipped for simple CRUD.
- •SHOULD be added once queries become non-trivial, transactional, or reused.
- •
Where do we validate input?
- •SHOULD be at the controller boundary (closest to untrusted input).
- •
Where do we implement authorization?
- •If the rule depends on HTTP context, keep enforcement in middleware/controller.
- •If the rule is domain-specific, enforce in services as well.
Steps
- •Identify the public interfaces (endpoints/jobs/events).
- •Define domain services and responsibilities.
- •Define repository boundaries (per aggregate or per module).
- •Define controller responsibilities and error mapping contract.
- •Define route layout and middleware order.
- •Document the request lifecycle and verification checkpoints.
- •Add templates to standardize new modules.
Verification
- • New endpoints follow the dependency direction (routes → controllers → services → repositories)
- • Controllers contain no data access queries
- • Routes contain no business logic
- • Services contain no HTTP concepts (
req,res) - • At least one service is unit-tested in isolation
- • Build passes after applying the architecture
Boundaries
- •MUST NOT allow dependencies to flow outward (repositories calling controllers)
- •MUST NOT embed business logic in routes or controllers
- •MUST NOT introduce circular dependencies between layers
- •SHOULD NOT skip repository layer for non-trivial queries
- •SHOULD NOT mix multiple responsibilities in a single layer
- •SHOULD NOT bypass architectural invariants without documented justification
Included assets
- •Examples: see
./examples/for request lifecycle and module layout blueprints.