#Python

Running Python ASGI Apps in the Browser with Pyodide and Service Workers

AI & ML Reporter
5 min read

A new proof‑of‑concept shows that any ASGI‑compatible Python web framework—FastAPI, Datasette, and likely others—can be executed entirely client‑side using Pyodide and a service worker, eliminating the need for a remote backend for same‑origin routes.

What’s claimed

The author demonstrates a technique that lets a full‑featured Python ASGI application run inside the browser. By loading the CPython interpreter compiled to WebAssembly via Pyodide and routing all requests under a chosen path (e.g., /app/) through a service worker, the browser can handle the ASGI lifecycle locally. The claim is that this removes the requirement for a traditional server‑side component—only static assets need to be served from a conventional host.

What’s actually new

1. Service‑worker‑based request interception

Previously, projects such as Datasette Lite used a Web Worker that fetched generated HTML after the Python code ran. That approach broke any <script> tags embedded in the response because the HTML was inserted via innerHTML rather than a real navigation. The new method registers a service worker that intercepts fetch events for the designated URL space. The worker:

  1. Receives the request object.
  2. Passes it to a tiny ASGI shim that translates the Fetch API request into an ASGI scope and receive channel.
  3. Executes the Python coroutine inside the Pyodide runtime.
  4. Collects the ASGI send messages, builds a Response object, and returns it to the browser.

The key novelty is the tight coupling of the service worker’s network layer with the ASGI protocol, something that has only been sketched in academic papers but not released as a reusable library.

2. General‑purpose ASGI demo

The author provides two concrete examples:

  • A minimal FastAPI app that echoes request data, showing that the typical FastAPI routing, dependency injection, and response handling work unchanged.
  • A full Datasette 1.0a31 instance, proving that a more complex, plugin‑heavy stack can be bootstrapped without server code.

Both demos are hosted on a static GitHub Pages site, meaning the only server‑side component is the static file host.

3. Re‑usable shim code

The implementation lives in a small JavaScript module (see the linked GitHub repo) that:

  • Starts the Pyodide runtime on first request.
  • Installs the asgi package from PyPI inside the WebAssembly environment.
  • Exposes a handleFetch(event) function that can be dropped into any service worker.

Because the shim is framework‑agnostic, any ASGI‑compatible library—Starlette, Quart, Sanic—should be able to run with minimal changes.

Limitations and practical concerns

Area Issue Why it matters
Performance Pyodide startup can take 2–5 seconds on a typical desktop connection, and each request incurs the overhead of marshaling data between JavaScript and the WebAssembly Python VM. Interactive apps will feel sluggish compared to a native server, especially on low‑end devices or mobile browsers.
Memory usage The full CPython interpreter plus any compiled wheels (e.g., uvicorn, orjson) can consume 150–250 MiB of RAM in the browser. Browsers may evict the service worker or throttle it on memory‑constrained devices, leading to crashes or degraded performance.
File system Pyodide provides a virtual FS that lives only for the session. Persistent storage must be layered on top of IndexedDB or the Cache API, and the current demo does not persist any state across reloads. Applications that rely on a persistent SQLite database (like Datasette) will need additional glue code to sync the virtual FS with persistent storage.
Security model Service workers run with the same origin policy, but they have full network access. Running arbitrary Python code supplied by a user could expose the browser to denial‑of‑service attacks or excessive CPU usage. A production‑ready deployment would need sandboxing measures (e.g., limiting CPU time, restricting imports).
Browser compatibility The approach depends on ES2022 features (async service workers, ReadableStream), and on WebAssembly SIMD for acceptable speed. Older browsers (Safari < 16, IE) will not support it. Audience is limited to modern browsers; fallback strategies are not addressed in the current proof‑of‑concept.
Tooling and debugging Debugging Python code inside Pyodide is possible but less ergonomic than a normal server environment; stack traces are sometimes mangled, and source‑map support is minimal. Developers may find it harder to iterate on complex logic, reducing the attractiveness for large projects.

How this fits into the broader trend

Running server‑side code in the client is not new—Node.js can serve static files, and frameworks like WasmEdge aim to host WASM backends on the edge. What this work adds is a pure‑Python stack that can be shipped as static assets, which is attractive for data‑centric tools that already depend on Python libraries. It also sidesteps the need for a separate edge runtime, because the browser itself becomes the compute node.

Practical next steps for developers

  1. Clone the demo repo – the author’s GitHub repository contains the service‑worker script, a minimal FastAPI example, and a pre‑built Datasette Lite bundle.
  2. Run locallypython -m http.server (or any static server) will serve the files; the service worker registers automatically on the first page load.
  3. Experiment with other ASGI frameworks – replace the FastAPI app with a Starlette or Quart instance and observe the same request flow.
  4. Add persistence – use the Cache API to store the virtual FS snapshot after each write operation, then restore it on worker activation.
  5. Profile and limit – instrument the service worker with the Performance API to measure request latency; consider aborting long‑running coroutines after a timeout.

Verdict

The proof‑of‑concept is a solid engineering demonstration that ASGI apps can run client‑side using Pyodide and a service worker. It is not a drop‑in replacement for production backends, but it opens a niche for self‑contained data tools, educational notebooks, and demo deployments where the overhead of a server is undesirable. Future work will need to address performance, memory, and persistence before the approach can be recommended for anything beyond lightweight, read‑only workloads.

Resources

Comments

Loading comments...