Создание ресурса в FastAPI: статус 201 и заголовок Location
POST вернул «готово», но клиенту всё равно приходится угадывать адрес новой записи. Полезный ответ создания содержит идентификатор, данные и адрес ресурса. Статус 201 сообщает о завершённом создании, а Location даёт точку следующего обращения.
Адрес должен строиться по маршруту
Не склеивайте hostname и путь вручную в каждом обработчике. request.url_for использует имя маршрута и его path-параметры. Если вы перенесёте API под другой префикс, связанные имена помогут сохранить правильный адрес. Имя должно быть уникальным и осмысленным, чтобы ссылка указывала на нужную операцию.
201 подходит после фактического создания. Когда команда только принята в очередь, клиенту обычно нужен 202 и адрес задачи. В примере запись уже находится в хранилище к моменту ответа, поэтому GET по Location сразу возвращает тот же ресурс. В базе этот порядок должен включать успешный commit.
Проверим переход по выданной ссылке
Для запуска нужны FastAPI и HTTPX. Response позволяет добавить заголовок, сохранив обычную сериализацию словаря. Тест берёт Location из реального ответа и вызывает его, не повторяя формулу построения URL в проверке.
Сохраните весь блок в файл и запустите Python 3.11+ в отдельном окружении с пакетами fastapi, httpx. Для моделей используется Pydantic v2; отдельный сервер для TestClient не нужен.
from fastapi import FastAPI, Request, Response, HTTPException
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
app = FastAPI()
items = {}
class ItemIn(BaseModel):
name: str = Field(min_length=1)
@app.get("/api/items/{item_id}", name="read_item")
def read_item(item_id: int):
if item_id not in items:
raise HTTPException(404, "item not found")
return items[item_id]
@app.post("/api/items", status_code=201)
def create_item(data: ItemIn, request: Request, response: Response):
item_id = len(items) + 1
item = {"id": item_id, "name": data.name}
items[item_id] = item
response.headers["Location"] = str(request.url_for("read_item", item_id=item_id))
return item
with TestClient(app) as client:
created = client.post("/api/items", json={"name": "Lamp"})
assert created.status_code == 201
assert created.json() == {"id": 1, "name": "Lamp"}
location = created.headers["location"]
assert location == "http://testserver/api/items/1"
assert client.get(location).json() == created.json()
print("created resource is readable through Location")Что проверить в опубликованном приложении
За прокси url_for опирается на схему и host запроса, известные приложению. Если Location содержит внутренний адрес, проверьте конфигурацию прокси и доверенных forwarded-заголовков. Локальный тест подтверждает путь приложения, а внешний проверяет действительный публичный origin. Не подставляйте непроверенный клиентский заголовок в ссылку вручную.
Если frontend на другом origin читает Location из JavaScript, разрешите этот заголовок через CORS expose_headers. Сам факт его наличия в HTTP-ответе не всегда делает его доступным браузерному коду. Для обычного backend-клиента такого ограничения чтения нет.
Как сохранить договор при развитии API
Новый id должен оставаться идентификатором ресурса, а не номером операции в памяти. Для постоянной базы используйте сгенерированное значение после flush или commit и убедитесь, что ссылка содержит именно его. Не отдавайте клиенту заранее вычисленный id, если вставка ещё способна завершиться отказом.
Проверка перехода по Location связывает создание и чтение. Она обнаружит переименование пути, неверный параметр и ссылку на отсутствующую запись. Добавьте отдельный тест неуспешного создания: при 422 или 409 заголовок нового ресурса не должен создавать впечатление успешной операции.
Следующая практика
Открытый урок создания ресурса посвящён POST и статусу 201. Выполните базовый сценарий, затем добавьте Location и проверку чтения по выданному адресу, чтобы связать создание с дальнейшей работой клиента. Перейдите к бесплатной практике POST и 201.
Бесплатный курс FastAPI поможет связать создание ресурса с базой, авторизацией и тестами. После сохранения записи проверяйте не только полученный id, но и следующую операцию клиента.
Продолжить на практике
- FastAPI для начинающих: API с базой данных и тестами ↗
Соберёте свой первый полноценный backend на FastAPI: с базой данных, регистрацией, JWT-авторизацией и автотестами. 34 коротких урока и 140 заданий с проверкой — бесплатно.
