Skip to content

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.

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, /products all live in the same codebase.

Good for: early-stage products, small teams, and learning.

A modular monolith is still one deployed application, but internally split into clear modules (often called “bounded contexts”):

  • users/ module owns user logic
  • billing/ module owns payments
  • catalog/ 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.

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.

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-service owns the Users DB.
  • orders-service owns the Orders DB.
  • They communicate via HTTP/gRPC or a message broker.

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.

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.

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-service publishes OrderCreated.
  • email-service listens and sends a confirmation email.

This reduces direct dependencies between services, but introduces “eventual consistency”.

  • Just starting: monolith or modular monolith.
  • Multiple teams + independent scaling: microservices.
  • Many integrations / async workflows: event-driven architecture.
  • Spiky traffic + small functions: serverless.
  1. Monolith (start): one backend + one database.
  2. Modular monolith (next): split into modules: users, catalog, orders, billing.
  3. Microservices (later): extract billing and notifications first (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-service stores the order and publishes OrderCreated.
  • payments-service listens, charges the card, publishes PaymentSucceeded or PaymentFailed.
  • email-service listens and sends confirmation.

Trade-off: the UI might show “processing” for a short time because systems become eventually consistent.

Built with passion by Ngineer Lab