Files
kennethreitz.org/data/software/responder.md
T
kennethreitz 47305360c7 Heavily improve all software pages with docs links, examples, context
Added documentation links, expanded code examples, personal context,
and related essay links across all 11 software pages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 15:23:23 -04:00

3.5 KiB

Responder: A Familiar HTTP Service Framework

Responder is a web framework for Python that flips Requests inside out. If Requests is how you consume HTTP, Responder is how you serve it — using the same mental model.

$ uv pip install responder

What It Looks Like

import responder

api = responder.API()

@api.route("/")
def home(req, resp):
    resp.html = "<h1>Hello, world.</h1>"

@api.route("/api/data")
def data(req, resp):
    resp.media = {"message": "Hello from Responder", "status": "ok"}

@api.route("/greet/{name}")
async def greet(req, resp, *, name):
    resp.text = f"Hello, {name}!"

if __name__ == "__main__":
    api.run()

resp.text sends text. resp.html sends HTML. resp.media sends JSON. The async keyword is optional — use it when you need it, skip it when you don't.

Built-In Batteries

import responder

api = responder.API()

# Built-in background tasks.
@api.route("/upload")
async def upload(req, resp):
    data = await req.media()

    @api.background.task
    def process(data):
        # This runs after the response is sent.
        expensive_operation(data)

    process(data)
    resp.media = {"status": "processing"}

# GraphQL support out of the box.
import graphene

class Query(graphene.ObjectType):
    hello = graphene.String(name=graphene.String(default_value="world"))
    def resolve_hello(self, info, name):
        return f"Hello, {name}!"

schema = graphene.Schema(query=Query)
api.add_route("/graph", schema)

# OpenAPI schema generation.
# Visit /docs for interactive API documentation.

# WebSocket support.
@api.route("/ws", websocket=True)
async def websocket(ws):
    await ws.accept()
    while True:
        data = await ws.receive_text()
        await ws.send_text(f"Echo: {data}")

Background tasks, GraphQL, WebSockets, OpenAPI docs, and Jinja2 templates — all built in. No extensions to install. No configuration to fumble with.

The Idea

I wanted to take the API primitives from Requests and put them into a web framework. The niceties of Flask and the performance philosophy of Falcon, unified with a Requests-like interface for responses. Setting resp.content sends bytes. Setting resp.media sends JSON. Case-insensitive headers. Familiar status codes. If you know Requests, you already know half of Responder.

It was a bit ahead of its time. Some of these ideas — automatic async handling, type-aware serialization, built-in OpenAPI — showed up later in FastAPI, which I'd recommend for production use today. Responder was always more of an experiment in API design than a production framework. But as an exercise in "what if the server-side felt like the client-side?" I think it holds up.

The deeper question Responder tried to answer: why do we accept that consuming an API and serving an API should feel like completely different activities? They're the same protocol. The mental model should be the same.

Install

$ uv pip install responder

Resources