Designing APIs the Contract-First Way
How a contract-first approach aligns expectations across teams before a single line of code is written, and what it looks like in practice.
There are two ways to build an API: write the code first and document it later (code-first), or write the contract first and generate the code from it (contract-first). Both work — but when multiple teams or consumers are involved, the cost of that choice becomes very apparent.
The essence of the contract-first approach is this: you describe how the API will behave — which endpoints will exist, what data they accept and return, which error codes they use — in a document, before writing any code. That document becomes a developer guide, a cross-team agreement, and a test reference all at once.
The Inevitable Problem with Code-First
With a code-first approach, the backend team writes the code and produces documentation some time later. In the meantime, the frontend or mobile team is left hanging — either waiting for the backend to be done, or moving forward on assumptions. Those assumptions are usually wrong.
The result: integration-time arguments of the “you said it would return this, but the backend returns that” variety. Those arguments are a waste of time, and they would never have happened if a contract had been written upfront.
There is also the expectation mismatch problem. The backend returns user.name while the frontend expects user.fullName. Or the backend uses a page parameter for pagination while the frontend expects offset/limit. These are small inconsistencies, but they accumulate and drive up integration costs.
Writing a Contract with OpenAPI
In practice, the most common tool for contract-first work is the OpenAPI specification (formerly Swagger). It is written in YAML or JSON, and tooling support is extensive.
A small example:
openapi: "3.0.3"
info:
title: Sipariş API
version: "1.0"
paths:
/orders:
get:
summary: Sipariş listesi
parameters:
- name: status
in: query
required: false
schema:
type: string
enum: [pending, completed, cancelled]
responses:
"200":
description: Başarılı
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Order"
components:
schemas:
Order:
type: object
required: [id, status, total]
properties:
id:
type: integer
status:
type: string
total:
type: number
This specification can be written before any code exists. From there:
- The backend team implements against the contract.
- The frontend team spins up a mock server and starts building the UI.
- The test team defines their expectations based on the contract.
Everyone can move in parallel.
Parallel Development with a Mock Server
One of the concrete wins of contract-first is mock server usage. Tools like Prism read an OpenAPI specification and stand up a server that returns realistic responses as if the real API were already running.
The frontend team can work without waiting for the backend. As the contract evolves, the mock server evolves with it — no surprises at integration time.
There is a cost to this approach: writing the contract takes time. Especially in the early iterations, there will be discussions about “is this field required, is this type right.” But those discussions happen at design time, not integration time — the cost is far lower.
Keeping the Contract Alive
The most common failure mode for contract-first is this: the contract is written once, and it is never updated as the code changes. The contract and the implementation drift apart over time. After a few months, the document no longer reflects reality.
Contract testing is what prevents this. These are tests that compare the responses returned by the backend against the contract. When a discrepancy is detected, either the contract is updated or the implementation is corrected — a deliberate decision is made between the two.
Keeping the contract in sync with the code is what allows teams to connect to each other with confidence. A consumer can say “this endpoint works like this” and actually be right.
Where It Creates Real Value
Single developer, small project: contract-first may be overkill. It makes more sense to go straight to the code.
Multiple teams or consumers (web, mobile, third parties): contract-first saves time without question. Integration friction drops, expectation mismatches decrease.
An API designed to live for years: the contract serves as a decision log. The answer to “why did we design it this way” lives in the contract’s version history.
These days, I start any API that will have more than one consumer by writing a contract first. Writing code first was a habit; it took time to break, but the payoff is clear.
Comments
Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.