HomeBytesheetsRoadmapsUI TemplatesUI ToolsStack Discussion
3 Topics
Chapter•170 Minutes

API Application Architecture

Structure API applications into maintainable layers and understand separation of concerns.

Controller, Service and Repository Layers

Layered API Architecture

A common backend structure separates HTTP handling from business logic and persistence.

Controller: translates HTTP requests into application operations and formats responses.

Service: contains business rules and use-case orchestration.

Repository: abstracts persistence operations.

This prevents controllers from becoming large collections of database queries and business rules.

Layered API Architecture

Domain-Oriented API Design

As systems grow, organizing code only by HTTP routes may become insufficient.

Domain-oriented architecture groups functionality around business capabilities such as billing, identity, orders, inventory, or notifications.

The API layer becomes an adapter around application use cases rather than the center of the domain model.

This approach helps when the same business rules need to be triggered by HTTP APIs, queues, scheduled jobs, and event consumers.

Key Takeaways
  • HTTP should not own business rules.
  • Business operations can be invoked through multiple transport mechanisms.
  • Domain boundaries become increasingly important as systems grow.

Dependency Injection and Testability

#Services should depend on abstractions or injectable collaborators where practical.

For example, an order service can receive a payment gateway interface rather than directly importing a concrete Stripe or payment implementation.

Tests can then provide a fake implementation and test business behavior deterministically.

Dependency injection is not required for every small application, but explicit dependencies improve testability and architecture as complexity increases.

Service with Dependency Injection
class OrderService {
  constructor(orderRepository, paymentGateway) {
    this.orderRepository = orderRepository;
    this.paymentGateway = paymentGateway;
  }

  async pay(orderId) {
    const order = await this.orderRepository.findById(orderId);
    return this.paymentGateway.charge(order.total);
  }
}
💡 The service depends on collaborators supplied externally, making business logic easier to test.