Porting to JS/TS, Part 1: why JavaScript, and not Go, Rust, or Python
The Leaving PHP series answered why a team leaves: talent, runtime, types, ecosystem momentum, and a strategic bet on where the next decade of tooling lands. That question is settled ground on this blog. It leaves a second, narrower question hanging, and it is the one a CTO actually has to answer once the decision to leave is made: leave for what?
There is no shortage of candidates. Go and Rust both get pitched as the "serious" systems answer. Python gets pitched as the safe, mature default. This series starts by working through why none of the three end up being the actual landing zone for most PHP teams, and why JavaScript and TypeScript do.
Go and Rust: real performance, a new memory model to learn
Go and Rust earn their reputations honestly. Both compile to fast, predictable binaries, both ship strong tooling out of the box, and both are legitimate choices for a team that is optimizing primarily for raw throughput or a small deployment footprint. Neither claim here is that Go or Rust is a bad language.
The cost is what it asks of the team making the move. A PHP codebase is written by developers used to a dynamically-typed, garbage-collected, web-first language, where a request comes in, memory gets allocated freely for the duration of that request, and the runtime cleans up. Rust's ownership and borrow checker is a different mental model entirely, and it is not a model most PHP developers have had a reason to learn. Go softens that specific edge with its own garbage collector, but its concurrency primitives (goroutines, channels, explicit error returns instead of exceptions) are just as unfamiliar to a team whose instincts are built around synchronous, request-scoped PHP.
That onboarding cost compounds with an ecosystem gap. Go has Gin, Echo, and Fiber; Rust has Actix and Axum. They are good frameworks, but neither ecosystem is close to PHP's Packagist or JavaScript's npm in sheer package count, framework maturity, or the breadth of "someone already solved this" libraries a web team leans on daily. A PHP team moving to Go or Rust is paying twice: once to learn a new memory model, and again to rebuild what Packagist already gave them for free.
Python: a mature web ecosystem with its own baggage
Python is the safer-feeling choice on paper. Django, Flask, and FastAPI are all mature, well-documented, and genuinely comparable to what a PHP team is used to in terms of framework ergonomics. This is the strongest section of Python's case, and it deserves to be stated plainly rather than dismissed.
Python's own baggage shows up once you look past the framework layer. The GIL still shapes how CPU-bound Python workloads get deployed, pushing teams toward multi-process worker models instead of the simpler threading a web request often wants. Packaging is Python's own long-running pain: pip, venv, poetry, conda, and pyenv all coexist, and picking the wrong combination is a recurring source of friction. That is a fair comparison point against PHP specifically, because PHP has had one clear answer in Composer for years.
Python's async story has the same shape problem PHP's does. asyncio was bolted onto a language that was synchronous first, and the result is a codebase that has to pick a lane: async-native libraries or the synchronous ecosystem, rarely both cleanly. That is the same "retrofitted concurrency" complaint the Leaving PHP series' async post made about PHP's Fibers. Moving from PHP to Python does not resolve that particular tension so much as it moves to a different version of it.
JavaScript and TypeScript: consolidation, not addition
The actual differentiator for JavaScript and TypeScript is not a performance number or a framework list. It is that a team leaving PHP for JS/TS does not pick up a second language. It consolidates onto one. If the product has a frontend at all, that frontend is already JavaScript or TypeScript, and the backend move simply removes a language boundary rather than adding one.
npm is the closest thing to Packagist's breadth that exists in any of these candidates, both in raw package count and in the diversity of what is actually published there. Node and Bun are both first-class deployment targets today, with mature hosting, serverless, and edge support across every major cloud provider. That is infrastructure a Go or Rust team has to partially rebuild and a Python team gets in a narrower form.
This is also, plainly, pext's own bet. Pext transpiles PHP to JavaScript and TypeScript specifically because that is where a PHP migration actually lands once the ecosystem and team-consolidation math is worked through, not because JS was an arbitrary target of convenience. The rest of this series exists to make that math explicit.
This is the first post in a short series on what porting to JS/TS actually looks like in practice. Part 2 gets concrete about the runtime target itself: Node.js versus Bun, and what actually changes when you pick one over the other.