During the coding work for the Weather project, we encountered several problems, such as needing to change the frontend code to adapt to the backend API, or sometimes the opposite. These kinds of problems led us to a new topic to think about — boundaries. In simple terms, a boundary means how to divide arrangements and responsibilities in the architecture.

An Example

One day, my group mate came to me and said, “Can you please provide an API that gives weather data for a dozen cities?” I understood what he wanted to do, because if I could provide a backend API like that, the frontend wouldn’t need to call the backend several times to get the weather data. But why do this? Here is my thinking:

Good side: Friendly for the frontend
Bad side: Large backend queries, slower API response, no support for concurrent queries, too much coupling, not easy to maintain in the future

Because the weaknesses were greater than the strengths, I refused the request and explained the reason to my group mate. In the end, we agreed — no changes were needed.

Boundary principles

After some investigation, this is what I found and I believe it makes sense for frontend and backend collaboration.

Separation of concerns

  • Backend is responsible for: business logic, data access and persistence, authentication, authorization, API design and versioning, performance and reliability of data services
  • Frontend is responsible for: UI/UX and rendering, state management and routing, user input validation, displaying backend data, handling client-side performance

By keeping these concerns separate, teams can evolve independently and reduce cross-cutting impact.


Contract-First API design
Treat the API as a contract. Agree on request/response formats before implementation. This approach:

  • Reduces back-and-forth between teams
  • Supports parallel development (mock APIs)
  • Enables better testability
  • Promotes backward compatibility

Tools like OpenAPI/Swagger or GraphQL schemas formalize this contract and make the boundary explicit.


Minimize data coupling
Avoid exposing raw database models to the frontend. Instead, provide purpose-built DTOs (Data Transfer Objects) or response shapes that match the frontend needs—while abstracting internal logic.

Benefits:

  • Freedom to change backend internals without breaking FE
  • Reduced payload size (only necessary data)
  • Better security (hide internal fields or logic)

Backend for Frontend (BFF) pattern
Sometimes, FE needs are very specific (e.g., aggregate data from multiple services). Instead of breaking backend boundaries, introduce a BFF layer:

  • It acts as a translator between core backend services and the frontend.
  • It’s tightly coupled with FE but decoupled from core business logic.
  • This enables flexibility for FE needs without polluting backend APIs.

Idempotency and statelessness
Design BE APIs to be stateless and idempotent wherever possible. This simplifies:

  • Retry logic from FE
  • Testing and caching
  • Scalability (horizontal BE scaling)

FE should handle UI state and communicate clear expectations to users (e.g., loading states, retrying behavior).


Security at the boundary
FE is public; BE is private. Therefore:

  • Do not trust any data from the FE. Always validate and sanitize inputs.
  • Handle authentication and authorization in BE.
  • Minimize sensitive logic on the client (no secrets, no access control logic).
  • Use HTTPS and consider rate limiting, CSRF/XSS protection.

Error and logging protocols
Standardize how errors and logs flow across boundaries:

  • Use consistent error codes/messages for API responses.
  • Log request context (e.g., user ID, endpoint, timestamp) on the backend.
  • FE should gracefully handle known error types and display user-friendly messages.

Versioning and evolution
Boundaries should support change without breaking clients. Use:

  • API versioning strategies (e.g., /v1/, media types, or GraphQL schema evolution)
  • Feature flags in FE to handle rollout changes
  • Clear deprecation paths and communication

Some findings and thoughts

  • It is more important to define a clear boundary between the frontend and backend.
  • Using BFF could be a better way to solve the example problem.