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.11 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.11'

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 are JSON-encoded while preserving the tuple’s explicit content type. Exceptions propagate to the server or your middleware; Wred doesn’t convert them into a reassuringly vague success.