Browse documentation

Documentation overview

Tutorial

Reference

Project

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.

Terminal
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.

first_api.pyDownload
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

Terminal
weft first_api:app

Leave the server running. In a second terminal:

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.

validation.pyDownload
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()}
Terminal
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 valueResult
{"ok": True}JSON, 200
{"id": "n1"}, "application/json", 201JSON, 201
b"", "text/plain", 303, [("Location", "/")]Redirect
b"", "application/json", 204Empty response
A callable ASGI responseThe 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.