This commit is contained in:
kennethreitz
2026-03-22 17:08:16 +00:00
parent 5c5d0bbd74
commit f28cd7001a
15 changed files with 922 additions and 540 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+50 -33
View File
@@ -1,34 +1,32 @@
Deployment
==========
Responder applications are standard ASGI apps. You can deploy them anywhere
you'd deploy a Python web service.
Responder applications are standard `ASGI <https://asgi.readthedocs.io/>`_
apps. ASGI (Asynchronous Server Gateway Interface) is the modern successor
to WSGI — it supports async, WebSockets, and HTTP/2. This means you can
deploy a Responder app anywhere that runs Python, using any ASGI server.
Running Locally
---------------
The simplest way to run your application::
# api.py
import responder
api = responder.API()
@api.route("/")
def hello(req, resp):
resp.text = "hello, world!"
During development, ``api.run()`` is all you need::
if __name__ == "__main__":
api.run()
This starts a production uvicorn server on ``127.0.0.1:5042``.
This starts a `uvicorn <https://www.uvicorn.org/>`_ server on
``127.0.0.1:5042``. Uvicorn is a lightning-fast ASGI server built on
`uvloop <https://uvloop.readthedocs.io/>`_ — it handles thousands of
concurrent connections efficiently and protects against slowloris attacks,
making a reverse proxy like nginx optional for many deployments.
Docker
------
A minimal Dockerfile for deploying a Responder application::
Docker is the most common way to package and deploy web applications.
Here's a minimal Dockerfile::
FROM python:3.13-slim
WORKDIR /app
@@ -43,44 +41,63 @@ Build and run::
$ docker build -t myapi .
$ docker run -p 8000:80 myapi
The ``python:3.13-slim`` image is about 150MB — small enough for fast
deploys but includes everything you need. For even smaller images, you
can use ``python:3.13-alpine``, though some packages may need extra
build dependencies.
Cloud Platforms
---------------
Responder automatically honors the ``PORT`` environment variable, which is
set by most cloud platforms. When ``PORT`` is set, Responder binds to
``0.0.0.0`` on that port automatically.
Responder automatically honors the ``PORT`` environment variable. When
``PORT`` is set, the server binds to ``0.0.0.0`` on that port — this is
the convention that virtually every cloud platform uses.
This works out of the box with:
This means zero configuration on:
- **Fly.io**
- **Railway**
- **Render**
- **Google Cloud Run**
- **Azure Container Apps**
- **AWS App Runner**
- **Fly.io** — ``fly launch`` and you're done
- **Railway** — push your code, Railway sets ``PORT``
- **Render** — set start command to ``python api.py``
- **Google Cloud Run** — containerize and deploy
- **Azure Container Apps** — same pattern
- **AWS App Runner** — and here too
Just deploy your code and set the start command to ``python api.py``.
The pattern is always the same: deploy your code, set the start command
to ``python api.py``, and the platform handles the rest.
Uvicorn Directly
----------------
For more control over the production server, you can bypass ``api.run()``
and use uvicorn directly::
For production deployments where you want more control, bypass
``api.run()`` and use uvicorn directly::
$ uvicorn api:api --host 0.0.0.0 --port 8000 --workers 4
This gives you access to all of uvicorn's options: worker count, SSL
certificates, access logging, and more. See the
`uvicorn documentation <https://www.uvicorn.org/>`_ for details.
The ``--workers`` flag spawns multiple processes, each handling requests
independently. A good starting point is 2-4 workers per CPU core.
Uvicorn supports many options — SSL certificates, access logging, graceful
shutdown timeouts, and more. See the
`uvicorn documentation <https://www.uvicorn.org/deployment/>`_ for details.
Reverse Proxy
-------------
In production, you may want to place Responder behind a reverse proxy like
nginx or Caddy for SSL termination, load balancing, or serving static assets.
For high-traffic production deployments, you may want a reverse proxy like
`nginx <https://nginx.org/>`_ or `Caddy <https://caddyserver.com/>`_ in
front of your application for:
- **SSL/TLS termination** — let the proxy handle HTTPS certificates
- **Load balancing** — distribute traffic across multiple app instances
- **Static asset serving** — offload static files to the proxy
- **Rate limiting** — at the infrastructure level
Responder's ``TrustedHostMiddleware`` and ``HTTPSRedirectMiddleware`` work
correctly behind proxies that set standard forwarding headers.
correctly behind proxies that set standard forwarding headers
(``X-Forwarded-For``, ``X-Forwarded-Proto``).
That said, uvicorn is production-ready on its own. Many applications run
uvicorn directly without a reverse proxy and do just fine.
+155 -57
View File
@@ -2,164 +2,229 @@ Quick Start
===========
This guide will walk you through the basics of building a web service with
Responder. By the end, you'll know how to declare routes, handle requests,
send responses, render templates, and process background tasks.
Responder. By the end, you'll understand how HTTP requests and responses
work, how to define routes, read data from clients, send data back, render
HTML templates, and process work in the background.
Create a Web Service
--------------------
The first thing you need to do is declare a web service. This is the central
object that holds all your routes, middleware, and configuration::
Every web application starts with a single object — the application
instance. In Responder, this is the ``API`` class. It holds your routes,
middleware, templates, and configuration. Think of it as the central
nervous system of your web service::
import responder
api = responder.API()
That's it. One import, one line. You now have a fully functional ASGI
application with gzip compression, static file serving, session support,
and a production-ready server — all wired up and ready to go.
Hello World
-----------
Next, add a route. Here, we'll make the root URL say "hello, world!"::
A web service isn't very useful until it can respond to requests. In HTTP,
a *route* maps a URL path to a function that handles it. When a client
(like a browser or ``curl``) sends a request to that path, your function
runs and produces a response.
Here's the simplest possible route::
@api.route("/")
def hello_world(req, resp):
resp.text = "hello, world!"
Every view receives a ``req`` (request) and ``resp`` (response) object. You
don't need to return anything — just mutate the response directly.
Two things to notice:
1. Every view function receives two arguments: ``req`` (the incoming
request) and ``resp`` (the outgoing response).
2. You don't return anything. Instead, you *mutate* the response object
directly. This is a deliberate design choice — it keeps the API
consistent whether you're setting text, JSON, headers, cookies, or
status codes.
Run the Server
--------------
Start your web service with ``api.run()``::
Start your web service with a single call::
api.run()
This spins up a production-grade uvicorn server on port ``5042``, ready for
incoming HTTP requests.
This spins up a production-grade `uvicorn <https://www.uvicorn.org/>`_
server on port ``5042``, ready for incoming HTTP requests. Open
``http://localhost:5042`` in your browser and you'll see your hello world
response.
You can customize the port with ``api.run(port=8000)``. The ``PORT``
environment variable is also honored automatically — when set, Responder
binds to ``0.0.0.0`` on that port, which is what cloud platforms like
Fly.io, Railway, and Google Cloud Run expect.
binds to ``0.0.0.0`` on that port, which is what cloud platforms expect.
.. note::
Both sync and async views are supported. The ``async`` keyword is always
optional — use it when you need to ``await`` something.
optional — use it when you need to ``await`` something, like reading a
request body or querying a database.
Route Parameters
----------------
If you want dynamic URLs, use Python's familiar f-string syntax to declare
variables in your routes::
Static URLs like ``/about`` are useful, but most applications need dynamic
routes — URLs that contain variable data, like a user ID or a product slug.
In Responder, you declare route parameters using Python's f-string syntax::
@api.route("/hello/{who}")
def hello_to(req, resp, *, who):
resp.text = f"hello, {who}!"
A ``GET`` request to ``/hello/world`` will respond with ``hello, world!``.
A request to ``/hello/guido`` will respond with ``hello, guido!``.
Route parameters are passed as keyword-only arguments (after the ``*``).
Route parameters are passed as *keyword-only* arguments (after the ``*``
in the function signature). This is a Python feature that makes the
interface explicit — you always know which arguments come from the URL.
Type Convertors
^^^^^^^^^^^^^^^
You can constrain route parameters to specific types. The parameter will be
automatically converted before it reaches your view::
By default, route parameters are strings. But often you want them as
integers, UUIDs, or other types. Responder can convert them automatically
using type annotations in the route pattern::
@api.route("/add/{a:int}/{b:int}")
async def add(req, resp, *, a, b):
resp.text = f"{a} + {b} = {a + b}"
Here, ``a`` and ``b`` will arrive as Python ``int`` objects, not strings.
If someone requests ``/add/3/hello``, they'll get a 404 — the route won't
match because ``hello`` isn't a valid integer.
Supported types:
- ``str`` — matches any string without slashes (default)
- ``int`` — matches digits, converts to ``int``
- ``float`` — matches decimal numbers, converts to ``float``
- ``str`` — matches any string without slashes (this is the default)
- ``int`` — matches digits and converts to ``int``
- ``float`` — matches decimal numbers and converts to ``float``
- ``uuid`` — matches UUID strings like ``550e8400-e29b-41d4-a716-446655440000``
- ``path`` — matches any string *including* slashes, useful for file paths
like ``/files/{filepath:path}``
Sending Responses
-----------------
Responder gives you several ways to send data back to the client. Just set
the appropriate property on the response object.
When an HTTP server receives a request, it must send back a response. Every
HTTP response has three parts: a status code (like ``200 OK`` or ``404 Not
Found``), headers (metadata like ``Content-Type``), and a body (the actual
data).
**Text and HTML**::
Responder lets you set all three by mutating the response object.
**Text and HTML** — the simplest response types. ``resp.text`` sets the
``Content-Type`` to ``text/plain``, while ``resp.html`` sets it to
``text/html``::
resp.text = "plain text response"
resp.html = "<h1>HTML response</h1>"
**JSON** — the most common pattern for APIs. Set ``resp.media`` to any
JSON-serializable Python object::
**JSON** — the lingua franca of web APIs. Set ``resp.media`` to any
JSON-serializable Python object — a dict, a list, whatever — and Responder
will serialize it to JSON and set the right headers::
@api.route("/hello/{who}/json")
def hello_json(req, resp, *, who):
resp.media = {"hello": who}
If the client sends an ``Accept: application/x-yaml`` header, the same data
will be returned as YAML instead. Content negotiation is automatic.
will be returned as YAML instead. This is called *content negotiation* —
the server and client agree on a format. It happens automatically.
**Files** — serve a file from disk with automatic content-type detection::
**Files** — serve a file from disk. Responder uses Python's ``mimetypes``
module to figure out the ``Content-Type`` from the file extension::
resp.file("reports/annual.pdf")
**Raw bytes**::
**Raw bytes** — for binary data like images or protocol buffers::
resp.content = b"\x89PNG\r\n..."
**Status codes and headers**::
**Status codes** — HTTP status codes tell the client what happened. ``200``
means success, ``201`` means something was created, ``404`` means not found,
``500`` means the server broke. Set it directly::
resp.status_code = 201
**Headers** — HTTP headers carry metadata. Common ones include
``Content-Type``, ``Cache-Control``, ``Authorization``, and custom
application headers::
resp.headers["X-Custom"] = "value"
**Redirects**::
**Redirects** — tell the client to go somewhere else::
api.redirect(resp, location="/new-url")
This sends a ``301 Moved Permanently`` response by default. The client's
browser will automatically follow the redirect.
Reading Requests
----------------
The request object gives you access to everything the client sent.
The other half of HTTP is the request — the data the client sends to your
server. This includes the HTTP method (GET, POST, PUT, DELETE), the URL,
headers, query parameters, cookies, and optionally a body.
**Method and URL**::
Responder wraps all of this in the ``req`` object.
**Method and URL** — every HTTP request has a method (what the client wants
to do) and a URL (what resource it's about)::
req.method # "get", "post", etc. (lowercase)
req.full_url # "http://example.com/path?q=1"
req.url # parsed URL object
**Headers** — case-insensitive, just like you'd expect::
**Headers** — HTTP headers carry metadata from the client, like what
content types it accepts, authentication tokens, and more. Responder's
headers dict is case-insensitive, because the HTTP spec says header names
are case-insensitive::
req.headers["Content-Type"]
req.headers["content-type"] # same thing
**Query parameters**::
**Query parameters** — the part of the URL after the ``?``. These are
commonly used for search, filtering, and pagination::
# GET /search?q=python&page=2
req.params["q"] # "python"
req.params["page"] # "2"
**Path parameters** — also available on the request object::
Note that query parameters are always strings. If you need an integer,
you'll need to convert it yourself: ``int(req.params["page"])``.
**Path parameters** — the dynamic parts of the URL that matched your route
pattern. These are also available on the request object, which is useful
in before-request hooks where they aren't passed as function arguments::
req.path_params["user_id"] # same as the keyword argument
**Request body** — for POST/PUT/PATCH requests, you need to ``await`` the
body content::
**Request body** — for POST, PUT, and PATCH requests, the client sends
data in the body. Since reading the body is an I/O operation, you need to
``await`` it::
# JSON body
# JSON body (the most common format for APIs)
data = await req.media()
# Form data
# Form data (from HTML forms)
data = await req.media("form")
# File uploads
# File uploads (multipart)
files = await req.media("files")
# Raw bytes
@@ -170,41 +235,59 @@ body content::
**Other useful properties**::
req.is_json # True if content type is JSON
req.cookies # dict of cookies
req.session # session data (dict)
req.client # (host, port) tuple
req.is_secure # True if HTTPS
req.is_json # True if the content type is JSON
req.cookies # dict of cookies sent by the client
req.session # session data (a signed, server-side dict)
req.client # (host, port) tuple — the client's IP address
req.is_secure # True if the request came over HTTPS
Rendering Templates
-------------------
Responder includes built-in `Jinja2 <https://jinja.palletsprojects.com/>`_
support. Templates are loaded from the ``templates/`` directory by default.
While APIs typically return JSON, many web applications need to render
HTML pages. Responder includes built-in support for
`Jinja2 <https://jinja.palletsprojects.com/>`_, one of the most popular
templating engines in the Python ecosystem.
The simplest way is to use ``api.template()``::
Templates let you write HTML with placeholders that get filled in with
dynamic data. This keeps your presentation logic (HTML) separate from
your application logic (Python) — a pattern called
*separation of concerns*.
The simplest way to render a template is ``api.template()``. Templates
are loaded from the ``templates/`` directory by default::
@api.route("/hello/{name}/html")
def hello_html(req, resp, *, name):
resp.html = api.template("hello.html", name=name)
You can also use the ``Templates`` class directly for more control::
The template file ``templates/hello.html`` might look like::
<h1>Hello, {{ name }}!</h1>
The ``{{ name }}`` part is a Jinja2 expression — it gets replaced with
the value you passed in.
You can also use the ``Templates`` class directly for more control over
the template directory and configuration::
from responder.templates import Templates
templates = Templates(directory="templates")
templates = Templates(directory="my_templates")
@api.route("/page")
def page(req, resp):
resp.html = templates.render("page.html", title="Hello")
Async rendering is supported too::
For applications that need non-blocking template rendering (rare, but
useful under extreme load), async rendering is supported::
templates = Templates(directory="templates", enable_async=True)
resp.html = await templates.render_async("page.html", title="Hello")
You can render template strings without a file::
And for quick one-off templates, you can render a string directly without
a file::
resp.html = api.template_string("Hello, {{ name }}!", name="world")
@@ -213,7 +296,13 @@ Background Tasks
----------------
Sometimes you want to accept a request, respond immediately, and do the
actual processing later. Responder makes this easy with background tasks::
actual processing later. This is a common pattern for operations that take
a long time — sending emails, processing images, updating caches, or
calling slow external APIs.
Responder makes this easy with background tasks. Decorate any function
with ``@api.background.task`` and it will run in a thread pool, separate
from the request/response cycle::
@api.route("/incoming")
async def receive_incoming(req, resp):
@@ -227,8 +316,17 @@ actual processing later. Responder makes this easy with background tasks::
process_data(data)
# Respond immediately processing continues in the background
# This response is sent immediately, while process_data
# continues running in the background.
resp.media = {"status": "accepted"}
The ``@api.background.task`` decorator wraps any function to run in a thread
pool. The client gets an immediate response while the work continues.
The client gets an instant response — the heavy lifting happens after.
This is the same pattern used by task queues like Celery, but much simpler
for lightweight use cases where you don't need a full message broker.
.. note::
Background tasks run in threads, not processes. They share memory with
your application, which makes them fast to start but means CPU-intensive
work will block the event loop. For heavy computation, consider a proper
task queue.
+1 -1
View File
@@ -279,7 +279,7 @@ Tips
``API()`` configuration (like ``cors=True``), create a new instance
in the test rather than sharing the fixture.
- **Use ``api.url_for()``** instead of hard-coded paths. It's a small
- Use ``api.url_for()`` instead of hard-coded paths. It's a small
thing, but it makes refactoring painless.
- **Test the contract, not the implementation.** Assert on status codes,
+265 -176
View File
@@ -1,15 +1,27 @@
Feature Tour
============
This section walks through Responder's features in detail. Each section
includes working code examples you can copy into your application.
This section walks through Responder's features in depth. Each section
explains the concept, shows working code, and explains the design choices
behind it. If you're new to web development, this is a good place to learn
how modern web frameworks work under the hood.
Method Filtering
----------------
By default, a route matches all HTTP methods. If you want to restrict a
route to specific methods, pass the ``methods`` parameter::
HTTP defines several *methods* (also called verbs) that describe what a
client wants to do with a resource. The most common are:
- ``GET`` — retrieve data
- ``POST`` — create something new
- ``PUT`` — replace something entirely
- ``PATCH`` — update part of something
- ``DELETE`` — remove something
By default, a Responder route matches all methods. This is fine for simple
endpoints, but REST APIs typically map different methods to different
operations. Use the ``methods`` parameter to restrict a route::
@api.route("/items", methods=["GET"])
def list_items(req, resp):
@@ -20,15 +32,22 @@ route to specific methods, pass the ``methods`` parameter::
data = await req.media()
resp.media = {"created": data}
Note the ``check_existing=False`` — this allows you to register multiple
handlers for the same path with different methods.
Note the ``check_existing=False`` — Responder normally prevents you from
registering two routes with the same path (to catch typos). When you
intentionally want multiple handlers for the same path with different
methods, you need to opt in.
Class-Based Views
-----------------
For more complex resources, you can use class-based views. Responder will
dispatch to the appropriate method handler based on the HTTP method::
Function-based views are great for simple endpoints, but sometimes you want
to group related HTTP methods together into a single resource. This is
where class-based views come in — a pattern popularized by
`Falcon <https://falconframework.org/>`_.
Responder dispatches to the appropriate method handler based on the HTTP
method::
@api.route("/{greeting}")
class GreetingResource:
@@ -47,14 +66,19 @@ middleware scoped to a single route. Method-specific handlers (``on_get``,
``on_post``, ``on_put``, ``on_delete``, etc.) are called after.
No inheritance required — just define a class with the right method names.
This is simpler than Django's ``View`` classes and more Pythonic than
framework-specific base classes.
Lifespan Events
---------------
Modern applications often need to set up resources on startup (database
connections, caches, ML models) and tear them down on shutdown. Responder
supports the lifespan context manager pattern::
Real applications need to set up resources when they start (database
connection pools, ML models, caches) and tear them down when they stop.
This is called the application *lifespan*.
The modern approach is the *context manager* pattern, where startup and
shutdown are two halves of the same block::
from contextlib import asynccontextmanager
@@ -68,7 +92,11 @@ supports the lifespan context manager pattern::
api = responder.API(lifespan=lifespan)
You can also use the traditional event decorator style::
Everything before ``yield`` runs at startup. Everything after runs at
shutdown. If startup fails, the server won't start. If shutdown raises,
it's logged but the server still exits.
The traditional event decorator style also works::
@api.on_event("startup")
async def startup():
@@ -78,57 +106,71 @@ You can also use the traditional event decorator style::
async def shutdown():
print("shutting down")
The context manager approach is preferred for new code — it makes the
startup/shutdown relationship explicit and keeps related code together.
The context manager is preferred for new code — it keeps related startup
and shutdown logic together and makes resource cleanup more explicit.
Serving Files
-------------
Serve files from disk with automatic content-type detection. Responder
uses Python's ``mimetypes`` module to figure out the right ``Content-Type``
header for you::
Web applications often need to serve files — downloads, reports, images.
Responder makes this simple with ``resp.file()``, which reads a file from
disk and sets the ``Content-Type`` header automatically using Python's
``mimetypes`` module::
@api.route("/download")
def download(req, resp):
resp.file("reports/annual.pdf")
You can override the content type if needed::
You can override the content type if the automatic detection isn't right::
@api.route("/image")
def image(req, resp):
resp.file("photos/cat.jpg", content_type="image/jpeg")
For large files, use ``resp.stream_file()`` to avoid loading the entire
file into memory. This streams the file in chunks::
@api.route("/export")
def export(req, resp):
resp.stream_file("data/export.csv")
Custom Error Handling
---------------------
By default, unhandled exceptions result in a 500 Internal Server Error.
You can register custom handlers for specific exception types to return
structured error responses::
In production, you don't want your users to see raw Python tracebacks.
Responder lets you register custom handlers for specific exception types,
so you can return clean, structured error responses::
@api.exception_handler(ValueError)
async def handle_value_error(req, resp, exc):
resp.status_code = 400
resp.media = {"error": str(exc)}
Now, any route that raises a ``ValueError`` will return a clean 400 response
with a JSON error message instead of a generic 500 page.
Now, any route that raises a ``ValueError`` will return a clean JSON
response with a 400 status code instead of a generic 500 error page.
This is a common pattern in API development — you define your own exception
classes for different error conditions, register handlers for each, and
your API always returns consistent, machine-readable error responses.
Before-Request Hooks
--------------------
Run code before every request. This is useful for logging, adding common
headers, or setting up per-request state::
Sometimes you need to run the same code before every request —
authentication checks, request logging, adding common headers, or setting
up per-request state. Before-request hooks let you do this without
duplicating code in every route::
@api.route(before_request=True)
def add_headers(req, resp):
resp.headers["X-API-Version"] = "3.1"
resp.headers["X-API-Version"] = "3.2"
**Short-circuiting:** If your hook sets ``resp.status_code``, the route
handler will be skipped entirely and the response will be sent immediately.
This is the pattern for authentication guards::
**Short-circuiting** is the really powerful part. If your hook sets
``resp.status_code``, the route handler is skipped entirely and the
response is sent immediately. This is the pattern for authentication::
@api.route(before_request=True)
def auth_check(req, resp):
@@ -137,19 +179,36 @@ This is the pattern for authentication guards::
resp.media = {"error": "unauthorized"}
If the ``Authorization`` header is missing, the client gets a 401 response
and the actual route handler never runs.
and the actual route handler never runs. This is cleaner than adding
auth checks to every individual route.
WebSocket hooks work the same way::
@api.before_request(websocket=True)
async def ws_auth(ws):
await ws.accept()
After-Request Hooks
-------------------
The complement to before-request hooks. After-request hooks run after the
route handler completes but before the response is sent. They're useful
for logging, adding response headers, or any post-processing::
@api.after_request()
def log_response(req, resp):
print(f"{req.method} {req.full_url} -> {resp.status_code}")
@api.after_request()
async def add_timing(req, resp):
resp.headers["X-Served-By"] = "responder"
WebSocket Support
-----------------
Responder supports WebSockets for real-time, bidirectional communication::
HTTP is a request-response protocol — the client asks, the server answers.
But some applications need real-time, bidirectional communication: chat
apps, live dashboards, multiplayer games, collaborative editors.
`WebSockets <https://en.wikipedia.org/wiki/WebSocket>`_ solve this by
upgrading an HTTP connection into a persistent, full-duplex channel where
both sides can send messages at any time::
@api.route("/ws", websocket=True)
async def websocket(ws):
@@ -161,14 +220,54 @@ Responder supports WebSockets for real-time, bidirectional communication::
You can send and receive in multiple formats:
- ``send_text`` / ``receive_text`` — plain text
- ``send_json`` / ``receive_json`` — JSON objects
- ``send_text`` / ``receive_text`` — plain text strings
- ``send_json`` / ``receive_json`` — JSON objects (auto-serialized)
- ``send_bytes`` / ``receive_bytes`` — raw binary data
WebSocket routes are marked with ``websocket=True`` in the route decorator.
They receive a ``ws`` object instead of ``req`` and ``resp``.
Server-Sent Events (SSE)
-------------------------
SSE is a simpler alternative to WebSockets for *one-way* real-time
communication — the server pushes events to the client, but the client
can't send messages back. This is perfect for live feeds, progress bars,
notification streams, and AI response streaming.
Unlike WebSockets, SSE works over plain HTTP, is automatically reconnected
by the browser, and doesn't require any special client-side libraries::
@api.route("/events")
async def events(req, resp):
@resp.sse
async def stream():
for i in range(10):
yield {"data": f"message {i}"}
On the client side, you consume SSE events with JavaScript's built-in
``EventSource`` API::
const source = new EventSource("/events");
source.onmessage = (event) => {
console.log(event.data);
};
Each yielded value can be a string (treated as data) or a dict with the
standard SSE fields::
yield {"event": "update", "data": "hello", "id": "1", "retry": "5000"}
yield "simple string message"
GraphQL
-------
`GraphQL <https://graphql.org/>`_ is a query language for APIs that lets
clients request exactly the data they need — no more, no less. Instead of
multiple REST endpoints, you define a schema and let clients query it.
Responder includes built-in GraphQL support via
`Graphene <https://graphene-python.org/>`_. Set up a full GraphQL endpoint
with a single method call::
@@ -183,9 +282,10 @@ with a single method call::
api.graphql("/graphql", schema=graphene.Schema(query=Query))
Visiting ``/graphql`` in a browser renders the GraphiQL interactive IDE,
where you can explore your schema and test queries. Programmatic clients
can POST JSON queries to the same endpoint.
Visiting ``/graphql`` in a browser renders the
`GraphiQL <https://github.com/graphql/graphiql>`_ interactive IDE, where
you can explore your schema, write queries, and see results in real-time.
Programmatic clients can POST JSON queries to the same endpoint.
You can access the Responder request and response objects in your resolvers
through ``info.context["request"]`` and ``info.context["response"]``.
@@ -194,8 +294,12 @@ through ``info.context["request"]`` and ``info.context["response"]``.
OpenAPI Documentation
---------------------
Responder can generate an OpenAPI schema and serve interactive API
documentation automatically::
`OpenAPI <https://www.openapis.org/>`_ (formerly Swagger) is the industry
standard for describing REST APIs. An OpenAPI specification lets you
auto-generate interactive documentation, client libraries, and validation
logic.
Responder generates OpenAPI specs from your code::
api = responder.API(
title="Pet Store",
@@ -211,9 +315,11 @@ This gives you:
There are three ways to document your endpoints.
**Pydantic models** — the recommended approach for new APIs. Use
``request_model`` and ``response_model`` to annotate your routes, and
Responder will generate the schema automatically::
**Pydantic models** — the recommended approach. Use ``request_model`` and
``response_model`` to annotate your routes, and Responder generates the
schema automatically. When ``request_model`` is set, request bodies are
also validated automatically — invalid inputs get a ``422`` response with
detailed error messages::
from pydantic import BaseModel
@@ -232,19 +338,11 @@ Responder will generate the schema automatically::
data = await req.media()
resp.media = {"id": 1, **data}
This generates a full OpenAPI path with ``requestBody`` and ``responses``
schemas, all linked by ``$ref`` to your Pydantic models in
``components/schemas``.
When ``response_model`` is set, the response is serialized through the
model — extra fields are stripped and types are enforced.
You can also register standalone schemas with the ``@api.schema`` decorator::
@api.schema("Pet")
class Pet(BaseModel):
name: str
age: int = 0
**YAML docstrings** — inline your OpenAPI spec directly in the docstring.
This gives you full control over every detail::
**YAML docstrings** — for full control, embed OpenAPI YAML in the
docstring::
@api.route("/pets")
def list_pets(req, resp):
@@ -258,8 +356,7 @@ This gives you full control over every detail::
"""
resp.media = [{"name": "Fido"}]
**Marshmallow schemas** — if you're already using marshmallow for
validation, Responder integrates with it via the apispec plugin::
**Marshmallow schemas** — if you're already using marshmallow::
from marshmallow import Schema, fields
@@ -267,19 +364,43 @@ validation, Responder integrates with it via the apispec plugin::
class PetSchema(Schema):
name = fields.Str()
All three approaches can be mixed in the same API. Pydantic models,
marshmallow schemas, and YAML docstrings all contribute to the same
generated OpenAPI specification.
All three approaches can be mixed in the same API. You can choose from
multiple documentation themes: ``swagger_ui`` (default), ``redoc``,
``rapidoc``, or ``elements``.
You can choose from multiple documentation themes:
``swagger_ui`` (default), ``redoc``, ``rapidoc``, or ``elements``.
Route Groups
------------
As your application grows, you'll want to organize routes logically.
Route groups let you share a URL prefix across related endpoints — a
common pattern for API versioning::
v1 = api.group("/v1")
@v1.route("/users")
def list_users(req, resp):
resp.media = []
@v1.route("/users/{user_id:int}")
def get_user(req, resp, *, user_id):
resp.media = {"id": user_id}
v2 = api.group("/v2")
@v2.route("/users")
def list_users_v2(req, resp):
resp.media = {"users": [], "total": 0}
This keeps your code organized without affecting the routing logic.
Mounting Other Apps
-------------------
Responder can mount any WSGI or ASGI application at a subroute. This means
you can gradually migrate from Flask, or run multiple frameworks side by side::
Responder can mount any WSGI or ASGI application at a subroute. This is
incredibly useful for gradual migrations — you can run Flask and Responder
side by side, moving routes over one at a time::
from flask import Flask
@@ -293,12 +414,17 @@ you can gradually migrate from Flask, or run multiple frameworks side by side::
Requests to ``/flask/`` will be handled by Flask. Everything else goes
through Responder. Both WSGI and ASGI apps are supported — Responder
wraps WSGI apps automatically.
wraps WSGI apps in an ASGI adapter automatically.
Cookies
-------
`Cookies <https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies>`_ are
small pieces of data that the server asks the browser to store and send
back with every subsequent request. They're the foundation of sessions,
authentication tokens, and user preferences on the web.
Reading and writing cookies is straightforward::
# Read cookies from the request
@@ -307,26 +433,28 @@ Reading and writing cookies is straightforward::
# Set a cookie on the response
resp.cookies["hello"] = "world"
For more control over cookie directives, use ``set_cookie``::
For production use, you'll want to set security directives. The
``httponly`` flag prevents JavaScript from reading the cookie (defending
against XSS attacks), and ``secure`` ensures it's only sent over HTTPS::
resp.set_cookie(
"token",
value="abc123",
max_age=3600,
secure=True,
httponly=True,
max_age=3600, # expires in 1 hour
secure=True, # HTTPS only
httponly=True, # no JavaScript access
path="/",
)
Supported directives: ``key``, ``value``, ``expires``, ``max_age``,
``domain``, ``path``, ``secure``, ``httponly``.
Cookie-Based Sessions
---------------------
Responder has built-in support for signed, cookie-based sessions. Just
read from and write to the ``session`` dictionary::
Sessions let you store per-user data across multiple requests. Responder's
built-in sessions are cookie-based — the session data is serialized, signed
with your secret key, and stored in a cookie. The signature prevents
tampering: if someone modifies the cookie, the signature won't match and
the data will be rejected::
@api.route("/login")
def login(req, resp):
@@ -336,13 +464,9 @@ read from and write to the ``session`` dictionary::
def profile(req, resp):
resp.media = {"user": req.session.get("username")}
The session data is stored in a cookie called ``Responder-Session``. It's
signed for tamper protection, so you can trust that the data originated
from your server.
.. warning::
For production use, always set a secret key::
Always set a secret key in production. The default key is not secret::
api = responder.API(secret_key="your-secret-key-here")
@@ -350,141 +474,98 @@ from your server.
Static Files
------------
Static files are served from the ``static/`` directory by default::
Most web applications serve static assets — CSS stylesheets, JavaScript
files, images, fonts. Responder serves these from the ``static/`` directory
by default::
api = responder.API(static_dir="static", static_route="/static")
Place your CSS, JavaScript, images, and other assets in the ``static/``
directory and they'll be served automatically.
Place your assets in the ``static/`` directory and they'll be served
automatically at ``/static/style.css``, ``/static/app.js``, etc.
For single-page applications, you can serve ``index.html`` as the default
response for all unmatched routes::
For single-page applications (React, Vue, Angular), you can serve
``index.html`` as the default response for all unmatched routes::
api.add_route("/", static=True)
You can add additional static directories at runtime::
api.static_app.add_directory("extra_assets")
CORS
----
Enable Cross-Origin Resource Sharing for your API::
`CORS <https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS>`_ (Cross-
Origin Resource Sharing) is a security mechanism that controls which
websites can make requests to your API. Browsers enforce this — if your
API is at ``api.example.com`` and your frontend is at ``app.example.com``,
the browser will block requests unless your API explicitly allows it.
Enable CORS and configure which origins are allowed::
api = responder.API(cors=True, cors_params={
"allow_origins": ["https://example.com"],
"allow_origins": ["https://app.example.com"],
"allow_methods": ["GET", "POST"],
"allow_headers": ["*"],
"allow_credentials": True,
"max_age": 600,
})
The default CORS policy is restrictive — you must explicitly enable the
origins, methods, and headers your frontend needs.
The default policy is restrictive — you must explicitly allow each origin.
Using ``["*"]`` for allow_origins permits any website to call your API,
which is fine for public APIs but not for private ones.
HSTS
----
Force all traffic to HTTPS with a single flag::
`HSTS <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security>`_
(HTTP Strict Transport Security) tells browsers to always use HTTPS when
communicating with your server. Once a browser sees the HSTS header, it
will refuse to connect over plain HTTP, even if the user types ``http://``
in the address bar::
api = responder.API(enable_hsts=True)
This adds the ``Strict-Transport-Security`` header and redirects HTTP
requests to HTTPS.
Trusted Hosts
-------------
Protect against HTTP Host header attacks by restricting which hostnames
your application will respond to::
The ``Host`` header in an HTTP request tells the server which domain name
the client used. Attackers can forge this header to trick your application
into generating URLs to malicious domains (a class of attack called *Host
header injection*).
Restrict which hostnames your application accepts::
api = responder.API(allowed_hosts=["example.com", "*.example.com"])
Requests with a ``Host`` header that doesn't match any of the patterns
will receive a 400 Bad Request response. Wildcard domains are supported.
By default, all hostnames are allowed.
Server-Sent Events (SSE)
------------------------
Stream real-time updates to the client using Server-Sent Events. This is
great for live feeds, progress updates, and AI streaming responses::
@api.route("/events")
async def events(req, resp):
@resp.sse
async def stream():
for i in range(10):
yield {"data": f"message {i}"}
Each yielded value can be a string (treated as data) or a dict with
``data``, ``event``, ``id``, and ``retry`` fields::
yield {"event": "update", "data": "hello", "id": "1"}
yield "simple string message"
Streaming Files
---------------
For large files, use ``resp.stream_file()`` to stream the content without
loading the entire file into memory::
@api.route("/download")
def download(req, resp):
resp.stream_file("large-dataset.csv")
For small files where memory isn't a concern, ``resp.file()`` loads the
entire file at once — simpler but less efficient for large files.
After-Request Hooks
-------------------
Run code after every request, useful for logging, adding headers, or
cleanup::
@api.after_request()
def log_response(req, resp):
print(f"{req.method} {req.full_url} -> {resp.status_code}")
Route Groups
------------
Organize related routes with a shared URL prefix. Useful for API versioning
and logical grouping::
v1 = api.group("/v1")
@v1.route("/users")
def list_users(req, resp):
resp.media = []
@v1.route("/users/{user_id:int}")
def get_user(req, resp, *, user_id):
resp.media = {"id": user_id}
Requests with unrecognized hosts get a ``400 Bad Request``. Wildcard
patterns are supported. By default, all hostnames are allowed.
Request ID
----------
Auto-generate unique request IDs for tracing and debugging. If the client
sends an ``X-Request-ID`` header, it's forwarded; otherwise a new UUID is
generated::
In distributed systems, tracing a single request across multiple services
is essential for debugging. Request IDs are unique identifiers attached to
each request — if something goes wrong, you can search your logs for that
ID and find every related event.
Responder can auto-generate request IDs. If the client sends an
``X-Request-ID`` header (common in microservice architectures), it's
forwarded. Otherwise, a new UUID is generated::
api = responder.API(request_id=True)
The ID appears in the ``X-Request-ID`` response header.
Rate Limiting
-------------
Built-in token bucket rate limiter::
Rate limiting prevents individual clients from overwhelming your API with
too many requests. It's essential for public APIs, and good practice even
for internal ones.
Responder includes a built-in token bucket rate limiter::
from responder.ext.ratelimit import RateLimiter
@@ -492,17 +573,25 @@ Built-in token bucket rate limiter::
limiter.install(api)
When the limit is exceeded, clients receive a ``429 Too Many Requests``
response with ``Retry-After`` and ``X-RateLimit-Remaining`` headers.
response with a ``Retry-After`` header. Every response includes
``X-RateLimit-Limit`` and ``X-RateLimit-Remaining`` headers so clients
can pace themselves.
The rate limiter is per-client, keyed by IP address.
MessagePack
-----------
In addition to JSON and YAML, Responder supports MessagePack for efficient
binary serialization::
`MessagePack <https://msgpack.org/>`_ is a binary serialization format
that's more compact and faster to parse than JSON. It's useful for
high-throughput APIs, IoT devices, and anywhere bandwidth matters.
# Decode MessagePack request body
Responder supports MessagePack alongside JSON and YAML::
# Decode a MessagePack request body
data = await req.media("msgpack")
# Content negotiation also works — clients can send
# Accept: application/x-msgpack to receive MessagePack responses.
Content negotiation works too — clients can send
``Accept: application/x-msgpack`` to receive MessagePack responses
instead of JSON.
+49 -34
View File
@@ -42,29 +42,27 @@
<section id="deployment">
<h1>Deployment<a class="headerlink" href="#deployment" title="Link to this heading"></a></h1>
<p>Responder applications are standard ASGI apps. You can deploy them anywhere
youd deploy a Python web service.</p>
<p>Responder applications are standard <a class="reference external" href="https://asgi.readthedocs.io/">ASGI</a>
apps. ASGI (Asynchronous Server Gateway Interface) is the modern successor
to WSGI — it supports async, WebSockets, and HTTP/2. This means you can
deploy a Responder app anywhere that runs Python, using any ASGI server.</p>
<section id="running-locally">
<h2>Running Locally<a class="headerlink" href="#running-locally" title="Link to this heading"></a></h2>
<p>The simplest way to run your application:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># api.py</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">responder</span>
<span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">()</span>
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">hello</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="s2">&quot;hello, world!&quot;</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&quot;__main__&quot;</span><span class="p">:</span>
<p>During development, <code class="docutils literal notranslate"><span class="pre">api.run()</span></code> is all you need:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&quot;__main__&quot;</span><span class="p">:</span>
<span class="n">api</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
</pre></div>
</div>
<p>This starts a production uvicorn server on <code class="docutils literal notranslate"><span class="pre">127.0.0.1:5042</span></code>.</p>
<p>This starts a <a class="reference external" href="https://www.uvicorn.org/">uvicorn</a> server on
<code class="docutils literal notranslate"><span class="pre">127.0.0.1:5042</span></code>. Uvicorn is a lightning-fast ASGI server built on
<a class="reference external" href="https://uvloop.readthedocs.io/">uvloop</a> — it handles thousands of
concurrent connections efficiently and protects against slowloris attacks,
making a reverse proxy like nginx optional for many deployments.</p>
</section>
<section id="docker">
<h2>Docker<a class="headerlink" href="#docker" title="Link to this heading"></a></h2>
<p>A minimal Dockerfile for deploying a Responder application:</p>
<p>Docker is the most common way to package and deploy web applications.
Heres a minimal Dockerfile:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">FROM</span> <span class="n">python</span><span class="p">:</span><span class="mf">3.13</span><span class="o">-</span><span class="n">slim</span>
<span class="n">WORKDIR</span> <span class="o">/</span><span class="n">app</span>
<span class="n">COPY</span> <span class="o">.</span> <span class="o">.</span>
@@ -79,40 +77,57 @@ youd deploy a Python web service.</p>
$ docker run -p 8000:80 myapi
</pre></div>
</div>
<p>The <code class="docutils literal notranslate"><span class="pre">python:3.13-slim</span></code> image is about 150MB — small enough for fast
deploys but includes everything you need. For even smaller images, you
can use <code class="docutils literal notranslate"><span class="pre">python:3.13-alpine</span></code>, though some packages may need extra
build dependencies.</p>
</section>
<section id="cloud-platforms">
<h2>Cloud Platforms<a class="headerlink" href="#cloud-platforms" title="Link to this heading"></a></h2>
<p>Responder automatically honors the <code class="docutils literal notranslate"><span class="pre">PORT</span></code> environment variable, which is
set by most cloud platforms. When <code class="docutils literal notranslate"><span class="pre">PORT</span></code> is set, Responder binds to
<code class="docutils literal notranslate"><span class="pre">0.0.0.0</span></code> on that port automatically.</p>
<p>This works out of the box with:</p>
<p>Responder automatically honors the <code class="docutils literal notranslate"><span class="pre">PORT</span></code> environment variable. When
<code class="docutils literal notranslate"><span class="pre">PORT</span></code> is set, the server binds to <code class="docutils literal notranslate"><span class="pre">0.0.0.0</span></code> on that port — this is
the convention that virtually every cloud platform uses.</p>
<p>This means zero configuration on:</p>
<ul class="simple">
<li><p><strong>Fly.io</strong></p></li>
<li><p><strong>Railway</strong></p></li>
<li><p><strong>Render</strong></p></li>
<li><p><strong>Google Cloud Run</strong></p></li>
<li><p><strong>Azure Container Apps</strong></p></li>
<li><p><strong>AWS App Runner</strong></p></li>
<li><p><strong>Fly.io</strong><code class="docutils literal notranslate"><span class="pre">fly</span> <span class="pre">launch</span></code> and youre done</p></li>
<li><p><strong>Railway</strong> — push your code, Railway sets <code class="docutils literal notranslate"><span class="pre">PORT</span></code></p></li>
<li><p><strong>Render</strong> — set start command to <code class="docutils literal notranslate"><span class="pre">python</span> <span class="pre">api.py</span></code></p></li>
<li><p><strong>Google Cloud Run</strong> — containerize and deploy</p></li>
<li><p><strong>Azure Container Apps</strong> — same pattern</p></li>
<li><p><strong>AWS App Runner</strong> — and here too</p></li>
</ul>
<p>Just deploy your code and set the start command to <code class="docutils literal notranslate"><span class="pre">python</span> <span class="pre">api.py</span></code>.</p>
<p>The pattern is always the same: deploy your code, set the start command
to <code class="docutils literal notranslate"><span class="pre">python</span> <span class="pre">api.py</span></code>, and the platform handles the rest.</p>
</section>
<section id="uvicorn-directly">
<h2>Uvicorn Directly<a class="headerlink" href="#uvicorn-directly" title="Link to this heading"></a></h2>
<p>For more control over the production server, you can bypass <code class="docutils literal notranslate"><span class="pre">api.run()</span></code>
and use uvicorn directly:</p>
<p>For production deployments where you want more control, bypass
<code class="docutils literal notranslate"><span class="pre">api.run()</span></code> and use uvicorn directly:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ uvicorn api:api --host 0.0.0.0 --port 8000 --workers 4
</pre></div>
</div>
<p>This gives you access to all of uvicorns options: worker count, SSL
certificates, access logging, and more. See the
<a class="reference external" href="https://www.uvicorn.org/">uvicorn documentation</a> for details.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">--workers</span></code> flag spawns multiple processes, each handling requests
independently. A good starting point is 2-4 workers per CPU core.</p>
<p>Uvicorn supports many options — SSL certificates, access logging, graceful
shutdown timeouts, and more. See the
<a class="reference external" href="https://www.uvicorn.org/deployment/">uvicorn documentation</a> for details.</p>
</section>
<section id="reverse-proxy">
<h2>Reverse Proxy<a class="headerlink" href="#reverse-proxy" title="Link to this heading"></a></h2>
<p>In production, you may want to place Responder behind a reverse proxy like
nginx or Caddy for SSL termination, load balancing, or serving static assets.</p>
<p>For high-traffic production deployments, you may want a reverse proxy like
<a class="reference external" href="https://nginx.org/">nginx</a> or <a class="reference external" href="https://caddyserver.com/">Caddy</a> in
front of your application for:</p>
<ul class="simple">
<li><p><strong>SSL/TLS termination</strong> — let the proxy handle HTTPS certificates</p></li>
<li><p><strong>Load balancing</strong> — distribute traffic across multiple app instances</p></li>
<li><p><strong>Static asset serving</strong> — offload static files to the proxy</p></li>
<li><p><strong>Rate limiting</strong> — at the infrastructure level</p></li>
</ul>
<p>Responders <code class="docutils literal notranslate"><span class="pre">TrustedHostMiddleware</span></code> and <code class="docutils literal notranslate"><span class="pre">HTTPSRedirectMiddleware</span></code> work
correctly behind proxies that set standard forwarding headers.</p>
correctly behind proxies that set standard forwarding headers
(<code class="docutils literal notranslate"><span class="pre">X-Forwarded-For</span></code>, <code class="docutils literal notranslate"><span class="pre">X-Forwarded-Proto</span></code>).</p>
<p>That said, uvicorn is production-ready on its own. Many applications run
uvicorn directly without a reverse proxy and do just fine.</p>
</section>
</section>
+3 -4
View File
@@ -138,9 +138,12 @@ with — youre in the right place.</p>
<li class="toctree-l2"><a class="reference internal" href="tour.html#serving-files">Serving Files</a></li>
<li class="toctree-l2"><a class="reference internal" href="tour.html#custom-error-handling">Custom Error Handling</a></li>
<li class="toctree-l2"><a class="reference internal" href="tour.html#before-request-hooks">Before-Request Hooks</a></li>
<li class="toctree-l2"><a class="reference internal" href="tour.html#after-request-hooks">After-Request Hooks</a></li>
<li class="toctree-l2"><a class="reference internal" href="tour.html#websocket-support">WebSocket Support</a></li>
<li class="toctree-l2"><a class="reference internal" href="tour.html#server-sent-events-sse">Server-Sent Events (SSE)</a></li>
<li class="toctree-l2"><a class="reference internal" href="tour.html#graphql">GraphQL</a></li>
<li class="toctree-l2"><a class="reference internal" href="tour.html#openapi-documentation">OpenAPI Documentation</a></li>
<li class="toctree-l2"><a class="reference internal" href="tour.html#route-groups">Route Groups</a></li>
<li class="toctree-l2"><a class="reference internal" href="tour.html#mounting-other-apps">Mounting Other Apps</a></li>
<li class="toctree-l2"><a class="reference internal" href="tour.html#cookies">Cookies</a></li>
<li class="toctree-l2"><a class="reference internal" href="tour.html#cookie-based-sessions">Cookie-Based Sessions</a></li>
@@ -148,10 +151,6 @@ with — youre in the right place.</p>
<li class="toctree-l2"><a class="reference internal" href="tour.html#cors">CORS</a></li>
<li class="toctree-l2"><a class="reference internal" href="tour.html#hsts">HSTS</a></li>
<li class="toctree-l2"><a class="reference internal" href="tour.html#trusted-hosts">Trusted Hosts</a></li>
<li class="toctree-l2"><a class="reference internal" href="tour.html#server-sent-events-sse">Server-Sent Events (SSE)</a></li>
<li class="toctree-l2"><a class="reference internal" href="tour.html#streaming-files">Streaming Files</a></li>
<li class="toctree-l2"><a class="reference internal" href="tour.html#after-request-hooks">After-Request Hooks</a></li>
<li class="toctree-l2"><a class="reference internal" href="tour.html#route-groups">Route Groups</a></li>
<li class="toctree-l2"><a class="reference internal" href="tour.html#request-id">Request ID</a></li>
<li class="toctree-l2"><a class="reference internal" href="tour.html#rate-limiting">Rate Limiting</a></li>
<li class="toctree-l2"><a class="reference internal" href="tour.html#messagepack">MessagePack</a></li>
+148 -60
View File
@@ -43,146 +43,204 @@
<section id="quick-start">
<h1>Quick Start<a class="headerlink" href="#quick-start" title="Link to this heading"></a></h1>
<p>This guide will walk you through the basics of building a web service with
Responder. By the end, youll know how to declare routes, handle requests,
send responses, render templates, and process background tasks.</p>
Responder. By the end, youll understand how HTTP requests and responses
work, how to define routes, read data from clients, send data back, render
HTML templates, and process work in the background.</p>
<section id="create-a-web-service">
<h2>Create a Web Service<a class="headerlink" href="#create-a-web-service" title="Link to this heading"></a></h2>
<p>The first thing you need to do is declare a web service. This is the central
object that holds all your routes, middleware, and configuration:</p>
<p>Every web application starts with a single object — the application
instance. In Responder, this is the <code class="docutils literal notranslate"><span class="pre">API</span></code> class. It holds your routes,
middleware, templates, and configuration. Think of it as the central
nervous system of your web service:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">responder</span>
<span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">()</span>
</pre></div>
</div>
<p>Thats it. One import, one line. You now have a fully functional ASGI
application with gzip compression, static file serving, session support,
and a production-ready server — all wired up and ready to go.</p>
</section>
<section id="hello-world">
<h2>Hello World<a class="headerlink" href="#hello-world" title="Link to this heading"></a></h2>
<p>Next, add a route. Here, well make the root URL say “hello, world!”:</p>
<p>A web service isnt very useful until it can respond to requests. In HTTP,
a <em>route</em> maps a URL path to a function that handles it. When a client
(like a browser or <code class="docutils literal notranslate"><span class="pre">curl</span></code>) sends a request to that path, your function
runs and produces a response.</p>
<p>Heres the simplest possible route:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">hello_world</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="s2">&quot;hello, world!&quot;</span>
</pre></div>
</div>
<p>Every view receives a <code class="docutils literal notranslate"><span class="pre">req</span></code> (request) and <code class="docutils literal notranslate"><span class="pre">resp</span></code> (response) object. You
dont need to return anything — just mutate the response directly.</p>
<p>Two things to notice:</p>
<ol class="arabic simple">
<li><p>Every view function receives two arguments: <code class="docutils literal notranslate"><span class="pre">req</span></code> (the incoming
request) and <code class="docutils literal notranslate"><span class="pre">resp</span></code> (the outgoing response).</p></li>
<li><p>You dont return anything. Instead, you <em>mutate</em> the response object
directly. This is a deliberate design choice — it keeps the API
consistent whether youre setting text, JSON, headers, cookies, or
status codes.</p></li>
</ol>
</section>
<section id="run-the-server">
<h2>Run the Server<a class="headerlink" href="#run-the-server" title="Link to this heading"></a></h2>
<p>Start your web service with <code class="docutils literal notranslate"><span class="pre">api.run()</span></code>:</p>
<p>Start your web service with a single call:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
</pre></div>
</div>
<p>This spins up a production-grade uvicorn server on port <code class="docutils literal notranslate"><span class="pre">5042</span></code>, ready for
incoming HTTP requests.</p>
<p>This spins up a production-grade <a class="reference external" href="https://www.uvicorn.org/">uvicorn</a>
server on port <code class="docutils literal notranslate"><span class="pre">5042</span></code>, ready for incoming HTTP requests. Open
<code class="docutils literal notranslate"><span class="pre">http://localhost:5042</span></code> in your browser and youll see your hello world
response.</p>
<p>You can customize the port with <code class="docutils literal notranslate"><span class="pre">api.run(port=8000)</span></code>. The <code class="docutils literal notranslate"><span class="pre">PORT</span></code>
environment variable is also honored automatically — when set, Responder
binds to <code class="docutils literal notranslate"><span class="pre">0.0.0.0</span></code> on that port, which is what cloud platforms like
Fly.io, Railway, and Google Cloud Run expect.</p>
binds to <code class="docutils literal notranslate"><span class="pre">0.0.0.0</span></code> on that port, which is what cloud platforms expect.</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>Both sync and async views are supported. The <code class="docutils literal notranslate"><span class="pre">async</span></code> keyword is always
optional — use it when you need to <code class="docutils literal notranslate"><span class="pre">await</span></code> something.</p>
optional — use it when you need to <code class="docutils literal notranslate"><span class="pre">await</span></code> something, like reading a
request body or querying a database.</p>
</div>
</section>
<section id="route-parameters">
<h2>Route Parameters<a class="headerlink" href="#route-parameters" title="Link to this heading"></a></h2>
<p>If you want dynamic URLs, use Pythons familiar f-string syntax to declare
variables in your routes:</p>
<p>Static URLs like <code class="docutils literal notranslate"><span class="pre">/about</span></code> are useful, but most applications need dynamic
routes — URLs that contain variable data, like a user ID or a product slug.</p>
<p>In Responder, you declare route parameters using Pythons f-string syntax:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/hello/</span><span class="si">{who}</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">hello_to</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">who</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;hello, </span><span class="si">{</span><span class="n">who</span><span class="si">}</span><span class="s2">!&quot;</span>
</pre></div>
</div>
<p>A <code class="docutils literal notranslate"><span class="pre">GET</span></code> request to <code class="docutils literal notranslate"><span class="pre">/hello/world</span></code> will respond with <code class="docutils literal notranslate"><span class="pre">hello,</span> <span class="pre">world!</span></code>.</p>
<p>Route parameters are passed as keyword-only arguments (after the <code class="docutils literal notranslate"><span class="pre">*</span></code>).</p>
<p>A <code class="docutils literal notranslate"><span class="pre">GET</span></code> request to <code class="docutils literal notranslate"><span class="pre">/hello/world</span></code> will respond with <code class="docutils literal notranslate"><span class="pre">hello,</span> <span class="pre">world!</span></code>.
A request to <code class="docutils literal notranslate"><span class="pre">/hello/guido</span></code> will respond with <code class="docutils literal notranslate"><span class="pre">hello,</span> <span class="pre">guido!</span></code>.</p>
<p>Route parameters are passed as <em>keyword-only</em> arguments (after the <code class="docutils literal notranslate"><span class="pre">*</span></code>
in the function signature). This is a Python feature that makes the
interface explicit — you always know which arguments come from the URL.</p>
<section id="type-convertors">
<h3>Type Convertors<a class="headerlink" href="#type-convertors" title="Link to this heading"></a></h3>
<p>You can constrain route parameters to specific types. The parameter will be
automatically converted before it reaches your view:</p>
<p>By default, route parameters are strings. But often you want them as
integers, UUIDs, or other types. Responder can convert them automatically
using type annotations in the route pattern:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/add/{a:int}/{b:int}&quot;</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">add</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="n">a</span><span class="si">}</span><span class="s2"> + </span><span class="si">{</span><span class="n">b</span><span class="si">}</span><span class="s2"> = </span><span class="si">{</span><span class="n">a</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">b</span><span class="si">}</span><span class="s2">&quot;</span>
</pre></div>
</div>
<p>Here, <code class="docutils literal notranslate"><span class="pre">a</span></code> and <code class="docutils literal notranslate"><span class="pre">b</span></code> will arrive as Python <code class="docutils literal notranslate"><span class="pre">int</span></code> objects, not strings.
If someone requests <code class="docutils literal notranslate"><span class="pre">/add/3/hello</span></code>, theyll get a 404 — the route wont
match because <code class="docutils literal notranslate"><span class="pre">hello</span></code> isnt a valid integer.</p>
<p>Supported types:</p>
<ul class="simple">
<li><p><code class="docutils literal notranslate"><span class="pre">str</span></code> — matches any string without slashes (default)</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">int</span></code> — matches digits, converts to <code class="docutils literal notranslate"><span class="pre">int</span></code></p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">float</span></code> — matches decimal numbers, converts to <code class="docutils literal notranslate"><span class="pre">float</span></code></p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">str</span></code> — matches any string without slashes (this is the default)</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">int</span></code> — matches digits and converts to <code class="docutils literal notranslate"><span class="pre">int</span></code></p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">float</span></code> — matches decimal numbers and converts to <code class="docutils literal notranslate"><span class="pre">float</span></code></p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">uuid</span></code> — matches UUID strings like <code class="docutils literal notranslate"><span class="pre">550e8400-e29b-41d4-a716-446655440000</span></code></p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">path</span></code> — matches any string <em>including</em> slashes, useful for file paths</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">path</span></code> — matches any string <em>including</em> slashes, useful for file paths
like <code class="docutils literal notranslate"><span class="pre">/files/{filepath:path}</span></code></p></li>
</ul>
</section>
</section>
<section id="sending-responses">
<h2>Sending Responses<a class="headerlink" href="#sending-responses" title="Link to this heading"></a></h2>
<p>Responder gives you several ways to send data back to the client. Just set
the appropriate property on the response object.</p>
<p><strong>Text and HTML</strong>:</p>
<p>When an HTTP server receives a request, it must send back a response. Every
HTTP response has three parts: a status code (like <code class="docutils literal notranslate"><span class="pre">200</span> <span class="pre">OK</span></code> or <code class="docutils literal notranslate"><span class="pre">404</span> <span class="pre">Not</span>
<span class="pre">Found</span></code>), headers (metadata like <code class="docutils literal notranslate"><span class="pre">Content-Type</span></code>), and a body (the actual
data).</p>
<p>Responder lets you set all three by mutating the response object.</p>
<p><strong>Text and HTML</strong> — the simplest response types. <code class="docutils literal notranslate"><span class="pre">resp.text</span></code> sets the
<code class="docutils literal notranslate"><span class="pre">Content-Type</span></code> to <code class="docutils literal notranslate"><span class="pre">text/plain</span></code>, while <code class="docutils literal notranslate"><span class="pre">resp.html</span></code> sets it to
<code class="docutils literal notranslate"><span class="pre">text/html</span></code>:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">resp</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="s2">&quot;plain text response&quot;</span>
<span class="n">resp</span><span class="o">.</span><span class="n">html</span> <span class="o">=</span> <span class="s2">&quot;&lt;h1&gt;HTML response&lt;/h1&gt;&quot;</span>
</pre></div>
</div>
<p><strong>JSON</strong> — the most common pattern for APIs. Set <code class="docutils literal notranslate"><span class="pre">resp.media</span></code> to any
JSON-serializable Python object:</p>
<p><strong>JSON</strong> — the lingua franca of web APIs. Set <code class="docutils literal notranslate"><span class="pre">resp.media</span></code> to any
JSON-serializable Python object — a dict, a list, whatever — and Responder
will serialize it to JSON and set the right headers:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/hello/</span><span class="si">{who}</span><span class="s2">/json&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">hello_json</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">who</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;hello&quot;</span><span class="p">:</span> <span class="n">who</span><span class="p">}</span>
</pre></div>
</div>
<p>If the client sends an <code class="docutils literal notranslate"><span class="pre">Accept:</span> <span class="pre">application/x-yaml</span></code> header, the same data
will be returned as YAML instead. Content negotiation is automatic.</p>
<p><strong>Files</strong> serve a file from disk with automatic content-type detection:</p>
will be returned as YAML instead. This is called <em>content negotiation</em>
the server and client agree on a format. It happens automatically.</p>
<p><strong>Files</strong> — serve a file from disk. Responder uses Pythons <code class="docutils literal notranslate"><span class="pre">mimetypes</span></code>
module to figure out the <code class="docutils literal notranslate"><span class="pre">Content-Type</span></code> from the file extension:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">resp</span><span class="o">.</span><span class="n">file</span><span class="p">(</span><span class="s2">&quot;reports/annual.pdf&quot;</span><span class="p">)</span>
</pre></div>
</div>
<p><strong>Raw bytes</strong>:</p>
<p><strong>Raw bytes</strong> — for binary data like images or protocol buffers:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">resp</span><span class="o">.</span><span class="n">content</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">&quot;</span><span class="se">\x89</span><span class="s2">PNG</span><span class="se">\r\n</span><span class="s2">...&quot;</span>
</pre></div>
</div>
<p><strong>Status codes and headers</strong>:</p>
<p><strong>Status codes</strong> — HTTP status codes tell the client what happened. <code class="docutils literal notranslate"><span class="pre">200</span></code>
means success, <code class="docutils literal notranslate"><span class="pre">201</span></code> means something was created, <code class="docutils literal notranslate"><span class="pre">404</span></code> means not found,
<code class="docutils literal notranslate"><span class="pre">500</span></code> means the server broke. Set it directly:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="mi">201</span>
<span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">&quot;X-Custom&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;value&quot;</span>
</pre></div>
</div>
<p><strong>Redirects</strong>:</p>
<p><strong>Headers</strong> — HTTP headers carry metadata. Common ones include
<code class="docutils literal notranslate"><span class="pre">Content-Type</span></code>, <code class="docutils literal notranslate"><span class="pre">Cache-Control</span></code>, <code class="docutils literal notranslate"><span class="pre">Authorization</span></code>, and custom
application headers:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">&quot;X-Custom&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;value&quot;</span>
</pre></div>
</div>
<p><strong>Redirects</strong> — tell the client to go somewhere else:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span><span class="o">.</span><span class="n">redirect</span><span class="p">(</span><span class="n">resp</span><span class="p">,</span> <span class="n">location</span><span class="o">=</span><span class="s2">&quot;/new-url&quot;</span><span class="p">)</span>
</pre></div>
</div>
<p>This sends a <code class="docutils literal notranslate"><span class="pre">301</span> <span class="pre">Moved</span> <span class="pre">Permanently</span></code> response by default. The clients
browser will automatically follow the redirect.</p>
</section>
<section id="reading-requests">
<h2>Reading Requests<a class="headerlink" href="#reading-requests" title="Link to this heading"></a></h2>
<p>The request object gives you access to everything the client sent.</p>
<p><strong>Method and URL</strong>:</p>
<p>The other half of HTTP is the request — the data the client sends to your
server. This includes the HTTP method (GET, POST, PUT, DELETE), the URL,
headers, query parameters, cookies, and optionally a body.</p>
<p>Responder wraps all of this in the <code class="docutils literal notranslate"><span class="pre">req</span></code> object.</p>
<p><strong>Method and URL</strong> — every HTTP request has a method (what the client wants
to do) and a URL (what resource its about):</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">req</span><span class="o">.</span><span class="n">method</span> <span class="c1"># &quot;get&quot;, &quot;post&quot;, etc. (lowercase)</span>
<span class="n">req</span><span class="o">.</span><span class="n">full_url</span> <span class="c1"># &quot;http://example.com/path?q=1&quot;</span>
<span class="n">req</span><span class="o">.</span><span class="n">url</span> <span class="c1"># parsed URL object</span>
</pre></div>
</div>
<p><strong>Headers</strong>case-insensitive, just like youd expect:</p>
<p><strong>Headers</strong>HTTP headers carry metadata from the client, like what
content types it accepts, authentication tokens, and more. Responders
headers dict is case-insensitive, because the HTTP spec says header names
are case-insensitive:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">req</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">&quot;Content-Type&quot;</span><span class="p">]</span>
<span class="n">req</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">&quot;content-type&quot;</span><span class="p">]</span> <span class="c1"># same thing</span>
</pre></div>
</div>
<p><strong>Query parameters</strong>:</p>
<p><strong>Query parameters</strong> — the part of the URL after the <code class="docutils literal notranslate"><span class="pre">?</span></code>. These are
commonly used for search, filtering, and pagination:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># GET /search?q=python&amp;page=2</span>
<span class="n">req</span><span class="o">.</span><span class="n">params</span><span class="p">[</span><span class="s2">&quot;q&quot;</span><span class="p">]</span> <span class="c1"># &quot;python&quot;</span>
<span class="n">req</span><span class="o">.</span><span class="n">params</span><span class="p">[</span><span class="s2">&quot;page&quot;</span><span class="p">]</span> <span class="c1"># &quot;2&quot;</span>
</pre></div>
</div>
<p><strong>Path parameters</strong> — also available on the request object:</p>
<p>Note that query parameters are always strings. If you need an integer,
youll need to convert it yourself: <code class="docutils literal notranslate"><span class="pre">int(req.params[&quot;page&quot;])</span></code>.</p>
<p><strong>Path parameters</strong> — the dynamic parts of the URL that matched your route
pattern. These are also available on the request object, which is useful
in before-request hooks where they arent passed as function arguments:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">req</span><span class="o">.</span><span class="n">path_params</span><span class="p">[</span><span class="s2">&quot;user_id&quot;</span><span class="p">]</span> <span class="c1"># same as the keyword argument</span>
</pre></div>
</div>
<p><strong>Request body</strong> — for POST/PUT/PATCH requests, you need to <code class="docutils literal notranslate"><span class="pre">await</span></code> the
body content:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># JSON body</span>
<p><strong>Request body</strong> — for POST, PUT, and PATCH requests, the client sends
data in the body. Since reading the body is an I/O operation, you need to
<code class="docutils literal notranslate"><span class="pre">await</span></code> it:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># JSON body (the most common format for APIs)</span>
<span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">req</span><span class="o">.</span><span class="n">media</span><span class="p">()</span>
<span class="c1"># Form data</span>
<span class="c1"># Form data (from HTML forms)</span>
<span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">req</span><span class="o">.</span><span class="n">media</span><span class="p">(</span><span class="s2">&quot;form&quot;</span><span class="p">)</span>
<span class="c1"># File uploads</span>
<span class="c1"># File uploads (multipart)</span>
<span class="n">files</span> <span class="o">=</span> <span class="k">await</span> <span class="n">req</span><span class="o">.</span><span class="n">media</span><span class="p">(</span><span class="s2">&quot;files&quot;</span><span class="p">)</span>
<span class="c1"># Raw bytes</span>
@@ -193,40 +251,56 @@ body content:</p>
</pre></div>
</div>
<p><strong>Other useful properties</strong>:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">req</span><span class="o">.</span><span class="n">is_json</span> <span class="c1"># True if content type is JSON</span>
<span class="n">req</span><span class="o">.</span><span class="n">cookies</span> <span class="c1"># dict of cookies</span>
<span class="n">req</span><span class="o">.</span><span class="n">session</span> <span class="c1"># session data (dict)</span>
<span class="n">req</span><span class="o">.</span><span class="n">client</span> <span class="c1"># (host, port) tuple</span>
<span class="n">req</span><span class="o">.</span><span class="n">is_secure</span> <span class="c1"># True if HTTPS</span>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">req</span><span class="o">.</span><span class="n">is_json</span> <span class="c1"># True if the content type is JSON</span>
<span class="n">req</span><span class="o">.</span><span class="n">cookies</span> <span class="c1"># dict of cookies sent by the client</span>
<span class="n">req</span><span class="o">.</span><span class="n">session</span> <span class="c1"># session data (a signed, server-side dict)</span>
<span class="n">req</span><span class="o">.</span><span class="n">client</span> <span class="c1"># (host, port) tuple — the client&#39;s IP address</span>
<span class="n">req</span><span class="o">.</span><span class="n">is_secure</span> <span class="c1"># True if the request came over HTTPS</span>
</pre></div>
</div>
</section>
<section id="rendering-templates">
<h2>Rendering Templates<a class="headerlink" href="#rendering-templates" title="Link to this heading"></a></h2>
<p>Responder includes built-in <a class="reference external" href="https://jinja.palletsprojects.com/">Jinja2</a>
support. Templates are loaded from the <code class="docutils literal notranslate"><span class="pre">templates/</span></code> directory by default.</p>
<p>The simplest way is to use <code class="docutils literal notranslate"><span class="pre">api.template()</span></code>:</p>
<p>While APIs typically return JSON, many web applications need to render
HTML pages. Responder includes built-in support for
<a class="reference external" href="https://jinja.palletsprojects.com/">Jinja2</a>, one of the most popular
templating engines in the Python ecosystem.</p>
<p>Templates let you write HTML with placeholders that get filled in with
dynamic data. This keeps your presentation logic (HTML) separate from
your application logic (Python) — a pattern called
<em>separation of concerns</em>.</p>
<p>The simplest way to render a template is <code class="docutils literal notranslate"><span class="pre">api.template()</span></code>. Templates
are loaded from the <code class="docutils literal notranslate"><span class="pre">templates/</span></code> directory by default:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/hello/</span><span class="si">{name}</span><span class="s2">/html&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">hello_html</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">html</span> <span class="o">=</span> <span class="n">api</span><span class="o">.</span><span class="n">template</span><span class="p">(</span><span class="s2">&quot;hello.html&quot;</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="n">name</span><span class="p">)</span>
</pre></div>
</div>
<p>You can also use the <code class="docutils literal notranslate"><span class="pre">Templates</span></code> class directly for more control:</p>
<p>The template file <code class="docutils literal notranslate"><span class="pre">templates/hello.html</span></code> might look like:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>&lt;h1&gt;Hello, {{ name }}!&lt;/h1&gt;
</pre></div>
</div>
<p>The <code class="docutils literal notranslate"><span class="pre">{{</span> <span class="pre">name</span> <span class="pre">}}</span></code> part is a Jinja2 expression — it gets replaced with
the value you passed in.</p>
<p>You can also use the <code class="docutils literal notranslate"><span class="pre">Templates</span></code> class directly for more control over
the template directory and configuration:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder.templates</span><span class="w"> </span><span class="kn">import</span> <span class="n">Templates</span>
<span class="n">templates</span> <span class="o">=</span> <span class="n">Templates</span><span class="p">(</span><span class="n">directory</span><span class="o">=</span><span class="s2">&quot;templates&quot;</span><span class="p">)</span>
<span class="n">templates</span> <span class="o">=</span> <span class="n">Templates</span><span class="p">(</span><span class="n">directory</span><span class="o">=</span><span class="s2">&quot;my_templates&quot;</span><span class="p">)</span>
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/page&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">page</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">html</span> <span class="o">=</span> <span class="n">templates</span><span class="o">.</span><span class="n">render</span><span class="p">(</span><span class="s2">&quot;page.html&quot;</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s2">&quot;Hello&quot;</span><span class="p">)</span>
</pre></div>
</div>
<p>Async rendering is supported too:</p>
<p>For applications that need non-blocking template rendering (rare, but
useful under extreme load), async rendering is supported:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">templates</span> <span class="o">=</span> <span class="n">Templates</span><span class="p">(</span><span class="n">directory</span><span class="o">=</span><span class="s2">&quot;templates&quot;</span><span class="p">,</span> <span class="n">enable_async</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">resp</span><span class="o">.</span><span class="n">html</span> <span class="o">=</span> <span class="k">await</span> <span class="n">templates</span><span class="o">.</span><span class="n">render_async</span><span class="p">(</span><span class="s2">&quot;page.html&quot;</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s2">&quot;Hello&quot;</span><span class="p">)</span>
</pre></div>
</div>
<p>You can render template strings without a file:</p>
<p>And for quick one-off templates, you can render a string directly without
a file:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">resp</span><span class="o">.</span><span class="n">html</span> <span class="o">=</span> <span class="n">api</span><span class="o">.</span><span class="n">template_string</span><span class="p">(</span><span class="s2">&quot;Hello, {{ name }}!&quot;</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s2">&quot;world&quot;</span><span class="p">)</span>
</pre></div>
</div>
@@ -234,7 +308,12 @@ support. Templates are loaded from the <code class="docutils literal notranslate
<section id="background-tasks">
<h2>Background Tasks<a class="headerlink" href="#background-tasks" title="Link to this heading"></a></h2>
<p>Sometimes you want to accept a request, respond immediately, and do the
actual processing later. Responder makes this easy with background tasks:</p>
actual processing later. This is a common pattern for operations that take
a long time — sending emails, processing images, updating caches, or
calling slow external APIs.</p>
<p>Responder makes this easy with background tasks. Decorate any function
with <code class="docutils literal notranslate"><span class="pre">&#64;api.background.task</span></code> and it will run in a thread pool, separate
from the request/response cycle:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/incoming&quot;</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">receive_incoming</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">req</span><span class="o">.</span><span class="n">media</span><span class="p">()</span>
@@ -247,12 +326,21 @@ actual processing later. Responder makes this easy with background tasks:</p>
<span class="n">process_data</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="c1"># Respond immediately processing continues in the background</span>
<span class="c1"># This response is sent immediately, while process_data</span>
<span class="c1"># continues running in the background.</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;status&quot;</span><span class="p">:</span> <span class="s2">&quot;accepted&quot;</span><span class="p">}</span>
</pre></div>
</div>
<p>The <code class="docutils literal notranslate"><span class="pre">&#64;api.background.task</span></code> decorator wraps any function to run in a thread
pool. The client gets an immediate response while the work continues.</p>
<p>The client gets an instant response — the heavy lifting happens after.
This is the same pattern used by task queues like Celery, but much simpler
for lightweight use cases where you dont need a full message broker.</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>Background tasks run in threads, not processes. They share memory with
your application, which makes them fast to start but means CPU-intensive
work will block the event loop. For heavy computation, consider a proper
task queue.</p>
</div>
</section>
</section>
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -303,7 +303,7 @@ network overhead. Avoid <code class="docutils literal notranslate"><span class="
<li><p><strong>One API per test</strong> when testing configuration. If you need a specific
<code class="docutils literal notranslate"><span class="pre">API()</span></code> configuration (like <code class="docutils literal notranslate"><span class="pre">cors=True</span></code>), create a new instance
in the test rather than sharing the fixture.</p></li>
<li><p><strong>Use ``api.url_for()``</strong> instead of hard-coded paths. Its a small
<li><p>Use <code class="docutils literal notranslate"><span class="pre">api.url_for()</span></code> instead of hard-coded paths. Its a small
thing, but it makes refactoring painless.</p></li>
<li><p><strong>Test the contract, not the implementation.</strong> Assert on status codes,
response bodies, and headers — not on internal state.</p></li>
+249 -173
View File
@@ -42,12 +42,24 @@
<section id="feature-tour">
<h1>Feature Tour<a class="headerlink" href="#feature-tour" title="Link to this heading"></a></h1>
<p>This section walks through Responders features in detail. Each section
includes working code examples you can copy into your application.</p>
<p>This section walks through Responders features in depth. Each section
explains the concept, shows working code, and explains the design choices
behind it. If youre new to web development, this is a good place to learn
how modern web frameworks work under the hood.</p>
<section id="method-filtering">
<h2>Method Filtering<a class="headerlink" href="#method-filtering" title="Link to this heading"></a></h2>
<p>By default, a route matches all HTTP methods. If you want to restrict a
route to specific methods, pass the <code class="docutils literal notranslate"><span class="pre">methods</span></code> parameter:</p>
<p>HTTP defines several <em>methods</em> (also called verbs) that describe what a
client wants to do with a resource. The most common are:</p>
<ul class="simple">
<li><p><code class="docutils literal notranslate"><span class="pre">GET</span></code> — retrieve data</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">POST</span></code> — create something new</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">PUT</span></code> — replace something entirely</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">PATCH</span></code> — update part of something</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">DELETE</span></code> — remove something</p></li>
</ul>
<p>By default, a Responder route matches all methods. This is fine for simple
endpoints, but REST APIs typically map different methods to different
operations. Use the <code class="docutils literal notranslate"><span class="pre">methods</span></code> parameter to restrict a route:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/items&quot;</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;GET&quot;</span><span class="p">])</span>
<span class="k">def</span><span class="w"> </span><span class="nf">list_items</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;items&quot;</span><span class="p">:</span> <span class="p">[]}</span>
@@ -58,13 +70,19 @@ route to specific methods, pass the <code class="docutils literal notranslate"><
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;created&quot;</span><span class="p">:</span> <span class="n">data</span><span class="p">}</span>
</pre></div>
</div>
<p>Note the <code class="docutils literal notranslate"><span class="pre">check_existing=False</span></code>this allows you to register multiple
handlers for the same path with different methods.</p>
<p>Note the <code class="docutils literal notranslate"><span class="pre">check_existing=False</span></code>Responder normally prevents you from
registering two routes with the same path (to catch typos). When you
intentionally want multiple handlers for the same path with different
methods, you need to opt in.</p>
</section>
<section id="class-based-views">
<h2>Class-Based Views<a class="headerlink" href="#class-based-views" title="Link to this heading"></a></h2>
<p>For more complex resources, you can use class-based views. Responder will
dispatch to the appropriate method handler based on the HTTP method:</p>
<p>Function-based views are great for simple endpoints, but sometimes you want
to group related HTTP methods together into a single resource. This is
where class-based views come in — a pattern popularized by
<a class="reference external" href="https://falconframework.org/">Falcon</a>.</p>
<p>Responder dispatches to the appropriate method handler based on the HTTP
method:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/</span><span class="si">{greeting}</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="k">class</span><span class="w"> </span><span class="nc">GreetingResource</span><span class="p">:</span>
<span class="k">def</span><span class="w"> </span><span class="nf">on_get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">greeting</span><span class="p">):</span>
@@ -81,13 +99,17 @@ dispatch to the appropriate method handler based on the HTTP method:</p>
<p>The <code class="docutils literal notranslate"><span class="pre">on_request</span></code> method is called for all HTTP methods, much like
middleware scoped to a single route. Method-specific handlers (<code class="docutils literal notranslate"><span class="pre">on_get</span></code>,
<code class="docutils literal notranslate"><span class="pre">on_post</span></code>, <code class="docutils literal notranslate"><span class="pre">on_put</span></code>, <code class="docutils literal notranslate"><span class="pre">on_delete</span></code>, etc.) are called after.</p>
<p>No inheritance required — just define a class with the right method names.</p>
<p>No inheritance required — just define a class with the right method names.
This is simpler than Djangos <code class="docutils literal notranslate"><span class="pre">View</span></code> classes and more Pythonic than
framework-specific base classes.</p>
</section>
<section id="lifespan-events">
<h2>Lifespan Events<a class="headerlink" href="#lifespan-events" title="Link to this heading"></a></h2>
<p>Modern applications often need to set up resources on startup (database
connections, caches, ML models) and tear them down on shutdown. Responder
supports the lifespan context manager pattern:</p>
<p>Real applications need to set up resources when they start (database
connection pools, ML models, caches) and tear them down when they stop.
This is called the application <em>lifespan</em>.</p>
<p>The modern approach is the <em>context manager</em> pattern, where startup and
shutdown are two halves of the same block:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">contextlib</span><span class="w"> </span><span class="kn">import</span> <span class="n">asynccontextmanager</span>
<span class="nd">@asynccontextmanager</span>
@@ -101,7 +123,10 @@ supports the lifespan context manager pattern:</p>
<span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">lifespan</span><span class="o">=</span><span class="n">lifespan</span><span class="p">)</span>
</pre></div>
</div>
<p>You can also use the traditional event decorator style:</p>
<p>Everything before <code class="docutils literal notranslate"><span class="pre">yield</span></code> runs at startup. Everything after runs at
shutdown. If startup fails, the server wont start. If shutdown raises,
its logged but the server still exits.</p>
<p>The traditional event decorator style also works:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">on_event</span><span class="p">(</span><span class="s2">&quot;startup&quot;</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">startup</span><span class="p">():</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;starting up&quot;</span><span class="p">)</span>
@@ -111,52 +136,65 @@ supports the lifespan context manager pattern:</p>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;shutting down&quot;</span><span class="p">)</span>
</pre></div>
</div>
<p>The context manager approach is preferred for new code — it makes the
startup/shutdown relationship explicit and keeps related code together.</p>
<p>The context manager is preferred for new code — it keeps related startup
and shutdown logic together and makes resource cleanup more explicit.</p>
</section>
<section id="serving-files">
<h2>Serving Files<a class="headerlink" href="#serving-files" title="Link to this heading"></a></h2>
<p>Serve files from disk with automatic content-type detection. Responder
uses Pythons <code class="docutils literal notranslate"><span class="pre">mimetypes</span></code> module to figure out the right <code class="docutils literal notranslate"><span class="pre">Content-Type</span></code>
header for you:</p>
<p>Web applications often need to serve files — downloads, reports, images.
Responder makes this simple with <code class="docutils literal notranslate"><span class="pre">resp.file()</span></code>, which reads a file from
disk and sets the <code class="docutils literal notranslate"><span class="pre">Content-Type</span></code> header automatically using Pythons
<code class="docutils literal notranslate"><span class="pre">mimetypes</span></code> module:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/download&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">download</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">file</span><span class="p">(</span><span class="s2">&quot;reports/annual.pdf&quot;</span><span class="p">)</span>
</pre></div>
</div>
<p>You can override the content type if needed:</p>
<p>You can override the content type if the automatic detection isnt right:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/image&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">image</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">file</span><span class="p">(</span><span class="s2">&quot;photos/cat.jpg&quot;</span><span class="p">,</span> <span class="n">content_type</span><span class="o">=</span><span class="s2">&quot;image/jpeg&quot;</span><span class="p">)</span>
</pre></div>
</div>
<p>For large files, use <code class="docutils literal notranslate"><span class="pre">resp.stream_file()</span></code> to avoid loading the entire
file into memory. This streams the file in chunks:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/export&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">export</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">stream_file</span><span class="p">(</span><span class="s2">&quot;data/export.csv&quot;</span><span class="p">)</span>
</pre></div>
</div>
</section>
<section id="custom-error-handling">
<h2>Custom Error Handling<a class="headerlink" href="#custom-error-handling" title="Link to this heading"></a></h2>
<p>By default, unhandled exceptions result in a 500 Internal Server Error.
You can register custom handlers for specific exception types to return
structured error responses:</p>
<p>In production, you dont want your users to see raw Python tracebacks.
Responder lets you register custom handlers for specific exception types,
so you can return clean, structured error responses:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">exception_handler</span><span class="p">(</span><span class="ne">ValueError</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">handle_value_error</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="n">exc</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="mi">400</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;error&quot;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">exc</span><span class="p">)}</span>
</pre></div>
</div>
<p>Now, any route that raises a <code class="docutils literal notranslate"><span class="pre">ValueError</span></code> will return a clean 400 response
with a JSON error message instead of a generic 500 page.</p>
<p>Now, any route that raises a <code class="docutils literal notranslate"><span class="pre">ValueError</span></code> will return a clean JSON
response with a 400 status code instead of a generic 500 error page.</p>
<p>This is a common pattern in API development — you define your own exception
classes for different error conditions, register handlers for each, and
your API always returns consistent, machine-readable error responses.</p>
</section>
<section id="before-request-hooks">
<h2>Before-Request Hooks<a class="headerlink" href="#before-request-hooks" title="Link to this heading"></a></h2>
<p>Run code before every request. This is useful for logging, adding common
headers, or setting up per-request state:</p>
<p>Sometimes you need to run the same code before every request —
authentication checks, request logging, adding common headers, or setting
up per-request state. Before-request hooks let you do this without
duplicating code in every route:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="n">before_request</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">add_headers</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">&quot;X-API-Version&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;3.1&quot;</span>
<span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">&quot;X-API-Version&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;3.2&quot;</span>
</pre></div>
</div>
<p><strong>Short-circuiting:</strong> If your hook sets <code class="docutils literal notranslate"><span class="pre">resp.status_code</span></code>, the route
handler will be skipped entirely and the response will be sent immediately.
This is the pattern for authentication guards:</p>
<p><strong>Short-circuiting</strong> is the really powerful part. If your hook sets
<code class="docutils literal notranslate"><span class="pre">resp.status_code</span></code>, the route handler is skipped entirely and the
response is sent immediately. This is the pattern for authentication:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="n">before_request</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">auth_check</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="k">if</span> <span class="s2">&quot;Authorization&quot;</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">req</span><span class="o">.</span><span class="n">headers</span><span class="p">:</span>
@@ -165,17 +203,32 @@ This is the pattern for authentication guards:</p>
</pre></div>
</div>
<p>If the <code class="docutils literal notranslate"><span class="pre">Authorization</span></code> header is missing, the client gets a 401 response
and the actual route handler never runs.</p>
<p>WebSocket hooks work the same way:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">before_request</span><span class="p">(</span><span class="n">websocket</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">ws_auth</span><span class="p">(</span><span class="n">ws</span><span class="p">):</span>
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">accept</span><span class="p">()</span>
and the actual route handler never runs. This is cleaner than adding
auth checks to every individual route.</p>
</section>
<section id="after-request-hooks">
<h2>After-Request Hooks<a class="headerlink" href="#after-request-hooks" title="Link to this heading"></a></h2>
<p>The complement to before-request hooks. After-request hooks run after the
route handler completes but before the response is sent. Theyre useful
for logging, adding response headers, or any post-processing:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">after_request</span><span class="p">()</span>
<span class="k">def</span><span class="w"> </span><span class="nf">log_response</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="n">req</span><span class="o">.</span><span class="n">method</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">req</span><span class="o">.</span><span class="n">full_url</span><span class="si">}</span><span class="s2"> -&gt; </span><span class="si">{</span><span class="n">resp</span><span class="o">.</span><span class="n">status_code</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="nd">@api</span><span class="o">.</span><span class="n">after_request</span><span class="p">()</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">add_timing</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">&quot;X-Served-By&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;responder&quot;</span>
</pre></div>
</div>
</section>
<section id="websocket-support">
<h2>WebSocket Support<a class="headerlink" href="#websocket-support" title="Link to this heading"></a></h2>
<p>Responder supports WebSockets for real-time, bidirectional communication:</p>
<p>HTTP is a request-response protocol — the client asks, the server answers.
But some applications need real-time, bidirectional communication: chat
apps, live dashboards, multiplayer games, collaborative editors.</p>
<p><a class="reference external" href="https://en.wikipedia.org/wiki/WebSocket">WebSockets</a> solve this by
upgrading an HTTP connection into a persistent, full-duplex channel where
both sides can send messages at any time:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/ws&quot;</span><span class="p">,</span> <span class="n">websocket</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">websocket</span><span class="p">(</span><span class="n">ws</span><span class="p">):</span>
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">accept</span><span class="p">()</span>
@@ -187,13 +240,49 @@ and the actual route handler never runs.</p>
</div>
<p>You can send and receive in multiple formats:</p>
<ul class="simple">
<li><p><code class="docutils literal notranslate"><span class="pre">send_text</span></code> / <code class="docutils literal notranslate"><span class="pre">receive_text</span></code> — plain text</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">send_json</span></code> / <code class="docutils literal notranslate"><span class="pre">receive_json</span></code> — JSON objects</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">send_text</span></code> / <code class="docutils literal notranslate"><span class="pre">receive_text</span></code> — plain text strings</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">send_json</span></code> / <code class="docutils literal notranslate"><span class="pre">receive_json</span></code> — JSON objects (auto-serialized)</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">send_bytes</span></code> / <code class="docutils literal notranslate"><span class="pre">receive_bytes</span></code> — raw binary data</p></li>
</ul>
<p>WebSocket routes are marked with <code class="docutils literal notranslate"><span class="pre">websocket=True</span></code> in the route decorator.
They receive a <code class="docutils literal notranslate"><span class="pre">ws</span></code> object instead of <code class="docutils literal notranslate"><span class="pre">req</span></code> and <code class="docutils literal notranslate"><span class="pre">resp</span></code>.</p>
</section>
<section id="server-sent-events-sse">
<h2>Server-Sent Events (SSE)<a class="headerlink" href="#server-sent-events-sse" title="Link to this heading"></a></h2>
<p>SSE is a simpler alternative to WebSockets for <em>one-way</em> real-time
communication — the server pushes events to the client, but the client
cant send messages back. This is perfect for live feeds, progress bars,
notification streams, and AI response streaming.</p>
<p>Unlike WebSockets, SSE works over plain HTTP, is automatically reconnected
by the browser, and doesnt require any special client-side libraries:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/events&quot;</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">events</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="nd">@resp</span><span class="o">.</span><span class="n">sse</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">stream</span><span class="p">():</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
<span class="k">yield</span> <span class="p">{</span><span class="s2">&quot;data&quot;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&quot;message </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">}</span>
</pre></div>
</div>
<p>On the client side, you consume SSE events with JavaScripts built-in
<code class="docutils literal notranslate"><span class="pre">EventSource</span></code> API:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">const</span> <span class="n">source</span> <span class="o">=</span> <span class="n">new</span> <span class="n">EventSource</span><span class="p">(</span><span class="s2">&quot;/events&quot;</span><span class="p">);</span>
<span class="n">source</span><span class="o">.</span><span class="n">onmessage</span> <span class="o">=</span> <span class="p">(</span><span class="n">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
<span class="n">console</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="n">event</span><span class="o">.</span><span class="n">data</span><span class="p">);</span>
<span class="p">};</span>
</pre></div>
</div>
<p>Each yielded value can be a string (treated as data) or a dict with the
standard SSE fields:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">yield</span> <span class="p">{</span><span class="s2">&quot;event&quot;</span><span class="p">:</span> <span class="s2">&quot;update&quot;</span><span class="p">,</span> <span class="s2">&quot;data&quot;</span><span class="p">:</span> <span class="s2">&quot;hello&quot;</span><span class="p">,</span> <span class="s2">&quot;id&quot;</span><span class="p">:</span> <span class="s2">&quot;1&quot;</span><span class="p">,</span> <span class="s2">&quot;retry&quot;</span><span class="p">:</span> <span class="s2">&quot;5000&quot;</span><span class="p">}</span>
<span class="k">yield</span> <span class="s2">&quot;simple string message&quot;</span>
</pre></div>
</div>
</section>
<section id="graphql">
<h2>GraphQL<a class="headerlink" href="#graphql" title="Link to this heading"></a></h2>
<p><a class="reference external" href="https://graphql.org/">GraphQL</a> is a query language for APIs that lets
clients request exactly the data they need — no more, no less. Instead of
multiple REST endpoints, you define a schema and let clients query it.</p>
<p>Responder includes built-in GraphQL support via
<a class="reference external" href="https://graphene-python.org/">Graphene</a>. Set up a full GraphQL endpoint
with a single method call:</p>
@@ -208,16 +297,20 @@ with a single method call:</p>
<span class="n">api</span><span class="o">.</span><span class="n">graphql</span><span class="p">(</span><span class="s2">&quot;/graphql&quot;</span><span class="p">,</span> <span class="n">schema</span><span class="o">=</span><span class="n">graphene</span><span class="o">.</span><span class="n">Schema</span><span class="p">(</span><span class="n">query</span><span class="o">=</span><span class="n">Query</span><span class="p">))</span>
</pre></div>
</div>
<p>Visiting <code class="docutils literal notranslate"><span class="pre">/graphql</span></code> in a browser renders the GraphiQL interactive IDE,
where you can explore your schema and test queries. Programmatic clients
can POST JSON queries to the same endpoint.</p>
<p>Visiting <code class="docutils literal notranslate"><span class="pre">/graphql</span></code> in a browser renders the
<a class="reference external" href="https://github.com/graphql/graphiql">GraphiQL</a> interactive IDE, where
you can explore your schema, write queries, and see results in real-time.
Programmatic clients can POST JSON queries to the same endpoint.</p>
<p>You can access the Responder request and response objects in your resolvers
through <code class="docutils literal notranslate"><span class="pre">info.context[&quot;request&quot;]</span></code> and <code class="docutils literal notranslate"><span class="pre">info.context[&quot;response&quot;]</span></code>.</p>
</section>
<section id="openapi-documentation">
<h2>OpenAPI Documentation<a class="headerlink" href="#openapi-documentation" title="Link to this heading"></a></h2>
<p>Responder can generate an OpenAPI schema and serve interactive API
documentation automatically:</p>
<p><a class="reference external" href="https://www.openapis.org/">OpenAPI</a> (formerly Swagger) is the industry
standard for describing REST APIs. An OpenAPI specification lets you
auto-generate interactive documentation, client libraries, and validation
logic.</p>
<p>Responder generates OpenAPI specs from your code:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span>
<span class="n">title</span><span class="o">=</span><span class="s2">&quot;Pet Store&quot;</span><span class="p">,</span>
<span class="n">version</span><span class="o">=</span><span class="s2">&quot;1.0&quot;</span><span class="p">,</span>
@@ -232,9 +325,11 @@ documentation automatically:</p>
<li><p>Interactive Swagger UI documentation at <code class="docutils literal notranslate"><span class="pre">/docs</span></code></p></li>
</ul>
<p>There are three ways to document your endpoints.</p>
<p><strong>Pydantic models</strong> — the recommended approach for new APIs. Use
<code class="docutils literal notranslate"><span class="pre">request_model</span></code> and <code class="docutils literal notranslate"><span class="pre">response_model</span></code> to annotate your routes, and
Responder will generate the schema automatically:</p>
<p><strong>Pydantic models</strong> — the recommended approach. Use <code class="docutils literal notranslate"><span class="pre">request_model</span></code> and
<code class="docutils literal notranslate"><span class="pre">response_model</span></code> to annotate your routes, and Responder generates the
schema automatically. When <code class="docutils literal notranslate"><span class="pre">request_model</span></code> is set, request bodies are
also validated automatically — invalid inputs get a <code class="docutils literal notranslate"><span class="pre">422</span></code> response with
detailed error messages:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">pydantic</span><span class="w"> </span><span class="kn">import</span> <span class="n">BaseModel</span>
<span class="k">class</span><span class="w"> </span><span class="nc">PetIn</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
@@ -253,18 +348,10 @@ Responder will generate the schema automatically:</p>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="o">**</span><span class="n">data</span><span class="p">}</span>
</pre></div>
</div>
<p>This generates a full OpenAPI path with <code class="docutils literal notranslate"><span class="pre">requestBody</span></code> and <code class="docutils literal notranslate"><span class="pre">responses</span></code>
schemas, all linked by <code class="docutils literal notranslate"><span class="pre">$ref</span></code> to your Pydantic models in
<code class="docutils literal notranslate"><span class="pre">components/schemas</span></code>.</p>
<p>You can also register standalone schemas with the <code class="docutils literal notranslate"><span class="pre">&#64;api.schema</span></code> decorator:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">schema</span><span class="p">(</span><span class="s2">&quot;Pet&quot;</span><span class="p">)</span>
<span class="k">class</span><span class="w"> </span><span class="nc">Pet</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">age</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span>
</pre></div>
</div>
<p><strong>YAML docstrings</strong> — inline your OpenAPI spec directly in the docstring.
This gives you full control over every detail:</p>
<p>When <code class="docutils literal notranslate"><span class="pre">response_model</span></code> is set, the response is serialized through the
model — extra fields are stripped and types are enforced.</p>
<p><strong>YAML docstrings</strong> — for full control, embed OpenAPI YAML in the
docstring:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/pets&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">list_pets</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;A list of pets.</span>
@@ -278,8 +365,7 @@ This gives you full control over every detail:</p>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">[{</span><span class="s2">&quot;name&quot;</span><span class="p">:</span> <span class="s2">&quot;Fido&quot;</span><span class="p">}]</span>
</pre></div>
</div>
<p><strong>Marshmallow schemas</strong> — if youre already using marshmallow for
validation, Responder integrates with it via the apispec plugin:</p>
<p><strong>Marshmallow schemas</strong> — if youre already using marshmallow:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">marshmallow</span><span class="w"> </span><span class="kn">import</span> <span class="n">Schema</span><span class="p">,</span> <span class="n">fields</span>
<span class="nd">@api</span><span class="o">.</span><span class="n">schema</span><span class="p">(</span><span class="s2">&quot;Pet&quot;</span><span class="p">)</span>
@@ -287,16 +373,39 @@ validation, Responder integrates with it via the apispec plugin:</p>
<span class="n">name</span> <span class="o">=</span> <span class="n">fields</span><span class="o">.</span><span class="n">Str</span><span class="p">()</span>
</pre></div>
</div>
<p>All three approaches can be mixed in the same API. Pydantic models,
marshmallow schemas, and YAML docstrings all contribute to the same
generated OpenAPI specification.</p>
<p>You can choose from multiple documentation themes:
<code class="docutils literal notranslate"><span class="pre">swagger_ui</span></code> (default), <code class="docutils literal notranslate"><span class="pre">redoc</span></code>, <code class="docutils literal notranslate"><span class="pre">rapidoc</span></code>, or <code class="docutils literal notranslate"><span class="pre">elements</span></code>.</p>
<p>All three approaches can be mixed in the same API. You can choose from
multiple documentation themes: <code class="docutils literal notranslate"><span class="pre">swagger_ui</span></code> (default), <code class="docutils literal notranslate"><span class="pre">redoc</span></code>,
<code class="docutils literal notranslate"><span class="pre">rapidoc</span></code>, or <code class="docutils literal notranslate"><span class="pre">elements</span></code>.</p>
</section>
<section id="route-groups">
<h2>Route Groups<a class="headerlink" href="#route-groups" title="Link to this heading"></a></h2>
<p>As your application grows, youll want to organize routes logically.
Route groups let you share a URL prefix across related endpoints — a
common pattern for API versioning:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">v1</span> <span class="o">=</span> <span class="n">api</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="s2">&quot;/v1&quot;</span><span class="p">)</span>
<span class="nd">@v1</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/users&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">list_users</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">[]</span>
<span class="nd">@v1</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/users/{user_id:int}&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">get_user</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">user_id</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;id&quot;</span><span class="p">:</span> <span class="n">user_id</span><span class="p">}</span>
<span class="n">v2</span> <span class="o">=</span> <span class="n">api</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="s2">&quot;/v2&quot;</span><span class="p">)</span>
<span class="nd">@v2</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/users&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">list_users_v2</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;users&quot;</span><span class="p">:</span> <span class="p">[],</span> <span class="s2">&quot;total&quot;</span><span class="p">:</span> <span class="mi">0</span><span class="p">}</span>
</pre></div>
</div>
<p>This keeps your code organized without affecting the routing logic.</p>
</section>
<section id="mounting-other-apps">
<h2>Mounting Other Apps<a class="headerlink" href="#mounting-other-apps" title="Link to this heading"></a></h2>
<p>Responder can mount any WSGI or ASGI application at a subroute. This means
you can gradually migrate from Flask, or run multiple frameworks side by side:</p>
<p>Responder can mount any WSGI or ASGI application at a subroute. This is
incredibly useful for gradual migrations — you can run Flask and Responder
side by side, moving routes over one at a time:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">flask</span><span class="w"> </span><span class="kn">import</span> <span class="n">Flask</span>
<span class="n">flask_app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
@@ -310,10 +419,14 @@ you can gradually migrate from Flask, or run multiple frameworks side by side:</
</div>
<p>Requests to <code class="docutils literal notranslate"><span class="pre">/flask/</span></code> will be handled by Flask. Everything else goes
through Responder. Both WSGI and ASGI apps are supported — Responder
wraps WSGI apps automatically.</p>
wraps WSGI apps in an ASGI adapter automatically.</p>
</section>
<section id="cookies">
<h2>Cookies<a class="headerlink" href="#cookies" title="Link to this heading"></a></h2>
<p><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies">Cookies</a> are
small pieces of data that the server asks the browser to store and send
back with every subsequent request. Theyre the foundation of sessions,
authentication tokens, and user preferences on the web.</p>
<p>Reading and writing cookies is straightforward:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Read cookies from the request</span>
<span class="n">session_id</span> <span class="o">=</span> <span class="n">req</span><span class="o">.</span><span class="n">cookies</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;session_id&quot;</span><span class="p">)</span>
@@ -322,24 +435,27 @@ wraps WSGI apps automatically.</p>
<span class="n">resp</span><span class="o">.</span><span class="n">cookies</span><span class="p">[</span><span class="s2">&quot;hello&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;world&quot;</span>
</pre></div>
</div>
<p>For more control over cookie directives, use <code class="docutils literal notranslate"><span class="pre">set_cookie</span></code>:</p>
<p>For production use, youll want to set security directives. The
<code class="docutils literal notranslate"><span class="pre">httponly</span></code> flag prevents JavaScript from reading the cookie (defending
against XSS attacks), and <code class="docutils literal notranslate"><span class="pre">secure</span></code> ensures its only sent over HTTPS:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">resp</span><span class="o">.</span><span class="n">set_cookie</span><span class="p">(</span>
<span class="s2">&quot;token&quot;</span><span class="p">,</span>
<span class="n">value</span><span class="o">=</span><span class="s2">&quot;abc123&quot;</span><span class="p">,</span>
<span class="n">max_age</span><span class="o">=</span><span class="mi">3600</span><span class="p">,</span>
<span class="n">secure</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
<span class="n">httponly</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
<span class="n">max_age</span><span class="o">=</span><span class="mi">3600</span><span class="p">,</span> <span class="c1"># expires in 1 hour</span>
<span class="n">secure</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="c1"># HTTPS only</span>
<span class="n">httponly</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="c1"># no JavaScript access</span>
<span class="n">path</span><span class="o">=</span><span class="s2">&quot;/&quot;</span><span class="p">,</span>
<span class="p">)</span>
</pre></div>
</div>
<p>Supported directives: <code class="docutils literal notranslate"><span class="pre">key</span></code>, <code class="docutils literal notranslate"><span class="pre">value</span></code>, <code class="docutils literal notranslate"><span class="pre">expires</span></code>, <code class="docutils literal notranslate"><span class="pre">max_age</span></code>,
<code class="docutils literal notranslate"><span class="pre">domain</span></code>, <code class="docutils literal notranslate"><span class="pre">path</span></code>, <code class="docutils literal notranslate"><span class="pre">secure</span></code>, <code class="docutils literal notranslate"><span class="pre">httponly</span></code>.</p>
</section>
<section id="cookie-based-sessions">
<h2>Cookie-Based Sessions<a class="headerlink" href="#cookie-based-sessions" title="Link to this heading"></a></h2>
<p>Responder has built-in support for signed, cookie-based sessions. Just
read from and write to the <code class="docutils literal notranslate"><span class="pre">session</span></code> dictionary:</p>
<p>Sessions let you store per-user data across multiple requests. Responders
built-in sessions are cookie-based — the session data is serialized, signed
with your secret key, and stored in a cookie. The signature prevents
tampering: if someone modifies the cookie, the signature wont match and
the data will be rejected:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/login&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">login</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">session</span><span class="p">[</span><span class="s2">&quot;username&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;alice&quot;</span>
@@ -349,12 +465,9 @@ read from and write to the <code class="docutils literal notranslate"><span clas
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;user&quot;</span><span class="p">:</span> <span class="n">req</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;username&quot;</span><span class="p">)}</span>
</pre></div>
</div>
<p>The session data is stored in a cookie called <code class="docutils literal notranslate"><span class="pre">Responder-Session</span></code>. Its
signed for tamper protection, so you can trust that the data originated
from your server.</p>
<div class="admonition warning">
<p class="admonition-title">Warning</p>
<p>For production use, always set a secret key:</p>
<p>Always set a secret key in production. The default key is not secret:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">secret_key</span><span class="o">=</span><span class="s2">&quot;your-secret-key-here&quot;</span><span class="p">)</span>
</pre></div>
</div>
@@ -362,27 +475,30 @@ from your server.</p>
</section>
<section id="static-files">
<h2>Static Files<a class="headerlink" href="#static-files" title="Link to this heading"></a></h2>
<p>Static files are served from the <code class="docutils literal notranslate"><span class="pre">static/</span></code> directory by default:</p>
<p>Most web applications serve static assets — CSS stylesheets, JavaScript
files, images, fonts. Responder serves these from the <code class="docutils literal notranslate"><span class="pre">static/</span></code> directory
by default:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">static_dir</span><span class="o">=</span><span class="s2">&quot;static&quot;</span><span class="p">,</span> <span class="n">static_route</span><span class="o">=</span><span class="s2">&quot;/static&quot;</span><span class="p">)</span>
</pre></div>
</div>
<p>Place your CSS, JavaScript, images, and other assets in the <code class="docutils literal notranslate"><span class="pre">static/</span></code>
directory and theyll be served automatically.</p>
<p>For single-page applications, you can serve <code class="docutils literal notranslate"><span class="pre">index.html</span></code> as the default
response for all unmatched routes:</p>
<p>Place your assets in the <code class="docutils literal notranslate"><span class="pre">static/</span></code> directory and theyll be served
automatically at <code class="docutils literal notranslate"><span class="pre">/static/style.css</span></code>, <code class="docutils literal notranslate"><span class="pre">/static/app.js</span></code>, etc.</p>
<p>For single-page applications (React, Vue, Angular), you can serve
<code class="docutils literal notranslate"><span class="pre">index.html</span></code> as the default response for all unmatched routes:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span><span class="o">.</span><span class="n">add_route</span><span class="p">(</span><span class="s2">&quot;/&quot;</span><span class="p">,</span> <span class="n">static</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</pre></div>
</div>
<p>You can add additional static directories at runtime:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span><span class="o">.</span><span class="n">static_app</span><span class="o">.</span><span class="n">add_directory</span><span class="p">(</span><span class="s2">&quot;extra_assets&quot;</span><span class="p">)</span>
</pre></div>
</div>
</section>
<section id="cors">
<h2>CORS<a class="headerlink" href="#cors" title="Link to this heading"></a></h2>
<p>Enable Cross-Origin Resource Sharing for your API:</p>
<p><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">CORS</a> (Cross-
Origin Resource Sharing) is a security mechanism that controls which
websites can make requests to your API. Browsers enforce this — if your
API is at <code class="docutils literal notranslate"><span class="pre">api.example.com</span></code> and your frontend is at <code class="docutils literal notranslate"><span class="pre">app.example.com</span></code>,
the browser will block requests unless your API explicitly allows it.</p>
<p>Enable CORS and configure which origins are allowed:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">cors</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">cors_params</span><span class="o">=</span><span class="p">{</span>
<span class="s2">&quot;allow_origins&quot;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&quot;https://example.com&quot;</span><span class="p">],</span>
<span class="s2">&quot;allow_origins&quot;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&quot;https://app.example.com&quot;</span><span class="p">],</span>
<span class="s2">&quot;allow_methods&quot;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&quot;GET&quot;</span><span class="p">,</span> <span class="s2">&quot;POST&quot;</span><span class="p">],</span>
<span class="s2">&quot;allow_headers&quot;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&quot;*&quot;</span><span class="p">],</span>
<span class="s2">&quot;allow_credentials&quot;</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
@@ -390,98 +506,54 @@ response for all unmatched routes:</p>
<span class="p">})</span>
</pre></div>
</div>
<p>The default CORS policy is restrictive — you must explicitly enable the
origins, methods, and headers your frontend needs.</p>
<p>The default policy is restrictive — you must explicitly allow each origin.
Using <code class="docutils literal notranslate"><span class="pre">[&quot;*&quot;]</span></code> for allow_origins permits any website to call your API,
which is fine for public APIs but not for private ones.</p>
</section>
<section id="hsts">
<h2>HSTS<a class="headerlink" href="#hsts" title="Link to this heading"></a></h2>
<p>Force all traffic to HTTPS with a single flag:</p>
<p><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security">HSTS</a>
(HTTP Strict Transport Security) tells browsers to always use HTTPS when
communicating with your server. Once a browser sees the HSTS header, it
will refuse to connect over plain HTTP, even if the user types <code class="docutils literal notranslate"><span class="pre">http://</span></code>
in the address bar:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">enable_hsts</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</pre></div>
</div>
<p>This adds the <code class="docutils literal notranslate"><span class="pre">Strict-Transport-Security</span></code> header and redirects HTTP
requests to HTTPS.</p>
</section>
<section id="trusted-hosts">
<h2>Trusted Hosts<a class="headerlink" href="#trusted-hosts" title="Link to this heading"></a></h2>
<p>Protect against HTTP Host header attacks by restricting which hostnames
your application will respond to:</p>
<p>The <code class="docutils literal notranslate"><span class="pre">Host</span></code> header in an HTTP request tells the server which domain name
the client used. Attackers can forge this header to trick your application
into generating URLs to malicious domains (a class of attack called <em>Host
header injection</em>).</p>
<p>Restrict which hostnames your application accepts:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">allowed_hosts</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;example.com&quot;</span><span class="p">,</span> <span class="s2">&quot;*.example.com&quot;</span><span class="p">])</span>
</pre></div>
</div>
<p>Requests with a <code class="docutils literal notranslate"><span class="pre">Host</span></code> header that doesnt match any of the patterns
will receive a 400 Bad Request response. Wildcard domains are supported.</p>
<p>By default, all hostnames are allowed.</p>
</section>
<section id="server-sent-events-sse">
<h2>Server-Sent Events (SSE)<a class="headerlink" href="#server-sent-events-sse" title="Link to this heading"></a></h2>
<p>Stream real-time updates to the client using Server-Sent Events. This is
great for live feeds, progress updates, and AI streaming responses:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/events&quot;</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">events</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="nd">@resp</span><span class="o">.</span><span class="n">sse</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">stream</span><span class="p">():</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
<span class="k">yield</span> <span class="p">{</span><span class="s2">&quot;data&quot;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&quot;message </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">}</span>
</pre></div>
</div>
<p>Each yielded value can be a string (treated as data) or a dict with
<code class="docutils literal notranslate"><span class="pre">data</span></code>, <code class="docutils literal notranslate"><span class="pre">event</span></code>, <code class="docutils literal notranslate"><span class="pre">id</span></code>, and <code class="docutils literal notranslate"><span class="pre">retry</span></code> fields:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">yield</span> <span class="p">{</span><span class="s2">&quot;event&quot;</span><span class="p">:</span> <span class="s2">&quot;update&quot;</span><span class="p">,</span> <span class="s2">&quot;data&quot;</span><span class="p">:</span> <span class="s2">&quot;hello&quot;</span><span class="p">,</span> <span class="s2">&quot;id&quot;</span><span class="p">:</span> <span class="s2">&quot;1&quot;</span><span class="p">}</span>
<span class="k">yield</span> <span class="s2">&quot;simple string message&quot;</span>
</pre></div>
</div>
</section>
<section id="streaming-files">
<h2>Streaming Files<a class="headerlink" href="#streaming-files" title="Link to this heading"></a></h2>
<p>For large files, use <code class="docutils literal notranslate"><span class="pre">resp.stream_file()</span></code> to stream the content without
loading the entire file into memory:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/download&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">download</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">stream_file</span><span class="p">(</span><span class="s2">&quot;large-dataset.csv&quot;</span><span class="p">)</span>
</pre></div>
</div>
<p>For small files where memory isnt a concern, <code class="docutils literal notranslate"><span class="pre">resp.file()</span></code> loads the
entire file at once — simpler but less efficient for large files.</p>
</section>
<section id="after-request-hooks">
<h2>After-Request Hooks<a class="headerlink" href="#after-request-hooks" title="Link to this heading"></a></h2>
<p>Run code after every request, useful for logging, adding headers, or
cleanup:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">after_request</span><span class="p">()</span>
<span class="k">def</span><span class="w"> </span><span class="nf">log_response</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="n">req</span><span class="o">.</span><span class="n">method</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">req</span><span class="o">.</span><span class="n">full_url</span><span class="si">}</span><span class="s2"> -&gt; </span><span class="si">{</span><span class="n">resp</span><span class="o">.</span><span class="n">status_code</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
</pre></div>
</div>
</section>
<section id="route-groups">
<h2>Route Groups<a class="headerlink" href="#route-groups" title="Link to this heading"></a></h2>
<p>Organize related routes with a shared URL prefix. Useful for API versioning
and logical grouping:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">v1</span> <span class="o">=</span> <span class="n">api</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="s2">&quot;/v1&quot;</span><span class="p">)</span>
<span class="nd">@v1</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/users&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">list_users</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">[]</span>
<span class="nd">@v1</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/users/{user_id:int}&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">get_user</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">user_id</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;id&quot;</span><span class="p">:</span> <span class="n">user_id</span><span class="p">}</span>
</pre></div>
</div>
<p>Requests with unrecognized hosts get a <code class="docutils literal notranslate"><span class="pre">400</span> <span class="pre">Bad</span> <span class="pre">Request</span></code>. Wildcard
patterns are supported. By default, all hostnames are allowed.</p>
</section>
<section id="request-id">
<h2>Request ID<a class="headerlink" href="#request-id" title="Link to this heading"></a></h2>
<p>Auto-generate unique request IDs for tracing and debugging. If the client
sends an <code class="docutils literal notranslate"><span class="pre">X-Request-ID</span></code> header, its forwarded; otherwise a new UUID is
generated:</p>
<p>In distributed systems, tracing a single request across multiple services
is essential for debugging. Request IDs are unique identifiers attached to
each request — if something goes wrong, you can search your logs for that
ID and find every related event.</p>
<p>Responder can auto-generate request IDs. If the client sends an
<code class="docutils literal notranslate"><span class="pre">X-Request-ID</span></code> header (common in microservice architectures), its
forwarded. Otherwise, a new UUID is generated:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">request_id</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</pre></div>
</div>
<p>The ID appears in the <code class="docutils literal notranslate"><span class="pre">X-Request-ID</span></code> response header.</p>
</section>
<section id="rate-limiting">
<h2>Rate Limiting<a class="headerlink" href="#rate-limiting" title="Link to this heading"></a></h2>
<p>Built-in token bucket rate limiter:</p>
<p>Rate limiting prevents individual clients from overwhelming your API with
too many requests. Its essential for public APIs, and good practice even
for internal ones.</p>
<p>Responder includes a built-in token bucket rate limiter:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder.ext.ratelimit</span><span class="w"> </span><span class="kn">import</span> <span class="n">RateLimiter</span>
<span class="n">limiter</span> <span class="o">=</span> <span class="n">RateLimiter</span><span class="p">(</span><span class="n">requests</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span> <span class="n">period</span><span class="o">=</span><span class="mi">60</span><span class="p">)</span> <span class="c1"># 100 req/min</span>
@@ -489,19 +561,24 @@ generated:</p>
</pre></div>
</div>
<p>When the limit is exceeded, clients receive a <code class="docutils literal notranslate"><span class="pre">429</span> <span class="pre">Too</span> <span class="pre">Many</span> <span class="pre">Requests</span></code>
response with <code class="docutils literal notranslate"><span class="pre">Retry-After</span></code> and <code class="docutils literal notranslate"><span class="pre">X-RateLimit-Remaining</span></code> headers.</p>
response with a <code class="docutils literal notranslate"><span class="pre">Retry-After</span></code> header. Every response includes
<code class="docutils literal notranslate"><span class="pre">X-RateLimit-Limit</span></code> and <code class="docutils literal notranslate"><span class="pre">X-RateLimit-Remaining</span></code> headers so clients
can pace themselves.</p>
<p>The rate limiter is per-client, keyed by IP address.</p>
</section>
<section id="messagepack">
<h2>MessagePack<a class="headerlink" href="#messagepack" title="Link to this heading"></a></h2>
<p>In addition to JSON and YAML, Responder supports MessagePack for efficient
binary serialization:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Decode MessagePack request body</span>
<p><a class="reference external" href="https://msgpack.org/">MessagePack</a> is a binary serialization format
thats more compact and faster to parse than JSON. Its useful for
high-throughput APIs, IoT devices, and anywhere bandwidth matters.</p>
<p>Responder supports MessagePack alongside JSON and YAML:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Decode a MessagePack request body</span>
<span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">req</span><span class="o">.</span><span class="n">media</span><span class="p">(</span><span class="s2">&quot;msgpack&quot;</span><span class="p">)</span>
<span class="c1"># Content negotiation also works — clients can send</span>
<span class="c1"># Accept: application/x-msgpack to receive MessagePack responses.</span>
</pre></div>
</div>
<p>Content negotiation works too — clients can send
<code class="docutils literal notranslate"><span class="pre">Accept:</span> <span class="pre">application/x-msgpack</span></code> to receive MessagePack responses
instead of JSON.</p>
</section>
</section>
@@ -535,9 +612,12 @@ binary serialization:</p>
<li><a class="reference internal" href="#serving-files">Serving Files</a></li>
<li><a class="reference internal" href="#custom-error-handling">Custom Error Handling</a></li>
<li><a class="reference internal" href="#before-request-hooks">Before-Request Hooks</a></li>
<li><a class="reference internal" href="#after-request-hooks">After-Request Hooks</a></li>
<li><a class="reference internal" href="#websocket-support">WebSocket Support</a></li>
<li><a class="reference internal" href="#server-sent-events-sse">Server-Sent Events (SSE)</a></li>
<li><a class="reference internal" href="#graphql">GraphQL</a></li>
<li><a class="reference internal" href="#openapi-documentation">OpenAPI Documentation</a></li>
<li><a class="reference internal" href="#route-groups">Route Groups</a></li>
<li><a class="reference internal" href="#mounting-other-apps">Mounting Other Apps</a></li>
<li><a class="reference internal" href="#cookies">Cookies</a></li>
<li><a class="reference internal" href="#cookie-based-sessions">Cookie-Based Sessions</a></li>
@@ -545,10 +625,6 @@ binary serialization:</p>
<li><a class="reference internal" href="#cors">CORS</a></li>
<li><a class="reference internal" href="#hsts">HSTS</a></li>
<li><a class="reference internal" href="#trusted-hosts">Trusted Hosts</a></li>
<li><a class="reference internal" href="#server-sent-events-sse">Server-Sent Events (SSE)</a></li>
<li><a class="reference internal" href="#streaming-files">Streaming Files</a></li>
<li><a class="reference internal" href="#after-request-hooks">After-Request Hooks</a></li>
<li><a class="reference internal" href="#route-groups">Route Groups</a></li>
<li><a class="reference internal" href="#request-id">Request ID</a></li>
<li><a class="reference internal" href="#rate-limiting">Rate Limiting</a></li>
<li><a class="reference internal" href="#messagepack">MessagePack</a></li>