Your first API
Install a server, return a dict, and make a request. This is the part where a larger framework might ask you to generate a project, three directories, and a personal philosophy.
Make a small project
Start in a new directory and install Wred 0.10 or newer. The server extra installs Weft.
mkdir hello-wred
cd hello-wred
python -m venv .venv
. .venv/bin/activate
python -m pip install 'wred[server]>=0.10'Give it some routes
Save this as first_api.py. An async handler receives a Wred request. Returning a dict or list produces JSON with status 200.
from wred import App
app = App()
@app.route('/')
async def hello(request):
return {'hello': 'world'}
@app.route('/api/health')
async def health(request):
return {'status': 'ok'}
@app.route('/api/hello/{name}')
async def greet(request, name):
return {'hello': name, 'language': request.query.get('lang', 'en')}name is a string, passed by keyword. request.query is a synchronous cached property and returns strings too. Wred will not infer that a query parameter is an integer because you looked at it meaningfully.
Methods default to GET. Fixed paths win over parameterized paths for the same method; trailing slashes are ignored. Unknown paths and unsupported methods return JSON 404. HEAD, OPTIONS, and 405 handling aren’t added automatically.
Run it and ask it something
weft first_api:appLeave the server running. In a second terminal:
curl -sS http://127.0.0.1:8000/
# {"hello":"world"}
curl -sS 'http://127.0.0.1:8000/api/hello/Ada?lang=en'
# {"hello":"Ada","language":"en"}The Python module is first_api; the exported ASGI application is app. That’s what the colon means. We have now exhausted the mystery of first_api:app.
Accept JSON without accepting absolutely anything
Save the next example as validation.py, stop the first server, and run weft validation:app. This endpoint echoes a validated title; it does not store anything.
import orjson
from wred import App
app = App()
@app.route('/api/echo', methods=['POST'])
async def echo(request):
try:
data = await request.json()
except orjson.JSONDecodeError:
return {'error': 'Invalid JSON'}, 'application/json', 400
if not isinstance(data, dict):
return {'error': 'Send a JSON object'}, 'application/json', 400
title = data.get('title')
if not isinstance(title, str) or not 1 <= len(title.strip()) <= 100:
return {'error': 'Title must contain 1–100 characters'}, 'application/json', 400
return {'title': title.strip()}curl -i http://127.0.0.1:8000/api/echo \
-H 'Content-Type: application/json' \
-d '{"title":"My first note"}'The response body is {"title":"My first note"}. A JSON array, missing title, or invalid JSON gets 400. Schema validation is your application’s decision; a few explicit checks are perfectly respectable.
Choose the response you mean
| Return value | Result |
|---|---|
{"ok": True} | JSON, 200 |
{"id": "n1"}, "application/json", 201 | JSON, 201 |
b"", "text/plain", 303, [("Location", "/")] | Redirect |
b"", "application/json", 204 | Empty response |
| A callable ASGI response | The response object sends its own messages |
Custom tuple bodies can be bytes, dicts, or lists. Dicts and lists use JSON even if you give them another content type. Exceptions propagate to the server or your middleware; Wred doesn’t convert them into a reassuringly vague success.
