Skip to main content

SECTION 0 — SENIORS SHIP CONTRACTS, NOT ENDPOINTS

The fastest teams are not the ones with the best framework.

They’re the ones with the cleanest contracts between:

  • UI ↔︎ API

  • service ↔︎ service

  • sync ↔︎ async boundaries

This chapter is a practical contract playbook.


SECTION 1 — CONTRACT PRINCIPLES

  1. Explicit beats implicit (states, errors, versioning).

  2. Backward compatible by default (additive changes).

  3. Idempotent where users retry.

  4. Partial failure is a first-class outcome.

  5. Contracts encode semantics, not implementation.


SECTION 2 — API SHAPE GUIDELINES

Responses

  • stable envelope: { data, error, meta } (or a consistent variant)

  • include requestId for support/on-call correlation

Error taxonomy

Use typed errors; don’t rely on free-form strings.

Example:

  • code: VALIDATION_FAILED | AUTH_REQUIRED | FORBIDDEN | NOT_FOUND | CONFLICT | RATE_LIMITED | INTERNAL

  • details: field-level validation info

  • retryable: boolean

Senior rule:

If the UI can’t decide “retry vs prompt user vs escalate” you didn’t define errors.


SECTION 3 — VERSIONING (HOW TO EVOLVE WITHOUT BREAKING)

Preferred approach:

  • keep URLs stable

  • evolve response schema additively

  • deprecate fields with a long window

When you need a true break:

  • new route or explicit version: /v2/...

  • support both versions until clients roll

Mobile reality:

  • assume old clients live for weeks/months

SECTION 4 — PAGINATION CONTRACTS

Use cursor-based pagination for large lists.

Contract:

  • request: limit, cursor

  • response: items, nextCursor

Cursor rules:

  • opaque

  • tied to filters/sort (include hash)

  • stable tie-breaker

Avoid:

  • offset pagination at scale

  • “page=3” without deterministic ordering


SECTION 5 — PARTIAL FAILURE PATTERNS

Partial failures happen when you aggregate:

  • multiple upstream calls

  • optional enrichments

Patterns:

  • return core items + warnings[]

  • mark fields as optional with explicit null semantics

  • provide a missingDependencies[] signal if applicable

UI rule:

The UI must be able to render a degraded-but-true state.


SECTION 6 — BFF VS DIRECT-TO-SERVICE

BFF (Backend for Frontend)

Pros:

  • UI-optimized responses

  • hides microservice complexity

  • centralizes auth/session

Cons:

  • can become a god-layer

Senior default:

  • BFF for complex products and multiple clients (web/mobile).

Direct-to-service can work for small systems, but becomes expensive as complexity grows.


SECTION 7 — SCHEMA-FIRST (OPENAPI / GRAPHQL)

Schema-first gives you:

  • contract reviews

  • client generation

  • compatibility checks

Guideline:

  • use schema as the canonical artifact

  • treat schema changes like code changes (reviews + changelog)


SECTION 8 — UI STATES AS STATE MACHINES (CONTRACT MEETS UX)

Most bugs are “impossible states” made possible.

Define UI state machines for flows:

  • checkout

  • upload

  • auth

Then map them to backend invariants:

  • backend guarantees transitions are valid

  • UI never invents success


SECTION 9 — EXERCISES

  1. Define an error taxonomy for your app (10–15 codes max).

  2. Choose cursor pagination for one list and specify cursor semantics.

  3. Pick a flow and define a UI state machine + matching backend invariants.

  4. Decide whether you need a BFF and justify.


🏁 END — FRONTEND–BACKEND CONTRACTS