Run ASGI in Browser with Pyodide and a Service Worker
Simon Willison shows how to serve ASGI apps entirely in the browser using Pyodide and a service worker to handle HTTP requests.
TL;DR
- 01Simon Willison shows how to serve ASGI apps entirely in the browser using Pyodide and a service worker to handle HTTP requests.
- 02Simon Willison published a technical walkthrough on May 30, 2026, showing how to run Python ASGI applications entirely inside the browser using Pyodide and a service worker.
- 03The post demonstrates a working pattern where fetch events are handled by a service worker that invokes a Pyodide-based ASGI app, producing HTTP responses without contacting a remote Python server.
Simon Willison published a technical walkthrough on May 30, 2026, showing how to run Python ASGI applications entirely inside the browser using Pyodide and a service worker. The post demonstrates a working pattern where fetch events are handled by a service worker that invokes a Pyodide-based ASGI app, producing HTTP responses without contacting a remote Python server.
The example uses Pyodide, the CPython-to-WebAssembly runtime, to load Python packages and execute an ASGI-compatible application in the browser process. A service worker intercepts page requests and forwards them into the Pyodide runtime, which runs the ASGI app and streams responses back to the page. Willison's code includes handling of request bodies, headers, status codes, and streaming response bodies so typical ASGI frameworks can run with few changes.
How it works
The flow is straightforward. The service worker registers under the site scope and listens for fetch events. When the worker receives an HTTP request it packages the request metadata, including method, path, headers and body, and passes that into a Python environment running Pyodide. Pyodide boots CPython compiled to WebAssembly, uses micropip to install any required dependencies, and imports an ASGI application object.
Within the Python runtime the code constructs a standard ASGI scope and runs the application. The ASGI send callable is implemented to translate application messages into response chunks or headers that the service worker can turn into a Response object. For streaming responses the implementation wires send calls back into the browser stream API so the page receives bytes as the Python app yields them. The service worker then resolves the original fetch with a constructed Response, completing the round trip without a backend server.
Willison's example covers practical issues such as cold-start time, packaging dependencies, and sharing a single Pyodide instance across multiple requests to avoid repeated heavy initialization. The walkthrough also shows how to expose simple persistence by using IndexedDB for local state where the browser environment requires it.
The setup supports common ASGI frameworks with minimal adapter code, making it possible to run frameworks that expect async call patterns. It also demonstrates how to use browser Web APIs, for example to read or stream request bodies, because the runtime is executing in the same page context or worker that has access to those APIs.
Why it matters
Running ASGI apps in the browser shifts some developer workflows by enabling demos, prototypes, and offline-first apps that use real Python servers without a remote host. The approach reduces needs for external infrastructure for small-scale or client-only applications, but it carries limits: memory, startup latency, lack of persistent server sockets, and the security model of the browser. Developers should view this pattern as a local execution option for specific scenarios, not a replacement for production server deployments.
Written by The Brieftide · Source: Simon Willison
The Brieftide Daily · 06:00
Briefs like this one, in your inbox every morning.
Continue reading
More in Python WebAssemblyWASM wheels on PyPI: publish packages for Pyodide browsers
Developers can publish WebAssembly wheels to PyPI so Python packages are distributed prebuilt for use with Pyodide in the browser.
luau-wasm 0.1a0 release: Luau in WebAssembly for browsers
An experimental alpha packages the Luau scripting language as a WebAssembly module for browser and server embedding.
asyncinject 0.7: Simon Willison releases Python async DI update
Version 0.7 refines async dependency injection for Python, tightening the API, improving typing and compatibility.
DiffusionGemma JavaScript: WebGPU browser diffusion demo
A compact, single-file JavaScript implementation that runs U-Net image diffusion in the browser using WebGPU for on-device sampling.