Backend Patterns
Structural and behavioral patterns for server-side systems. Covers how HTTP requests flow through a server, how business logic is organized, and how external dependencies are managed. Focus is on decision criteria and anti-patterns — not just definitions.Agent Trigger
Apply when: Writing or reviewing server-side middleware, auth, service/repository layers, queues/workers, or API gateways. Rule of thumb: Enforce auth at the right layer, keep service logic out of controllers, design queues for at-least-once + DLQ.Middleware Chains
Middleware is a function that sits in the request/response pipeline and either handles a concern or passes control to the next middleware.- Logger first: captures all requests before any early exit
- Auth before business logic: reject unauthenticated requests early — saves computation
- Rate limiter before auth: prevents auth endpoint abuse without touching the identity system
- Validator just before the handler: auth is confirmed, now check the request shape
- Error middleware last (Express/Node convention): catches errors bubbled from all prior middleware
next() after sending a response. Calling next() after a response causes double-response bugs.
Error middleware signature (Express): takes (err, req, res, next) — four arguments. The framework uses the arity to distinguish it from normal middleware. Place it after all routes.
Anti-patterns:
- Business logic inside middleware — middleware handles cross-cutting concerns only (auth, logging, rate-limiting, parsing)
- Deeply nested middleware sharing mutable state via the request object — use typed context instead
- Auth middleware that silently passes on failure instead of returning 401/403
Authentication and Authorization
Two distinct concerns often conflated:- Authentication (authn): who are you? (verify identity)
- Authorization (authz): are you allowed? (verify permission)
JWT vs Session Tokens
JWT placement:
Authorization: Bearer <token> header, not a cookie (avoids CSRF). Access tokens short-lived (15m–1h), refresh tokens long-lived (days–weeks) stored securely server-side or in HttpOnly cookie.
OAuth2 Flows
- Authorization Code + PKCE: web apps and mobile apps authenticating on behalf of a user. Redirect-based; PKCE required for public clients.
- Client Credentials: service-to-service (no user involved). Backend calls another backend with
client_id+client_secret. - Device Code: TV/CLI flows where user opens a browser separately.
- Never use the Implicit flow — deprecated; leaks tokens in URL fragments.
RBAC vs ABAC
RBAC (Role-Based Access Control):- User has roles; roles have permissions
- Simple to implement, easy to audit
- Fails when permissions need context:
user can edit their own posts but not others'
- Decision based on attributes: subject attrs + resource attrs + environment attrs
- Expressive:
user.department == resource.department AND time < 18:00 - Complex to implement and audit
- Use when RBAC requires an explosion of roles to represent contextual rules
- Authentication: middleware layer (verify token, attach identity to request context)
- Coarse-grained authorization (role check): middleware or route-level guard
- Fine-grained authorization (resource ownership, ABAC): service layer, not the handler. The handler doesn’t know enough about the domain to make the call correctly.
Service Layer Pattern
Separates HTTP concerns (parsing, serialization, status codes) from business logic.- Business logic in handlers cannot be tested without standing up an HTTP stack
- Logic duplicated across multiple routes (REST + GraphQL + CLI) if it lives in handlers
- Mixed concerns make it impossible to change HTTP framework without rewriting logic
Repository Pattern
Abstracts data access behind an interface. The rest of the application talks to the repository interface, not to the database driver.- Simple CRUD with no domain logic — the abstraction adds ceremony for no gain
- When you need database-specific features (complex CTEs, COPY, full-text search) that don’t map cleanly to a generic interface — wrap those queries in a specialized query object instead, or expose them explicitly
Queue / Worker Patterns
Offload work that doesn’t need to complete within the HTTP request cycle.At-Least-Once Delivery
Most queues (SQS, RabbitMQ, Kafka) guarantee at-least-once delivery — the same message may be delivered more than once (due to network errors, retries, or crashes before acknowledgment). Idempotent consumers: design workers so that processing the same message twice produces the same result as processing it once. Common techniques:- Deduplicate using a message ID stored in the database (insert-if-not-exists before processing)
- Use idempotency keys for external API calls
- Make operations naturally idempotent:
SET status = 'shipped'rather thanincrement shipment_count
Dead-Letter Queues (DLQ)
Messages that fail repeatedly (after N retries) move to a DLQ instead of being dropped. A DLQ is the operational safety net — inspect failed messages, fix the bug, then replay. Always configure a DLQ. Without one, poison messages (malformed or unprocessable) block the queue or silently disappear.Task Queue vs Event Stream
Dependency Injection
Pass dependencies in rather than constructing them inside a component. Decouples components from their concrete implementations. Three forms:- Constructor injection (preferred): dependencies declared in constructor signature. Mandatory, visible, testable.
- Property injection: set via public property after construction. Allows partial construction — harder to reason about.
- Method injection: pass dependency at call time. Use only when the dependency varies per-call.
API Gateway Pattern
A single entry point in front of multiple backend services (common in microservices). Functions at the gateway:- Request routing: route
/orders/*to order service,/users/*to user service - Auth at the edge: verify JWTs once at the gateway; forward verified identity (e.g.,
X-User-Idheader) to services — services trust the gateway, not raw clients - Rate limiting: enforce per-client or per-IP limits globally without duplicating logic in every service
- Aggregation: compose multiple service calls into a single response (BFF variant)
- Protocol translation: REST → gRPC, WebSocket upgrade
Backend Anti-Patterns
Fat Controllers
Controllers that contain business logic, data access, and HTTP handling all in one function. Symptoms: handlers over 50 lines, directdb.query() calls inside route handlers, duplicated logic across routes.
Fix: extract a service layer; move DB access to a repository.
Direct DB Calls from Handlers
Synchronous Chains That Should Be Async
Sending an email, resizing an image, or calling a slow third-party API inside the HTTP request cycle. Each adds latency and holds a connection open. Rule of thumb: if the user doesn’t need the result to continue, it belongs in a queue.Missing Input Validation
Never trust request data. Validate at the boundary — before it reaches the service layer. Unvalidated input causes type errors deep in domain code, is a primary injection vector, and produces confusing 500s instead of 400s. Use a schema library (Zod, Joi, class-validator) at the handler layer. The service layer should receive clean, typed data — not raw request bodies.Anemic Service Layer
Services that are just pass-throughs to repositories with no logic. Usually a symptom of putting logic back in handlers or fat repositories. Domain invariants (business rules) belong in the service, not scattered across callers.Cross-references
- API Design Patterns — RESTful resource design, error response shape, versioning, idempotency keys
- Software Design Principles — SOLID underpins DI (Dependency Inversion), SRP (thin controllers), OCP (repository interface)
- Distributed Systems — idempotency, saga, circuit breaker — required when workers call external services
- Architectural Patterns — hexagonal architecture is the generalization of the service layer / repository boundary
- Database Patterns — repository implementations: N+1, connection pooling, query optimization
- Error Handling Patterns — error taxonomy, retry/backoff for queue workers