Loading page...Загружаем страницу...正在加载页面...
AtlasRepo

Upgrading Laravel 5.5 to 13 without changing a single API response

Description and use

Migrated a Laravel 5.5 API to 13 without altering any API response for an external frontend. Employed DriftGate to capture and compare all 622 requests after each upgrade step, identified and fixed regressions, reduced capture time, and ensured byte‑for‑byte compatibility.

Problem

A Laravel 5.5 API had to be upgraded to Laravel 13 and PHP 8.3 while keeping every API response byte‑for‑byte unchanged for an external frontend.

Outcome

The migration was completed in about 15 steps, covering 622 requests. DriftGate detected and fixed more than ten regressions that feature tests missed, reduced capture time from 4 minutes to 148.9 s, and ensured every response remained byte‑for‑byte identical.

Workflow

  • Upgrade to next Laravel version (5.6, 5.7 … 13) one step per branch
  • Run driftgate run to capture baseline and compare responses
  • Fix drifted differences (e.g., cast "60" to int internally, adjust serializer settings)
  • Accept intentional changes with driftgate accept and log reason and ticket
  • Repeat until all 622 requests are identical
  • Merge pull request
  • Run final capture to confirm no drift

Evidence

The API ran on Laravel 5.5 and PHP 7.3. It had to end up on Laravel 13 and PHP 8.3, in about fifteen steps, and the other company's frontend had to keep working without a single change on their side.

Source

Feature tests rarely catch these. They assert what the developer thought mattered when the test was written, and they usually compare by meaning: assertJson(['id' => 42]) is happy with both 42 and "42". A JavaScript frontend written by another team is not always that forgiving.

Source

The login response includes the token lifetime, which comes from .env, so it was the string "60". Carbon 3 accepts only numbers in addMinutes(), and every login started failing. The obvious fix, casting the value to an integer, made login work again, but the response now said 60 instead of "60".

Source

What we needed was simpler and stricter: record what every route returns today, and after every upgrade step prove that it still returns exactly that, on the same data.

Source

The tool reads the application's own route list (for Laravel, route:list) and proposes a request for each route. driftgate coverage fails while a route has neither a request nor an exclusion with a reason, so nothing slips through because nobody thought of it.

Source

By the end, the acceptance log explains every difference between the old and the new API, and nothing else changed.

Source

Two production Laravel APIs: one from 5.5 to 13 in about 15 steps (622 requests), one from 6 to 13 in about 12 steps (494 requests). Each step was closed only at 622/622 and 494/494 identical responses.

Source

  • Record date: 2026-11-03T11:13:58.866Z