In my previous post, I built a toy Python-to-JavaScript transpiler. It handled simple scripts just fine, and it was honestly kind of magical to watch Python turn into working JavaScript. Naturally, this got me dreaming bigger. What if you could write real Python apps and deploy them anywhere Node.js runs? This idea came to me around the time Heroku killed off its free hobby tier while Vercel kept handing out free hosting for javascript web frameworks. Python is arguably the friendlier language to learn, but its hosting options had gotten worse. JavaScript's hosting options were still great. "Why not just... bridge the two?" I wondered.
So I went looking into whether this was actually possible. TLDR, it's not, and the reasons why turned out to be much more interesting than the idea itself.
The surface-level problems show up fast. Python and JavaScript don't share a vocabulary for a lot of things: decorators, multiple inheritance, context managers, magic methods like __len__, properties, and each one of these would need its own hand-built translation. Some of that is tedious but doable. Generators are close enough between the two languages. async/await looks almost identical on the page, even if the event loop underneath behaves differently (Check Oversimplified: The JavaScript event loop). None of this alone kills the dream.
What kills the dream is C.
Python's most useful libraries aren't really "Python" under the hood. Pydantic, which FastAPI leans on constantly, is C-accelerated. So is Werkzeug, which Flask sits on top of. NumPy, Pandas, Pillow, psycopg2, cryptography, lxml, and many more all have serious C cores doing the heavy lifting. You can't transpile compiled binary code, because it was never Python to begin with. There's no syntax tree to walk and translate, just machine code that Python happens to call into, and a transpiler only knows how to read source code, not reverse-engineer a compiled binary back into logic it can rewrite.
Your options at that point are all bad. You could rewrite tens of thousands of lines of highly optimized C by hand, which is a multi-year undertaking for a single library, let alone an ecosystem. You could lean on WebAssembly instead, the way Pyodide already does, but that gets you a 20MB+ Python runtime that only works in a browser, not something you'd want running a backend API. Or you strip out the C-dependent parts of the ecosystem entirely and accept what's left: Flask without its plugins is really just a routing library, stripped of most of what made it worth using in the first place.
And even setting C extensions aside, the runtimes themselves don't line up. Python does reference counting with a cycle-detecting garbage collector, meaning it cleans up unused objects by tracking how many references point to them and periodically checking for reference loops it might otherwise miss. JavaScript instead uses generational garbage collection, which assumes most objects die young and organizes cleanup around that assumption. Python's standard library is famously "batteries included," meaning most day-to-day utilities like os, pathlib, datetime, and json ship with the language itself. JavaScript's standard library is intentionally minimal and offloads almost everything to npm. Even something as basic as file paths gets messy: Python's pathlib handles Windows and Unix differences that you'd have to reimplement by hand in JS-land, mapped to who-knows-which npm package.
Frameworks make this worse, because they're not just syntax wrapped around HTTP requests, they're architectures built around language-specific quirks. Flask's request handling depends on thread-local storage, a mechanism where each thread gets its own private copy of a variable, so a global-feeling g object magically has the right data no matter which function touches it. JavaScript is single-threaded with an event loop; there's no thread-local storage to piggyback on, so you'd need to rebuild that context-tracking behavior from scratch using Node's async context APIs, and hope it doesn't leak across async boundaries. FastAPI's dependency injection is even trickier — it inspects a function's type hints at runtime to decide what to inject, and JavaScript simply doesn't have type hints at runtime. You will not be translating that pattern at this point, you will be reinventing it entirely.
Even if you solved all of the above, you still haven't captured what makes these frameworks useful. Flask's real value isn't its syntax, it's Flask-SQLAlchemy, Flask-Login, Flask-WTF, Flask-Mail, and a hundred other plugins built over a decade. Transpiling the framework without its ecosystem is like writing an operating system without the applications that make it actually usable.
I'm clearly not the first person to run into this. A few projects have already poked at pieces of it. Transcrypt does transpile Python to JavaScript, and it works fine for simple scripts, but it has no concept of web frameworks and no answer for C extensions. Brython runs Python directly in the browser, but it's an interpreter rather than a compiler, so it's slower and stuck on the frontend. Pyodide is the most impressive of the bunch. It can genuinely run NumPy and Pandas via WebAssembly, but it's browser-only, the bundle is enormous, and it doesn't play nicely with npm. PyScript is basically Pyodide with friendlier packaging, so it inherits the same limitations. Every one of these solves a piece of the puzzle by narrowing the problem: give up compatibility, or give up the backend, or give up the full ecosystem. Nobody has "write Python, deploy anywhere Node runs" actually solved.
So what would it actually take? Probably not a transpiler at all. The more realistic path is a framework with Python-like syntax that compiles to clean, native JavaScript without pretending to be Python, no Flask, no FastAPI, just something new that borrows their ergonomics and plugs directly into npm instead of fighting it. Alternatively, you could scope the ambition way down and build Python-like DSLs, small domain-specific languages built for one narrow job like config files, query builders, or template languages, where the surface area is small enough to actually pull off. Or, and this feels almost too obvious to say out loud, the fix isn't technical at all: better and cheaper Python hosting would make the entire problem disappear without a single line of transpiler code.
What I actually took away from all this wasn't a working tool, it was a better sense of where the real weight in a language sits. The syntax is the easy part. The hard part is thirty years of libraries, runtime quirks nobody documents because everyone just knows them, and an ecosystem that took a decade to build and can't be copy-pasted into a new one overnight. TypeScript succeeded because it added types to JavaScript instead of trying to become Java. Babel succeeded because it targets different JS versions instead of inventing new semantics. CoffeeScript tried to make JavaScript feel like Ruby, and mostly died the moment ES6 gave people native classes and arrow functions. The lesson keeps repeating: work with the grain of the platform, not against it.
The dream of "write Python, deploy anywhere" isn't dead, exactly. It's just not going to happen through transpiling entire frameworks. It's going to happen, if it happens, through cheaper Python hosting, or through someone building something inspired by Python rather than a copy of it. Either way, this was a fun rabbit hole to go down. I may not have a working Python-to-JS transpiler at the end of it, but I came out the other side with more respect for the problem than a solution to it. Which, honestly, still felt fulfilling.
Update: Turns out the hosting side of this is already improving on its own. Vercel rolled out zero-configuration Django support in April 2026, meaning you can now deploy a full Django app or API to Vercel. It's still serverless under the hood, so long-running background jobs and persistent connections aren't really the target use case, but for exactly the kind of small hobby project that pushed me down this rabbit hole in the first place, it's a real answer.