~/blog/php-to-typescript-subscription-platform-migration
Engineering2023·20 August 2023

PHP to TypeScript: Why We Migrated a Production Subscription Platform

Migrating a production subscription platform from PHP to TypeScript is not a decision taken lightly. Here's the honest case for why we did it, the alternatives we rejected, and what the migration actually delivered.

PHPTypeScriptmigrationNode.jsarchitecturereplatforming

The Context

When a PHP/Laravel monolith has served a business for eight years, the natural question is: why change? PHP runs a significant portion of the web. Laravel is a mature, capable framework. The answer is never "PHP is bad" — it's about the specific cluster of problems that accumulate in a system that wasn't designed for its eventual complexity.

In a subscription platform, the complexity is real: 139+ database entities, complex state machine transitions across subscription lifecycles, multi-currency payment handling, multi-region deployments. The problems we were seeing were predictable for a system of this age and complexity.

The Problems We Were Solving

| Problem | Business Impact |

| --- | --- |

| Optional runtime type checking | Complex subscription state transitions produced intermittent production errors that were expensive to diagnose |

| Two-language stack (PHP + JavaScript) | No shared data contracts; mismatch bugs between what the API returned and what the frontend expected |

| PHP on AWS Lambda requires a custom runtime | Deployment complexity; not natively supported; maintenance burden |

| Tight coupling in the monolith | A change to core routing logic had cascading, unpredictable effects on unrelated modules |

| Developer hiring friction | PHP is declining in developer mindshare; TypeScript is significantly more attractive to the talent market |

Why TypeScript — Alternatives Considered

I evaluated the realistic alternatives systematically before committing:

| Language | Why Not Chosen |

| --- | --- |

| JavaScript (untyped) | 139+ entities with complex relationships; payment handling requires precision; subscription state machines benefit from compile-time validation. Runtime type errors in a billing context are unacceptable. |

| Python | Strong for data science; weaker for typed web APIs. Type hints are advisory. Team expertise was JavaScript-oriented — would have replicated the two-language problem. |

| Java / C# | Heavier runtime, slower local development loop, more complex Lambda deployment, harder to attract developers. |

| Go | Steeper learning curve. Our bottleneck is database/third-party API latency, not compute — Go's performance advantage doesn't apply here. |

| PHP (continue) | Would retain all current problems. Clean break from accumulated legacy patterns was the goal. |

The TypeScript choice was also driven by a specific architectural goal: a single language across frontend, backend, admin, and external integrations. When all layers speak TypeScript, you get shared interfaces, shared validators, shared constants — and the mismatches that caused production errors are caught at compile time.

What the Migration Actually Delivered

After completing the migration:

  • ·Compile-time errors catch subscription state transition bugs before they reach production
  • ·TypeORM provides full type inference across all database entities — the IDE knows the shape of every database operation
  • ·AWS Lambda Node.js runtime is first-class — no custom runtime, no deployment complexity
  • ·Shared interfaces between frontend and backend eliminate the class of API contract mismatches that were causing production issues
  • ·Refactoring confidence: when business rules change, the IDE identifies every affected code path across all codebases

The Migration Strategy

We used the Strangler Fig pattern — new services written in TypeScript, running alongside the PHP monolith, gradually taking over its responsibilities. This avoided a big-bang cutover, which for a live subscription business would have been extremely high risk.

The critical discipline: don't refactor as you migrate. The temptation to improve business logic while rewriting in a new language is constant and dangerous. Rewrite the same behaviour in the new language with the new type system. Prove it works. Then iterate.

We broke this rule twice early in the migration — both times it introduced bugs that took significant time to trace.

The Honest Caveats

PHP was not the problem. A well-structured PHP codebase with strict types and a proper framework would have served the business adequately. The problems we were solving were architectural — monolith coupling, two-language stack, no shared types — more than language-level.

TypeScript is not magic. The benefits only materialise if you use strict mode and eliminate any usage. We set strict mode from day one. It catches things that would otherwise be production bugs.

Migration always costs more than estimated. Budget conservatively — then add 30%. The edge cases in a subscription platform are numerous and discovery-heavy.