PyWeb
An educational HTTP web server built from scratch in Python.
Every time you visit a website, your browser sends a letter to a server and gets a reply. PyWeb is that server -- built from scratch so you can see exactly how it works. Think of it as a post office: letters arrive, get sorted, and replies are sent back.
Features
- HTTP request parsing -- read the incoming letter (method, path, headers, body, query params)
- Response building -- write the reply (status codes, headers, body)
- URL routing -- the mailroom that sorts letters to the right handler
- Path parameters -- dynamic URLs like
/users/<id> - Template engine -- form letters with
{{ blanks }}to fill in - Static files -- serve CSS, JS, images straight from a folder
- JSON support -- parse JSON request bodies, return JSON responses
- Logging -- see every request and response in the terminal
- 405 handling -- proper "Method Not Allowed" for wrong HTTP verbs
Example
from pyweb.router import Router
from pyweb.response import html_response, json_response
from pyweb.server import Server
from pyweb.template import render
router = Router()
@router.get("/")
def homepage(request):
return html_response(render(
"<h1>Hello, {{ name }}!</h1>",
{"name": "World"}
))
@router.get("/users/<id>")
def get_user(request):
return json_response({"user_id": request.params["id"]})
Server(router).serve_forever()
Quick Start
# Install dependencies
uv sync --all-extras
# Run the example server
uv run python examples/hello_server.py
# Visit http://127.0.0.1:8000
# Run tests
uv run pytest
Concepts
Every concept is explained with real-world analogies:
| Concept | Doc | Analogy |
|---|---|---|
| Requests | requests.md | The incoming letter |
| Responses | responses.md | The reply letter |
| Routing | routing.md | The mailroom |
| Templates | templates.md | Form letters with blanks |
| Server | server.md | The post office |
Documentation
Full docs (with kid-friendly explanations of every idea): https://robcox2910.github.io/pyweb/
Async PyWeb
PyWeb also has an async framework that runs on PyNet's own event loop: one shopkeeper serving many customers at once, with server-sent events, WebSockets, sessions and roles.
from pynet.loop import run
from pynet.streams import open_connection
from pyweb.app import App
from pyweb.aserver import AsyncServer
app = App()
@app.get("/hello/<name>")
async def hello(request, name):
return {"greeting": f"Hello, {name}!"}
async def main():
async with AsyncServer(app, access_log=False) as server:
reader, writer = await open_connection("127.0.0.1", server.port)
writer.write(b"GET /hello/Sam HTTP/1.1\r\nConnection: close\r\n\r\n")
await writer.drain()
print((await reader.read()).split(b"\r\n\r\n")[1].decode())
writer.close()
run(main())
Output: {"greeting": "Hello, Sam!"}
Run the live demo (a page, a clock that ticks every second, and a WebSocket echo) and visit http://127.0.0.1:8000, then press Ctrl-C to stop:
uv run pyweb --async
More programs live in examples/: async_hello.py, sse_clock.py,
ws_echo_page.py and login_demo.py. The step-by-step lessons start at
"Part 2: A Busy Restaurant" in docs/index.md.
Developing alongside a sibling
PyWeb uses PyNet and PyCrypt from PyPI (fromscratch-pynet and
fromscratch-pycrypt). To work on a sibling
checkout at the same time, install it editable and tell uv not to
re-sync over it:
uv pip install -e ../pynet
uv run --no-sync pytest
Releasing
Other projects in the series (PyStack) use a pinned release of this one,
not whatever is on main. To publish a change for them:
- Merge the change to
main. - Tag it:
git tag -a vX.Y.Z -m vX.Y.Z && git push origin vX.Y.Z. A change to the public API bumps the minor version (v0.1.0→v0.2.0); anything else bumps the patch (v0.1.0→v0.1.1). - In each project that uses it, edit the tag in
[tool.uv.sources], runuv lock --upgrade-package <name>, and open a pull request so CI checks the pair together.
Related Projects
This project is one of a series that builds real systems "from scratch in Python" to teach how they work. See the series home for the full story.
| Project | What it teaches | Repository | Docs |
|---|---|---|---|
| PyOS | Operating systems | py-os | Docs |
| Pebble | Compilers and programming languages | pebble-lang | Docs |
| PyDB | Relational databases | pydb | Docs |
| PyKV | Key-value stores | pykv | Docs |
| PyDocDB | Document databases | pydoc-db | Docs |
| PyGraphDB | Graph databases | pygraphdb | Docs |
| PyTSDB | Time-series databases | pytsdb | Docs |
| PyVecDB | Vector databases | pyvecdb | Docs |
| PyColDB | Column-family databases | pycoldb | Docs |
| PyCrypt | Cryptography | pycrypt | Docs |
| PyNet | Computer networking | pynet | Docs |
| PyWeb | HTTP web servers | pyweb | Docs |
| PyGit | Version control | pygit | Docs |
| PySearch | Search engines | pysearch | Docs |
| PyMQ | Message queues | pymq | Docs |
| PyLLM | Language models (the brain) | pyllm | Docs |
| PyScheduler | Job scheduling | pyscheduler | Docs |
| PyStack | Full-stack integration | pystack | Docs |
License
MIT
Metadata
Release files for fromscratch-pyweb 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| fromscratch_pyweb-0.4.0.tar.gz | 223.8 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fromscratch_pyweb-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 292.0 kB
Release files / fromscratch_pyweb-0.4.0.tar.gz
| Download URL | fromscratch_pyweb-0.4.0.tar.gz |
|---|---|
| Size | 223.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8f03bf08884a5c004442d7c01264b44c82b4f4e9253b0d6b65702388984ee461
|
|
BLAKE2b-256 checksum How to use checksums |
26fff2b0c7e33ae6c5cb8f00e8dea2d0786ed2ca9aa4ddc8e3b637318972d830
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logRelease files / fromscratch_pyweb-0.4.0-py3-none-any.whl
| Download URL | fromscratch_pyweb-0.4.0-py3-none-any.whl |
|---|---|
| Size | 68.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
16f3b4a61978fe8d5bc778a8a08c7be629e052af803599b7cecc246b9e9e3cc1
|
|
BLAKE2b-256 checksum How to use checksums |
c320df06cc31f6ad5df1fa69ce833fbaefea7c57b2c8945ed7f6f319a43fe8c2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logSecurity for fromscratch-pyweb 0.4.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for fromscratch_pyweb-0.4.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.