Architecture Patterns
This content is for Backend. Switch to the latest version for up-to-date documentation.
How you structure your backend so it stays understandable as it grows.
Architecture patterns are trade-offs: you pick the shape that matches your team size, product complexity, and scaling needs.
Monolith
Section titled “Monolith”The entire application (frontend, backend, database code) is in one project and deployed together.
- Pros: Easy to start, easy to deploy initially.
- Cons: Hard to scale. If one part breaks, everything breaks.
Example
- One repo, one backend service (
api), one database. - Routes like
/users,/orders,/productsall live in the same codebase.
Good for: early-stage products, small teams, and learning.
Modular Monolith
Section titled “Modular Monolith”A modular monolith is still one deployed application, but internally split into clear modules (often called “bounded contexts”):
users/module owns user logicbilling/module owns paymentscatalog/module owns products
Why it matters: it keeps the simplicity of one deployment, but reduces “spaghetti code”.
Example
- A single Express app, but you organize by feature modules and enforce module boundaries.
Layered Architecture (Common)
Section titled “Layered Architecture (Common)”A very common structure is “layers”:
- Controllers / Routes: HTTP details (URLs, status codes)
- Services: business logic (rules)
- Data Access / Repositories: database queries
Example
POST /orders-> controller validates basic shape -> service checks inventory -> repository writes to DB.
Microservices
Section titled “Microservices”Breaking the app into many small services (e.g., User Service, Payment Service) that talk to each other.
- Pros: Scale parts independently. Different teams can work on different services.
- Cons: Very complex to manage.
What becomes harder:
- Service-to-service auth
- Debugging (distributed logs/tracing)
- Data consistency across services
- Local development (many things to run)
Example
users-serviceowns the Users DB.orders-serviceowns the Orders DB.- They communicate via HTTP/gRPC or a message broker.
Serverless
Section titled “Serverless”You write simple functions (e.g., AWS Lambda) and the cloud provider runs them only when needed. You don’t manage servers.
Typical uses
- Webhooks
- Background jobs
- “Glue code” integrations
Example
- A function
onPaymentSucceeded()updates an order status after Stripe webhook events.
Service-Oriented Architecture (SOA)
Section titled “Service-Oriented Architecture (SOA)”SOA is an older (but still relevant) approach where an application is built from reusable services that communicate over a network (often via an Enterprise Service Bus / shared integration layer).
- Compared to microservices: SOA services are often larger and share more infrastructure; microservices tend to be smaller and independently deployable.
Event-Driven Architecture (EDA)
Section titled “Event-Driven Architecture (EDA)”In an event-driven system, services communicate by publishing events like “OrderCreated” or “UserRegistered”. Other services subscribe and react.
- Often implemented via a message broker (RabbitMQ, Kafka).
Example
orders-servicepublishesOrderCreated.email-servicelistens and sends a confirmation email.
This reduces direct dependencies between services, but introduces “eventual consistency”.
Quick Guidance (Picking a Pattern)
Section titled “Quick Guidance (Picking a Pattern)”- Just starting: monolith or modular monolith.
- Multiple teams + independent scaling: microservices.
- Many integrations / async workflows: event-driven architecture.
- Spiky traffic + small functions: serverless.
Mini Case Studies
Section titled “Mini Case Studies”Case Study 1: E-Commerce Growth Path
Section titled “Case Study 1: E-Commerce Growth Path”- Monolith (start): one backend + one database.
- Modular monolith (next): split into modules:
users,catalog,orders,billing. - Microservices (later): extract
billingandnotificationsfirst (they often have very different scaling and reliability needs).
Why this order works: you keep deployment simple early, and only pay the microservices complexity cost when multiple teams and scaling pressure justify it.
Case Study 2: Order Processing with Events
Section titled “Case Study 2: Order Processing with Events”You want to avoid a big synchronous chain like “orders calls payments calls email calls shipping”.
orders-servicestores the order and publishesOrderCreated.payments-servicelistens, charges the card, publishesPaymentSucceededorPaymentFailed.email-servicelistens and sends confirmation.
Trade-off: the UI might show “processing” for a short time because systems become eventually consistent.