Library / SDK
kennethreitz/responder avatar
kennethreitz/responder

Responder: a Flask-shaped HTTP framework on ASGI

A familiar HTTP Service Framework for Python.

3,616 stars217 forksPythonApache-2.0

At a glance

What is it?
Responder keeps the req/resp view signature that Flask and Falcon users know, and runs it on Starlette with typed OpenAPI contracts. Here is what it does, how to start, and where it stops being the right tool.
Who is it for?
Adopt Responder if your team already writes Flask or Falcon views and wants ASGI, typed Pydantic contracts and generated OpenAPI without switching mental models. Skip it if you need a large third-party extension ecosystem or you are pinned below Python 3.11, since pyproject.toml sets requires-python to >=3.11.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 63 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 2, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Who Responder is for, and the problem it removes

Most Python teams do not struggle to pick a web framework. They struggle to pick two. Flask gives a view function and a mutable response object; FastAPI gives type-driven validation and generated documentation; Starlette gives ASGI and a routing layer. Assembling all three means writing glue that nobody owns.

Responder's answer is to keep one signature and add the rest around it. The README states the project is "the friendly request/response shape of Flask and Falcon, brought to ASGI with Starlette underneath." Every view receives req and resp, reads from one and writes to the other, and both sync and async views work. The target reader is someone who has written Flask handlers for years and does not want to learn a dependency-injection system to return JSON.

The framing matters more than the feature list. Responder is not trying to be the fastest framework or the most extensible one. It is trying to be the one you can read six months later, which is a different design goal with different trade-offs.

The req/resp model and what Starlette does underneath

A Responder view is a callable that takes req, resp and optional keyword arguments extracted from the path. Path convertors are typed, so a route declared as /users/{user_id:int} hands the view an int rather than a string. The response object is mutable: you assign resp.text, resp.html or resp.media, and the framework serializes on the way out.

Routing, ASGI plumbing and the server come from Starlette and uvicorn, both listed as dependencies in pyproject.toml. Responder adds the view shape, the response helpers, Pydantic-backed validation, OpenAPI generation through apispec, and operational pieces such as request IDs and structured access logs.

Content negotiation is handled for you. The README says resp.media is JSON by default, with YAML and msgpack available by negotiation. resp.file() detects the content type. Returning a value from a view also works, so a one-line handler can return a dict and skip the assignment entirely.

The composition story is the escape hatch worth noting. The README lists mounting Flask, Django, WSGI and ASGI applications under one API object. That means an existing WSGI service can sit next to new Responder routes during a migration instead of being rewritten first.

Installing Responder and serving a first endpoint

The README gives the install command with the orjson extra. Python 3.11 or newer is required according to pyproject.toml, so check your interpreter before starting.

bash
pip install "responder[orjson]"

Create a file with an API instance and one route. The README's quickstart example is exactly this:

python
import responder

api = responder.API()


@api.get("/hello/{name}")
def hello(req, resp, *, name):
    resp.media = {"hello": name}


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

Run it with python app.py. The default development server listens on port 5042, so opening http://127.0.0.1:5042/hello/world returns {"hello": "world"}. The port and the URL both come from the README; nothing needs configuring for this to work.

To read a request body, the README shows an async view awaiting req.media(). Note the trailing parentheses: media is a method on the request side and a property on the response side.

python
@api.post("/echo")
async def echo(req, resp):
    payload = await req.media()
    resp.media = {"you_sent": payload}

For a typed endpoint, declare Pydantic models and pass them as view arguments. The README's store example uses ItemIn for the body and ItemOut for the response, with the API constructed as responder.API(title="Store API", version="1.0", openapi="3.1.0", docs_route="/docs", request_id=True). With that configuration, Swagger UI is served at /docs and validation failures come back as Problem Details responses.

Typed contracts, streaming, and the operational defaults added in v9

The type inference is the part that separates Responder from a plain Flask clone. Annotating a view's return type as ItemOut makes the response model appear in the generated OpenAPI document, and the same inference works for list[ItemOut] on collection routes. OpenAPI 3.0 and 3.1 are both supported, and the README points to generated clients as part of that story.

Streaming is treated as a contract rather than an afterthought. The @api.sse decorator takes a heartbeat argument and expects an async iterator yielding responder.SSE objects, each carrying an event name and an id. The @api.ndjson decorator expects an async iterator of plain models. In both cases the README says each item is validated and serialized as it is yielded, the OpenAPI document carries the item schema, and generated clients expose a lazy iterator instead of buffering.

Version 9 changed several defaults, and these are the details most likely to surprise an upgrading team. Multipart uploads now stream from the wire and spool to disk rather than buffering whole files in memory. Request bodies are capped at 100 MiB by default, and API(max_request_size=None) restores the older unlimited behavior. API(csrf=True) adds session-bound CSRF protection for unsafe requests, with per-route opt-outs intended for webhooks. API(trust_proxy_headers=True) rewrites scheme, host and client IP from trusted reverse-proxy headers.

Framework-generated errors now use RFC 9457-style application/problem+json responses. The README states that OpenAPI documents operational responses including CSRF 403, body-cap 413, rate-limit 429, fail-closed limiter 503, validation 422 and timeout 504 where they can occur. Documenting failure modes in the spec is a genuinely useful choice, and it is rarer than it should be.

Where Responder is the wrong choice

The dependency list is long for a framework that markets itself on being small. Installing Responder brings in Starlette, uvicorn, Pydantic, marshmallow, apispec, Jinja2, httpx, msgpack, PyYAML, itsdangerous, a2wsgi, chardet, docopt-ng, python-multipart and httpx2. Several of those exist to support features you may never enable. If your service is a dozen endpoints and a database call, you are carrying a validation stack and a documentation generator for nothing.

The Python floor is 3.11. Teams still running 3.9 or 3.10 cannot use this at all, and pyproject.toml is explicit about it. That is not a small constraint in environments where the interpreter version is set by a base image someone else controls.

The extension ecosystem is the sharper limitation. Flask has years of third-party packages for things Responder does not cover. Responder's own answer is the mounting escape hatch: bring the Flask app along rather than looking for a Responder plugin. That works, but it means two frameworks in one process and two sets of conventions for the next person.

Finally, the 100 MiB body cap is a real behavior change. Applications that accepted large uploads on an earlier major version will start returning 413 until someone passes API(max_request_size=None) or raises the limit deliberately. The README points to a v9 migration guide for upgrades, and that guide is the first thing an upgrading team should read. The README does not document rollback behavior for the migration, so plan the upgrade as a one-way move.

How Responder differs from FastAPI and Flask

FastAPI is the closest comparison, and the difference is the shape of a view. FastAPI builds on type annotations and dependency injection: parameters are declared in the signature, dependencies are declared with Depends, and the framework assembles them. Responder keeps an explicit req and resp pair that you read from and write to. If you have ever found a FastAPI handler hard to follow because half its behavior lives in decorator arguments and dependency graphs, Responder's model is the alternative. If you like that style, Responder will feel like a step backward.

Flask is the other reference point, and the split is ASGI. Flask is WSGI, so async views and streaming responses run through a compatibility layer. Responder is ASGI end to end, with uvicorn by default and optional Granian, per the README. That matters for SSE and NDJSON endpoints, which are first-class here rather than bolted on.

Starlette itself is the third option, and Responder is a layer on top of it. Choosing Starlette directly means owning the validation, the OpenAPI generation and the response helpers yourself. Choosing Responder means accepting its opinions about all three in exchange for writing less code. The trade is reasonable for teams that want the opinions; it is friction for teams that already have their own.

Maintenance, licensing, and upgrade cost

The repository is not archived, and the last push was on 2026-08-01. Three releases landed in July 2026: v9.0.1 on 2026-07-06, v9.1.0 on 2026-07-12 and v9.2.0 on 2026-07-14. The project is on a 9.x line, and the README links a v9 migration guide for teams coming from an earlier major version.

The licence is Apache-2.0, declared in pyproject.toml using the SPDX identifier, and the build configuration requires setuptools>=77 because of it. Apache-2.0 includes an explicit patent grant and permits commercial and closed-source use. It also requires that you preserve copyright and licence notices and state significant changes. That is a summary of the licence text, not legal advice; route the specifics through whoever handles licensing on your side.

Upgrade cost concentrates in the v9 defaults rather than in the API surface. The view signature, the response helpers and the routing decorators described in the README are unchanged. What moves is behavior: the body cap, the Problem Details error format, and CSRF being opt-in via API(csrf=True). For development, the Makefile runs everything through uv, with make test invoking pytest, make lint running ruff, make types running mypy, and make check running all three. There is no published support window or long-term release branch in the README, so treat the 9.x line as the only supported target.

Editorial conclusion

Adopt Responder if your team already writes Flask or Falcon views and wants ASGI, typed Pydantic contracts and generated OpenAPI without switching mental models. Skip it if you need a large third-party extension ecosystem or you are pinned below Python 3.11, since pyproject.toml sets requires-python to >=3.11. Before committing, run your own load test against the 100 MiB default body cap and confirm that API(max_request_size=None) is acceptable to your security reviewers.

Frequently asked questions

What Python version does Responder require?

pyproject.toml sets requires-python to >=3.11 and classifies the package for CPython 3.11 through 3.15, including free-threading builds. Anything older cannot install it.

Which port does Responder use by default?

The README's quickstart runs the app with python app.py and says to open http://127.0.0.1:5042/hello/world, so the built-in uvicorn runner listens on port 5042.

Is Responder built on Starlette?

Yes. The README describes Responder as the request/response shape of Flask and Falcon brought to ASGI with Starlette underneath, and starlette>=1 appears in the dependency list in pyproject.toml.

What is the default request body size limit in Responder?

The v9 highlights state that request bodies are capped at 100 MiB by default, and that API(max_request_size=None) restores the earlier unlimited behavior. Requests over the cap produce a 413 Problem Details response.

What licence does Responder use?

pyproject.toml declares license = "Apache-2.0" using the SPDX identifier, and the build configuration requires setuptools>=77 for that declaration. The repository also carries a LICENSE file at the top level.

Official sources

  1. kennethreitz/responder on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/kennethreitz-responder.svg)](https://hysenlabs.com/projects/kennethreitz-responder)