https://github.com/kennethreitz/responder
A familiar HTTP Service Framework for Python.
https://github.com/kennethreitz/responder
falcon flask graphql http-framework microservices python web-services
Last synced: 20 days ago
JSON representation
A familiar HTTP Service Framework for Python.
- Host: GitHub
- URL: https://github.com/kennethreitz/responder
- Owner: kennethreitz
- License: apache-2.0
- Created: 2018-10-09T11:15:00.000Z (over 7 years ago)
- Default Branch: main
- Last Pushed: 2026-03-22T17:58:19.000Z (30 days ago)
- Last Synced: 2026-03-23T00:39:37.632Z (30 days ago)
- Topics: falcon, flask, graphql, http-framework, microservices, python, web-services
- Language: Python
- Homepage: http://responder.kennethreitz.org/
- Size: 12.9 MB
- Stars: 3,617
- Watchers: 89
- Forks: 216
- Open Issues: 21
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
- best-of-web-python - GitHub - 5% open · ⏱️ 02.02.2025): (Web Frameworks)
- awesome-systools - responder
README
# Responder
A familiar HTTP Service Framework for Python, powered by [Starlette](https://www.starlette.io/).
```python
import responder
api = responder.API()
@api.route("/{greeting}")
async def greet_world(req, resp, *, greeting):
resp.text = f"{greeting}, world!"
if __name__ == "__main__":
api.run()
```
$ pip install responder
That's it. Supports Python 3.10+.
## The Basics
- `resp.text` sends back text. `resp.html` sends back HTML. `resp.content` sends back bytes.
- `resp.media` sends back JSON (or YAML, with content negotiation).
- `resp.file("path.pdf")` serves a file with automatic content-type detection.
- `req.headers` is case-insensitive. `req.params` gives you query parameters.
- Both sync and async views work — the `async` is optional.
## Highlights
```python
# Type-safe route parameters
@api.route("/users/{user_id:int}")
async def get_user(req, resp, *, user_id):
resp.media = {"id": user_id}
# HTTP method filtering
@api.route("/items", methods=["POST"])
async def create_item(req, resp):
data = await req.media()
resp.media = {"created": data}
# Class-based views
@api.route("/things/{id}")
class ThingResource:
def on_get(self, req, resp, *, id):
resp.media = {"id": id}
def on_post(self, req, resp, *, id):
resp.text = "created"
# Before-request hooks (auth, rate limiting, etc.)
@api.route(before_request=True)
def check_auth(req, resp):
if not req.headers.get("Authorization"):
resp.status_code = 401
resp.media = {"error": "unauthorized"}
# Custom error handling
@api.exception_handler(ValueError)
async def handle_error(req, resp, exc):
resp.status_code = 400
resp.media = {"error": str(exc)}
# Lifespan events
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app):
print("starting up")
yield
print("shutting down")
api = responder.API(lifespan=lifespan)
# GraphQL
import graphene
api.graphql("/graphql", schema=graphene.Schema(query=Query))
# WebSockets
@api.route("/ws", websocket=True)
async def websocket(ws):
await ws.accept()
while True:
name = await ws.receive_text()
await ws.send_text(f"Hello {name}!")
# Mount WSGI/ASGI apps
from flask import Flask
flask_app = Flask(__name__)
api.mount("/flask", flask_app)
# Background tasks
@api.route("/work")
def do_work(req, resp):
@api.background.task
def process():
import time; time.sleep(10)
process()
resp.media = {"status": "processing"}
```
Built-in OpenAPI docs, cookie-based sessions, gzip compression, static file serving, Jinja2 templates, and a production uvicorn server.
Route convertors: `str`, `int`, `float`, `uuid`, `path`.
## Documentation
https://responder.kennethreitz.org