API-First: Workflow Between Backend, Frontend and QA
AI generated
{ }
GET
API-First · OpenAPI · Team Workflow · Contract Testing
API-First: Workflow Between Backend, Frontend and QA
OpenAPI as the Single Source of Truth in the Team

Anyone who implements an API before the design is settled blocks frontend teams and forces QA engineers into manual follow-up. API-first reverses the order: the OpenAPI specification comes first, enabling mocking for the frontend and contract tests for QA, so all teams can develop in parallel without waiting on each other.

18 min read OpenAPI · Mocking · Contract Testing · Review Process Teams · Parallel Work · Interface Design

1. The Problem Without API-First: Blockers in the Team Workflow

In most teams, API development follows a fixed order: the backend team implements endpoints, sends the frontend team the finished URL and a screenshot of the JSON response, and QA waits until both are done before running manual tests. This model has a fundamental weakness: every dependency creates a blocker. The frontend cannot start work until the endpoint is live. QA cannot test until the frontend has integrated. Backend has to interrupt its own work to respond to every follow-up question.

What looks like a sequential workflow on paper feels like permanent waiting in practice. On top of that come misunderstandings: the field name the frontend expects is called something else in the backend. The error code QA is testing does not match what the backend actually returns. The nullable field the frontend handles can, in an edge case, actually be returned empty by the backend, and this case is documented nowhere in the code. API-first solves this structural problem by making the interface the first artifact of the development cycle, not the last.

2. What API-First Actually Means, and What It Does Not

API-first means the specification is written before the code. It is not an afterthought documentation document but the design artifact from which all other activities follow. OpenAPI 3.1 is the industry standard for this specification, a YAML or JSON document that fully describes endpoints, request bodies, response schemas, status codes, security requirements and examples. This document lives in the repository, is versioned, and forms the contractual basis for all teams.

What API-first does not mean: it is not a waterfall process in which a mega document is specified for weeks before a single line of code is written. In practice, API-first means that every user story or feature is first discussed and specified at the interface level, even if that is only a single new endpoint or an extended schema. The specification is extended iteratively, not created all at once. The difference from the code-first approach is not the amount of upfront design, but the fact that the interface is explicitly designed before implementation decisions shape it implicitly.

3. Building the OpenAPI Specification Together

Building the specification is a collaborative process that involves all affected teams. Backend developers understand what is technically feasible and which data the database can supply. Frontend developers know which data structure their components need and which fields are displayed in which context. Product owners know the business requirements. QA engineers identify edge cases and error scenarios that must be covered in the specification.

The result is an OpenAPI file that is versioned in the Git repository under api/openapi.yaml. Every change to the specification goes through the same pull request process as code. API lint tools such as spectral automatically check the specification for consistency, missing examples and convention violations. The specification includes not only the happy-path responses but explicitly all error cases with status codes and error schemas, because these are exactly what generate the most misunderstandings in day-to-day team work.


# api/openapi.yaml - excerpt: user endpoint with full error coverage
openapi: "3.1.0"
info:
  title: Mironsoft API
  version: "2.0.0"

paths:
  /users/{id}:
    get:
      operationId: getUser
      summary: Retrieve a single user by ID
      tags: [Users]
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer, minimum: 1 }
          example: 42
      responses:
        "200":
          description: User found
          content:
            application/json:
              schema: { $ref: "#/components/schemas/UserResponse" }
              examples:
                active_user:
                  summary: Active user with all fields
                  value:
                    id: 42
                    email: "user@example.com"
                    name: "Maria Mustermann"
                    role: "editor"
                    active: true
                    createdAt: "2025-01-15T10:30:00Z"
        "404":
          description: User not found
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
              example:
                code: "USER_NOT_FOUND"
                message: "No user with ID 42 found"
        "401":
          $ref: "#/components/responses/Unauthorized"

components:
  schemas:
    UserResponse:
      type: object
      required: [id, email, name, role, active, createdAt]
      properties:
        id:     { type: integer }
        email:  { type: string, format: email }
        name:   { type: string, maxLength: 255 }
        role:   { type: string, enum: [admin, editor, viewer] }
        active: { type: boolean }
        createdAt: { type: string, format: date-time }
    ErrorResponse:
      type: object
      required: [code, message]
      properties:
        code:    { type: string }
        message: { type: string }

4. Backend: Developing Code Against the Specification

The backend team begins implementation with the fully approved specification as its contract. For PHP projects, tools such as openapi-generator are a good fit, generating server stubs from the specification: concrete interface classes that the backend must implement. This ensures that endpoint signatures, parameter names and response structures exactly match the specification, not a subjective interpretation of it.

In addition, a middleware validates on every request and every response whether the actual data matches the schema of the specification. In Symfony, league/openapi-psr7-validator provides this validation as a middleware layer. This means: if the backend implementation deviates from the specification, validation fails, not first in QA, not first with the frontend team, but immediately during development. The contract is enforced automatically, not only through reviews.


# Generate PHP server interfaces from OpenAPI spec
npx @openapitools/openapi-generator-cli generate \
  -i api/openapi.yaml \
  -g php-symfony \
  -o src/Generated/Api \
  --additional-properties=invokerPackage=App\\Generated

# Lint OpenAPI spec with Spectral (CI step)
npx @stoplight/spectral-cli lint api/openapi.yaml \
  --ruleset .spectral.yaml \
  --format junit \
  --output .reports/spectral.xml

# Validate generated PHP interfaces match spec
composer validate-api
# Runs: vendor/bin/openapi-spec-validator api/openapi.yaml

# Run backend tests with request/response validation middleware active
composer test -- --group api-contract

5. Frontend: Developing in Parallel with Mocks

As soon as the OpenAPI specification with complete examples is in the repository, the frontend team can start developing without waiting for the backend. A mock server such as Prism from Stoplight reads the specification and automatically serves the examples defined in it as real HTTP responses. The frontend developer points the API base URL at the local mock server and builds components against real data that exactly matches the specification.

This approach has a decisive advantage over hardcoded fixtures in frontend code: the mock data is the same data that is defined in the contract. If the backend later changes a response structure and the contract is updated accordingly, the mock updates automatically, and the frontend team immediately sees which components need adjusting, even before the backend feature is finished. In this setup, parallel development runs without dependencies.


# Start Prism mock server from OpenAPI spec (Frontend dev)
npx @stoplight/prism-cli mock api/openapi.yaml \
  --port 4010 \
  --dynamic           # generate dynamic fake data when no example exists

# Frontend connects to mock: http://localhost:4010/users/42
# Returns the example defined in openapi.yaml exactly

# Validate that mock responses match schema (CI smoke test)
npx @stoplight/prism-cli proxy api/openapi.yaml http://localhost:8080 \
  --port 4011 \
  --errors            # return 422 on schema violations instead of passing through

# TypeScript client generation from spec (keeps types in sync)
npx @openapitools/openapi-generator-cli generate \
  -i api/openapi.yaml \
  -g typescript-fetch \
  -o frontend/src/api/generated \
  --additional-properties=supportsES6=true,typescriptThreePlus=true

6. QA: Deriving Contract Tests Automatically from the Spec

QA teams benefit from API-first most directly: instead of manually checking whether an endpoint returns the documented response, contract tests are generated automatically from the specification. Tools such as schemathesis read the OpenAPI specification and systematically generate test cases, for all defined examples, for boundary values in numbers and strings, for missing required fields and for invalid types. Instead of 20 manually written test cases, hundreds of tests are created automatically, covering the contract exactly.

For more structured contract testing, QA additionally writes explicit tests against the specification. Each test calls a real endpoint and validates the response against the JSON schema from the specification. The test framework does not need to know the contract itself; it is enough to load the specification and call the validation library. QA does not validate what has been implemented, but what has been agreed, a fundamental difference for the error rate in integration.

7. API Design Review: How Structured Governance Works

In growing teams, inconsistent APIs quickly emerge: one team uses camelCase for fields, another snake_case. One endpoint returns errors as {"error": "…"}, another as {"message": "…", "code": 42}. Versioning is encoded once in the URL path, once as a header value. These inconsistencies do not arise from carelessness but from a missing governance process. API design reviews are the structural answer to this.

An effective review process for API-first teams contains three levels: automatic checks through lint rules that run in the CI pipeline and enforce technical conventions; peer review by another team member who assesses business completeness and clarity; and an optional architecture review by an API guild or a designated API owner for cross-team consistency. Only the first level is always mandatory; the others scale with team size and the risk of the affected endpoint.


# .spectral.yaml - Custom API governance rules
extends: ["spectral:oas"]

rules:
  # All operations must have operationId
  operation-operationId-required:
    message: "Every operation needs an operationId for code generation"
    given: "$.paths[*][get,post,put,patch,delete]"
    severity: error
    then:
      field: operationId
      function: truthy

  # Enforce camelCase field names
  schema-properties-camelCase:
    message: "Schema properties must use camelCase naming"
    given: "$.components.schemas[*].properties"
    severity: warn
    then:
      function: pattern
      functionOptions:
        match: "^[a-z][a-zA-Z0-9]*$"

  # All error responses need the standard ErrorResponse schema
  error-response-schema:
    message: "4xx/5xx responses must reference ErrorResponse schema"
    given: "$.paths[*][*].responses[4xx,5xx].content.application/json.schema"
    severity: warn
    then:
      function: schema
      functionOptions:
        schema:
          properties:
            $ref: { type: string, pattern: "ErrorResponse" }

  # Require examples for all response schemas
  response-examples-required:
    message: "All 2xx responses need at least one example"
    given: "$.paths[*][*].responses[2xx].content.application/json"
    severity: warn
    then:
      field: examples
      function: truthy

8. API-First vs. Code-First: A Direct Comparison

The choice between API-first and code-first is not a technical decision but a process decision with measurable effects on team speed and integration quality. The direct comparison shows where the decisive differences lie, not only in documentation but across the entire development cycle.

Aspect Code-First API-First Impact
Order Code → docs Spec → code Parallel team work possible
Frontend start Waits on backend Immediately via mock server Weeks earlier
QA tests Manual, after implementation Auto-generated from spec Higher test coverage
Field name conflicts Frequent, found late Prevented in design review Fewer integration bugs
Doc quality Often outdated Always current (=spec) Fewer follow-up questions

The table shows the structural advantage of API-first: every investment in specification quality pays off three times over, for backend, frontend and QA. The only real additional effort compared to code-first lies in the design review process and the initial discipline of writing the specification before the code. This effort typically pays for itself starting with the first feature that is developed in parallel by multiple teams.

Mironsoft

API design, OpenAPI workflows and team enablement

Introducing API-First in Your Team?

We help teams build an API-first workflow, from the first OpenAPI specification through mock server setup to automated contract tests in the CI pipeline.

OpenAPI Workshop

Building the first specification together with all teams

Tooling Setup

Integrating Prism, Spectral and contract tests into CI

Governance

Lint rules and review processes for consistent API quality

9. Summary

API-first is not a documentation project but a process pattern that enables parallel development within a team. The OpenAPI specification is created first, treated as a contract between backend, frontend and QA, and lives versioned in the repository. Backend implements against this contract, frontend develops in parallel with mock servers, QA derives contract tests automatically from the specification. Lint tools enforce technical conventions, design reviews secure business quality.

The measurable effect: fewer integration bugs, fewer follow-up questions between teams, and documentation that is always current because it is the development artifact itself, not an afterthought. Teams that introduce API-first regularly report shorter integration cycles and fewer surprising regressions in the final phase before release.

API-First Team Workflow - The Key Points at a Glance

Order

Spec before code. The OpenAPI file is the first artifact of every feature, not documentation added afterward.

Parallel Development

Frontend uses the Prism mock server against the spec. No need to wait for the backend to finish.

QA Automation

Schemathesis and contract-test tools generate test cases directly from the specification.

Governance

Spectral lint rules in CI enforce conventions automatically. Design reviews secure business quality.

10. FAQ: API-First Team Workflow

1What is the difference between API-first and code-first?
With code-first, docs are derived from code. With API-first, the spec is created before the code and is the contract for all teams. The interface is deliberately designed instead of being shaped implicitly by implementation decisions.
2How can the frontend develop when the backend is not finished yet?
With the Prism mock server, which reads the OpenAPI spec and serves defined examples as real HTTP responses. Frontend develops against the same data the backend will later deliver.
3How does QA test against a specification?
Schemathesis automatically generates test cases from the OpenAPI spec. A test middleware validates whether every real API response matches the schema.
4What is Spectral?
An OpenAPI linter with configurable rules: it finds missing operationIds, naming convention violations, missing examples, automatically in CI on every commit.
5How do I version an OpenAPI spec?
The OpenAPI file lives in the Git repo with a pull request process. Breaking changes get a new API version in the path. Non-breaking changes can be added to the existing version.
6Most common mistakes when introducing API-first?
A spec that is too granular before implementation starts, missing examples for mock servers, and unspecified error cases, exactly where integration bugs arise.
7Does every change need to go through a design review?
No. Lint rules are always mandatory. Peer review for new endpoints. Full architecture review only for cross-team or public APIs.
8Introducing API-first retroactively?
Yes, step by step. New features immediately with API-first. Existing endpoints generated from code, completed manually, gradually brought through the design process.
9What tools do I need at a minimum for API-first?
OpenAPI editor, Prism mock server, Spectral linter. Extended: openapi-generator for code generation, Schemathesis for contract tests.
10How do I handle breaking changes?
Breaking changes require a new API version /v2/ or an explicit migration process. Inform all affected teams in advance, the spec makes breaking changes visible before code is changed.