Spec-Driven Frontend-Backend Alignment

Spec-Driven Frontend-Backend Alignment
Spec Coding Editorial Team · Spec-first engineering notes

Every frontend-backend misalignment I have debugged traces back to the same root cause: two teams implementing their own interpretation of a conversation instead of the same written contract. Spec-first work is how you stop that, and it is cheaper than any integration war room.

Published on 2026-03-09 · Updated 2026-05-06 · 7 min read · Author: Spec Coding Editorial Team · Review policy: Editorial Policy

The Checkout Bug That Converted Me

A few years ago I watched a checkout flow ship on a Thursday and start quietly losing orders by Friday morning. The backend had shipped a new POST /orders that returned status: "pending_payment" when a card required 3DS. The frontend had built against a verbal description and was still branching on status === "pending". The order was created, the customer saw a success screen, and the payment never completed. Finance found it before monitoring did.

Nobody was lazy. Both teams wrote careful code. They simply did not share a single artifact that either could have been wrong about in the same way. That is the failure mode spec-first work exists to prevent, and the rest of this article is the playbook I now run.

The Schema Is the Source of Truth, Not the Code

The first rule I insist on: neither the frontend TypeScript types nor the backend ORM model gets to be the canonical contract. Both are downstream of an OpenAPI or GraphQL schema that lives in its own repository or package, versioned independently, reviewed by both teams, and frozen before either side starts implementation.

This sounds bureaucratic until you have lived through the alternative. When the Prisma model is the source of truth, the frontend discovers changes through build failures after deploy. When the TypeScript interface is the source of truth, the backend drifts quietly until a production payload breaks the client. A neutral schema repository forces changes to be proposed as contract edits, not byproducts of feature work.

Generated Clients, Hand-Edited Never

Once the spec exists, the frontend client and backend stubs should both be generated from it. I run openapi-typescript for types and openapi-fetch for the runtime client. The backend gets Zod schemas generated from the same document and uses them as request validators at the handler boundary.

Here is the rule that saves you: the generated file has a header comment that says DO NOT EDIT, and CI fails if it was modified without a corresponding spec change. I enforce it with a hash check in a pre-commit hook. Hand-editing the generated client is how drift starts, always by a developer in a hurry who thinks their one-line fix is harmless. It never is.

One Error Envelope, Both Sides Implement It

The other place contracts rot is in the error path, because error shapes rarely get the same review as success shapes. I force every API I design to use a single error envelope that looks something like this:

{
  "error": {
    "code": "PAYMENT_DECLINED",
    "message": "Card was declined by issuer",
    "fields": { "card_number": "issuer_declined" },
    "trace_id": "01HXYZ..."
  }
}

The code is a stable enum the frontend branches on. The message is human-readable but never parsed. The fields map carries form-level validation errors. The trace_id turns support tickets into log queries in under thirty seconds. Agree on this envelope once, and every endpoint inherits it for free.

Mock First, Implement in Parallel

The point of a frozen spec is that both teams can start building the same day. I run prism or msw against the OpenAPI document and the frontend team builds the entire feature against a mock server that returns schema-valid responses. The backend team builds against the same spec with contract tests that assert their handlers match it.

Neither team is blocked. When both sides are ready, integration is the boring part: the frontend points at the real backend and it works, because both conformed to the same document. When it does not work, the bug is almost always a spec ambiguity that needs a clarifying edit, not a week of finger-pointing in Slack.

Acceptance Criteria in Given/When/Then

I write acceptance criteria for every cross-team feature in Given/When/Then form, and I include them in the spec document next to the endpoint they cover. This is the format I use:

Feature: Checkout submits card requiring 3DS

- Given a logged-in customer with a valid cart
  When they submit a card that triggers 3DS
  Then the API returns 202 with status "pending_payment"
  And the response includes a redirect_url for the 3DS challenge
  And the frontend renders the challenge in an iframe
  And the order is not marked paid until the callback arrives

- Given the 3DS challenge times out after 10 minutes
  When the callback has not arrived
  Then the order transitions to "payment_abandoned"
  And the frontend shows a retry affordance, not a success screen

QA writes tests from these. The frontend engineer knows exactly which status values to handle. The backend engineer knows exactly which state transitions to implement. Nobody has to guess.

Who Owns What

The ownership model I use is specific on purpose. Product owns the feature spec, which is the user-facing behavior and the acceptance criteria. Backend owns the data contract, which means field names, types, validation rules, and the error taxonomy. Frontend owns the interaction spec, which covers loading states, optimistic updates, retry behavior, and the empty and error UI.

When a change crosses two of these, both owners sign off on the PR that edits the schema. This small rule prevents a depressingly common pattern: the backend renames a field because the ORM changed, the frontend finds out on deploy, and the PM discovers the affected screen three days later.

Breaking Changes Need a Protocol, Not a Meeting

Sometimes you have to make a backward-incompatible change. The protocol is non-negotiable: never remove or rename fields in place. Add the new field, dual-write for a release, update the frontend to read the new field, then remove the old one. If the change is semantic (the meaning of a status value shifts), bump the path or add a version header, and keep the old version alive until instrumentation shows nobody is calling it.

Enforcement lives in CI. I run openapi-diff on every PR that edits the schema, and breaking changes fail the build unless the PR is labeled breaking-change-approved with a linked migration plan. That single check has prevented more incidents than any monitoring dashboard I have built.

The Integration Gate

The last piece is the rule that lets both teams ship independently: a release deploys only if it conforms to a frozen spec version. I tag the spec repository with semver, pin frontend and backend to specific versions in their manifests, and run contract tests in CI. If both are on [email protected], they are safe to deploy in any order. If they diverge, the deploy gate fails and forces a coordinated release.

This is the whole point of spec-first work. You are not slowing down to write more documents. You are replacing integration risk with a contract both sides can be held to, and the contract is cheap compared to the cost of the checkout bug I opened with.

Contract Review Packet to Copy

Use this when the work touches API behavior, schema, events, retries, or consumer expectations. The packet makes compatibility and release evidence explicit.

API contract review packet: Spec-Driven Frontend-Backend Alignment

Decision to make:
- How spec-first work keeps frontend and backend from shipping on different contracts: shared schemas, generated clients, error-shape agreement, and the parallel-build playbook.

Owner check:
- Product owner:
- Engineering owner:
- QA or operations reviewer:

Scope boundary:
- In scope:
- Out of scope:
- Assumption that still needs approval:

Acceptance evidence:
- Test or fixture:
- Log, metric, or screenshot:
- Manual review step:

Contract boundary: no release without compatibility classification, consumer impact, retry behavior, and rollback notes.

Reviewer prompt:
- What would still be ambiguous to someone who missed the planning meeting?
- What evidence would make this safe enough to ship?

Editorial Review Note

Reviewed Apr 28, 2026. This update added a reusable artifact, checked the article against the related topic hub, and tightened the next-step links so the page works as a practical reference rather than a standalone essay.

Keywords: OpenAPI contract · generated API client · error envelope · mock-first development · schema-driven development · contract testing · breaking change protocol

Topic Path

This article belongs to the API Contracts track. Start with the hub, then use the checklist, template, or tool below on a real project.

Editorial Note

Last reviewed Apr 28, 2026: examples, internal links, and reusable review blocks were checked for practical specificity.