An old reporting API, written in PHP 5.6, had run for seven years. Migrating it to Node.js, the code itself translated in two or three days. What really took time was not breaking anyone.
First, Find Who's Calling
Nobody could say for sure who was using this API. The docs had stopped at 2019, listing 2 callers — clearly incomplete.
The method was crude: pull six months of Nginx access logs, filter by path, and break down the query strings into stats. We found 3 kinds of requests carrying old parameters:
- An internal cron job still passing old enums like type=1.
- A client-built dashboard with a misspelled parameter name that had always worked, thanks to fault tolerance on the PHP side.
- The most surprising: an Excel macro on an office PC pulling data via Web Query, its User-Agent that familiar Office string.
Had we missed the third one, that report would have gone blank on day two after launch — and nobody would ever have thought to check a macro.
How the Compatibility Layer Was Kept
The new API's parameters were redesigned, but we kept an adapter at the outer layer: old parameters are still accepted and mapped to the new fields internally; legacy enums like type=1 translate to the new ones; the misspelled parameter name also stays, marked deprecated.
The response JSON's field names and structure didn't change at all. Changing the response is far more dangerous than changing parameters — downstream code tends to index directly into it.
We also log one line per request: raw parameters, which caller type we identified, and whether the compatibility branch fired. Over two months we worked down that list, contacting owners one by one and migrating the cron job and dashboard to the new parameters. The Excel macro stayed put — the client said that PC has two more years of life in it.
The Gray Release
Both versions ran in parallel for 3 weeks, with Nginx splitting traffic by cookie — the same playbook as our earlier gray releases. The old API stayed up 7 more days before shutting down, and only after logs confirmed no more incoming requests.
One small trap: Node.js handles numeric precision the JavaScript way by default. A 17-digit order number in the report came back to Excel with trailing zeros. We caught it and switched to returning it as a string before things matched again. PHP-era code never hits this — it came with the language switch.
Where It Stands
The compatibility layer is still there — a switch statement under 30 lines, with a comment: "Don't remove; Excel still uses this." We'll deal with it when that PC retires.
In a migration like this, writing the code is the shortest part.

