Django-Admin.ru
FastAPI · Основы FastAPI★ Руководство

FastAPI: руководство для начинающих

FastAPI на Python с нуля: что это за фреймворк, установка, маршруты и параметры, Pydantic-модели, Depends, работа с БД, тесты и запуск через fastapi dev.

Редакция Django-Admin.ru
· обновлено · 17 мин чтения

Содержание · 20 разделов
  1. Что такое FastAPI
  2. Установка FastAPI
  3. Первое приложение и запуск
  4. Path-параметры
  5. Query-параметры
  6. Тело запроса и Pydantic-модели
  7. Модель ответа: response_model
  8. Автоматическая документация: /docs и /redoc
  9. Зависимости: Depends
  10. async def или def
  11. Работа с базой данных
  12. Обработка ошибок: HTTPException
  13. Тестирование: TestClient
  14. Запуск: fastapi dev, fastapi run и uvicorn
  15. Структура проекта и APIRouter
  16. Документация и уроки FastAPI
  17. Частые ошибки
  18. 422 Unprocessable Entity на любой запрос
  19. «‘uvicorn’ не является внутренней или внешней командой»
  20. Изменения в коде не применяются
  21. Запрос без слэша возвращает 307
  22. Сервер «зависает» под нагрузкой
  23. Чек-лист первого проекта
  24. Частые вопросы
  25. Как включить CORS в FastAPI?
  26. Как принять файл от клиента?
  27. Как выполнить действие после отправки ответа?
  28. Как вернуть HTML или текст вместо JSON?
  29. Как отключить /docs в продакшне?
  30. Итог

FastAPI — веб-фреймворк на Python для создания API. Вы описываете эндпоинты обычными функциями с аннотациями типов, а FastAPI сам проверяет входные данные, преобразует их в нужные типы, возвращает понятные ошибки и строит интерактивную документацию. Минимальное приложение — пять строк кода, а для старта нужны две команды:

pip install "fastapi[standard]"
fastapi dev main.py

В руководстве пройдём весь базовый путь: от установки и первого эндпоинта до Pydantic-моделей, зависимостей, базы данных, тестов и структуры проекта. Каждый раздел — короткий обзор с рабочим примером и ссылкой на подробную статью. Примеры проверены на FastAPI 0.143, Pydantic 2.14 и Python 3.12.

Что такое FastAPI

FastAPI построен на двух библиотеках: Starlette отвечает за веб-часть (маршрутизация, запросы и ответы, WebSocket), Pydantic — за валидацию и сериализацию данных. Сам FastAPI связывает их через аннотации типов Python: по сигнатуре функции он понимает, откуда брать параметры и как их проверять.

Ключевые особенности:

  • Аннотации типов вместо ручного разбора. item_id: int — и строка из URL сама станет числом, а "abc" вернёт клиенту ошибку 422.
  • Автодокументация. Из кода генерируется схема OpenAPI и страницы /docs (Swagger UI) и /redoc.
  • ASGI и асинхронность. Приложение работает на ASGI-сервере (по умолчанию Uvicorn) и поддерживает async def, но обычные функции тоже работают.
  • Подсказки в редакторе. Раз всё описано типами, IDE подсказывает поля моделей и находит ошибки до запуска.

Для чего выбирают FastAPI: REST API для фронтенда и мобильных приложений, микросервисы, бэкенд для ML-моделей, сервисы с большим количеством сетевых запросов. Если же нужен сайт с шаблонами, готовой админкой и ORM «из коробки», чаще удобнее Django. Подробное сравнение — в статье FastAPI, Django или Flask: что выбрать, а общий обзор фреймворков — в материале про веб-фреймворки Python.

Установка FastAPI

FastAPI требует Python 3.10 или новее. Устанавливайте его в виртуальное окружение, чтобы зависимости проекта не смешивались с системными:

python -m venv .venv
# Windows:
.venv\Scripts\activate
# macOS / Linux:
source .venv/bin/activate

pip install "fastapi[standard]"

Набор [standard] кроме самого фреймворка ставит Uvicorn, консольную команду fastapi, HTTPX для тестов, email-validator, python-multipart для форм и pydantic-settings. Кавычки вокруг "fastapi[standard]" нужны, чтобы оболочка (например, zsh) не пыталась интерпретировать квадратные скобки.

Пошаговая установка для Windows, macOS и Linux, выбор порта и разбор ошибок запуска — в статье как установить и запустить FastAPI.

Первое приложение и запуск

Создайте файл main.py:

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def root():
    return {"message": "Привет, FastAPI!"}

Запустите сервер разработки:

fastapi dev main.py

Откройте в браузере http://127.0.0.1:8000 — вы увидите JSON:

{ "message": "Привет, FastAPI!" }

Разберём код. app = FastAPI() создаёт приложение. Декоратор @app.get("/") регистрирует операцию пути: обработчик для метода GET по адресу /. Функция возвращает словарь, и FastAPI сам сериализует его в JSON. Для других HTTP-методов есть @app.post(), @app.put(), @app.patch() и @app.delete().

fastapi dev запускает Uvicorn в режиме разработки: с автоперезагрузкой при изменении кода и только на 127.0.0.1. Для продакшна есть fastapi run, о нём — в разделе про запуск ниже.

Path-параметры

Часть пути можно сделать переменной — она передаётся в функцию как аргумент с тем же именем:

@app.get("/items/{item_id}")
def read_item(item_id: int):
    return {"item_id": item_id}

Запрос GET /items/42 вернёт {"item_id": 42} — обратите внимание, число, а не строка. Запрос GET /items/abc не дойдёт до функции: FastAPI сразу ответит статусом 422 Unprocessable Entity и описанием ошибки:

{
  "detail": [
    {
      "type": "int_parsing",
      "loc": ["path", "item_id"],
      "msg": "Input should be a valid integer, unable to parse string as an integer",
      "input": "abc"
    }
  ]
}

Поле loc показывает, где ошибка: в пути (path), в параметре item_id.

Порядок объявления маршрутов важен. Если есть фиксированный путь /users/me и параметризованный /users/{username}, первым объявляйте фиксированный — иначе запрос к /users/me попадёт в обработчик с username="me".

Дополнительные ограничения задаются через Path:

from typing import Annotated

from fastapi import Path


@app.get("/chapters/{chapter_id}")
def get_chapter(chapter_id: Annotated[int, Path(ge=1)]):
    return {"chapter_id": chapter_id}

Теперь GET /chapters/0 вернёт 422 с сообщением Input should be greater than or equal to 1.

Query-параметры

Все аргументы функции, которых нет в пути, FastAPI считает query-параметрами — частью URL после ?:

from typing import Annotated

from fastapi import Query


@app.get("/books/")
def list_books(
    q: str | None = None,
    skip: int = 0,
    limit: Annotated[int, Query(ge=1, le=100)] = 10,
):
    return {"q": q, "skip": skip, "limit": limit}

Правила простые:

  • параметр со значением по умолчанию — необязательный: GET /books/ вернёт {"q": null, "skip": 0, "limit": 10};
  • параметр без значения по умолчанию — обязательный, без него будет 422;
  • str | None = None — необязательная строка;
  • Query(ge=1, le=100) добавляет проверку диапазона: GET /books/?limit=0 вернёт 422.

Запрос GET /books/?q=django&limit=5 вернёт {"q": "django", "skip": 0, "limit": 5}.

Совет: форма Annotated[int, Query(...)] = 10 — основной способ в актуальных версиях FastAPI. В старых примерах встречается limit: int = Query(default=10, le=100): он тоже работает, но с Annotated код читается проще и переиспользуется в зависимостях.

Тело запроса и Pydantic-модели

Данные, которые клиент отправляет в теле POST/PUT/PATCH-запроса, описываются моделью Pydantic:

from fastapi import status
from pydantic import BaseModel, Field


class BookCreate(BaseModel):
    title: str = Field(min_length=1, max_length=200)
    author: str
    year: int = Field(ge=1450, le=2100)
    tags: list[str] = []


@app.post("/books/", status_code=status.HTTP_201_CREATED)
def create_book(book: BookCreate):
    return {"id": 1, **book.model_dump()}

Если аргумент функции — Pydantic-модель, FastAPI читает JSON из тела запроса, проверяет его по модели и передаёт в функцию готовый объект. Запрос с телом {"title": "Мастер и Маргарита", "author": "Булгаков", "year": 1967} вернёт статус 201 и:

{ "id": 1, "title": "Мастер и Маргарита", "author": "Булгаков", "year": 1967, "tags": [] }

Если отправить {"title": "Мастер и Маргарита", "year": "давно"}, ответ 422 перечислит обе проблемы сразу: отсутствует поле author ("type": "missing") и year не число ("type": "int_parsing").

Внутри функции с моделью работают как с обычным объектом: book.title, book.year. Метод model_dump() превращает модель в словарь — например, чтобы сохранить в базу.

Pydantic — отдельная библиотека, и её стоит знать глубже: типы полей, Field, собственные валидаторы, вложенные модели и настройки через pydantic-settings. Всё это разобрано в статье Pydantic: валидация данных в Python.

Модель ответа: response_model

Модель ответа определяет, какие поля уйдут клиенту. Это защищает от случайной утечки данных — например, пароля:

from pydantic import BaseModel, EmailStr


class UserIn(BaseModel):
    email: EmailStr
    password: str


class UserOut(BaseModel):
    id: int
    email: EmailStr


@app.post("/users/", response_model=UserOut)
def create_user(user: UserIn):
    return {"id": 1, **user.model_dump()}

Функция возвращает словарь с паролем, но клиент получит только {"id": 1, "email": "reader@example.com"}: FastAPI отфильтрует ответ по UserOut. Тип EmailStr проверяет формат адреса и требует пакет email-validator (входит в fastapi[standard]).

Вместо response_model можно указать аннотацию возвращаемого типа — эффект тот же:

@app.get("/profiles/{user_id}")
def get_profile(user_id: int) -> UserOut:
    return UserOut(id=user_id, email="reader@example.com")

response_model в декораторе удобен, когда функция возвращает словарь или объект другого типа, а аннотация — когда возвращается ровно та модель, что объявлена.

Автоматическая документация: /docs и /redoc

После запуска приложения доступны три адреса:

АдресЧто там
/docsSwagger UI: список эндпоинтов, схемы, кнопка «Try it out»
/redocReDoc: та же схема в формате справочника
/openapi.jsonСхема OpenAPI в JSON — для генераторов клиентов, Postman и т. п.

Документация строится из кода: пути, параметры, модели, ограничения ge/le, коды ответов. Заголовок и версию задают при создании приложения — FastAPI(title="Книжный API", version="1.0.0"), а группировку эндпоинтов — параметром tags. Отдельно писать документацию для фронтенда не нужно: в /docs можно сразу отправить запрос и посмотреть ответ.

Зависимости: Depends

Система внедрения зависимостей — способ вынести общий код (пагинацию, подключение к БД, проверку токена) в функции и подключать их к эндпоинтам:

from typing import Annotated

from fastapi import Depends


def pagination(skip: int = 0, limit: int = 10) -> dict:
    return {"skip": skip, "limit": limit}


Pagination = Annotated[dict, Depends(pagination)]


@app.get("/authors/")
def list_authors(page: Pagination):
    return {"page": page}

GET /authors/?skip=20 вернёт {"page": {"skip": 20, "limit": 10}}. FastAPI вызывает pagination перед обработчиком, сам достаёт для неё query-параметры и передаёт результат в page. Псевдоним Pagination можно использовать в любом количестве эндпоинтов.

Зависимость может ничего не возвращать, а только проверять запрос:

from fastapi import Header, HTTPException


def verify_token(x_token: Annotated[str, Header()]):
    if x_token != "secret-token":
        raise HTTPException(status_code=401, detail="Неверный токен")


@app.get("/admin/stats", dependencies=[Depends(verify_token)])
def admin_stats():
    return {"books": 42}

Параметр x_token читается из заголовка X-Token (подчёркивания автоматически превращаются в дефисы). Без заголовка будет 422, с неверным значением — 401 {"detail": "Неверный токен"}. Это учебный пример: в реальном API токены проверяют через OAuth2PasswordBearer и JWT.

async def или def

FastAPI поддерживает оба вида обработчиков:

@app.get("/async-example")
async def async_example():
    return {"ok": True}

Правило выбора:

  • async def — если внутри вы используете await: асинхронный HTTP-клиент (httpx.AsyncClient), асинхронный драйвер БД и т. п.
  • def — если код блокирующий: синхронная ORM, requests, работа с файлами. FastAPI запустит такую функцию в пуле потоков, и она не заблокирует остальные запросы.

Главная ошибка — вызывать блокирующий код внутри async def (например, time.sleep() или requests.get()): такой вызов останавливает цикл событий, и сервер перестаёт обслуживать другие запросы, пока он не завершится. Если сомневаетесь, пишите обычный def.

Работа с базой данных

В FastAPI нет встроенной ORM — вы выбираете её сами. Популярные варианты: SQLAlchemy, SQLModel (от автора FastAPI, объединяет модели SQLAlchemy и Pydantic), Tortoise ORM. Типовая схема: сессия БД выдаётся через зависимость с yield, таблицы создаются при старте в lifespan.

Короткий пример на SQLModel и SQLite (pip install sqlmodel):

from contextlib import asynccontextmanager
from typing import Annotated

from fastapi import Depends, FastAPI, HTTPException
from sqlmodel import Field, Session, SQLModel, create_engine, select


class Book(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    title: str
    year: int


engine = create_engine("sqlite:///database.db", connect_args={"check_same_thread": False})


def get_session():
    with Session(engine) as session:
        yield session


SessionDep = Annotated[Session, Depends(get_session)]


@asynccontextmanager
async def lifespan(app: FastAPI):
    SQLModel.metadata.create_all(engine)
    yield


app = FastAPI(lifespan=lifespan)


@app.post("/books/")
def create_book(book: Book, session: SessionDep) -> Book:
    session.add(book)
    session.commit()
    session.refresh(book)
    return book


@app.get("/books/{book_id}")
def get_book(book_id: int, session: SessionDep) -> Book:
    book = session.get(Book, book_id)
    if not book:
        raise HTTPException(status_code=404, detail="Книга не найдена")
    return book

Код до yield в get_session выполняется перед обработчиком, после — когда ответ сформирован: сессия гарантированно закрывается. Для изменения схемы в рабочем проекте вместо create_all используют миграции Alembic.

Если вам нужна база с готовой админкой и миграциями «из коробки», посмотрите на Django ORM — шпаргалка по QuerySet в Django поможет сравнить подходы.

Обработка ошибок: HTTPException

Чтобы вернуть клиенту ошибку, поднимите HTTPException с кодом и описанием:

from fastapi import FastAPI, HTTPException

app = FastAPI()

BOOKS = {1: {"id": 1, "title": "Мастер и Маргарита"}}


@app.get("/books/{book_id}")
def get_book(book_id: int):
    if book_id not in BOOKS:
        raise HTTPException(status_code=404, detail="Книга не найдена")
    return BOOKS[book_id]

Клиент получит статус 404 и тело {"detail": "Книга не найдена"}. Именно raise, а не return: исключение прерывает обработчик, где бы оно ни возникло — хоть в зависимости, хоть во вложенной функции.

Типичные коды ответов API:

КодКогда
400некорректный запрос с точки зрения бизнес-логики
401не передан или неверен токен
403пользователь авторизован, но прав не хватает
404объект не найден
409конфликт: например, email уже занят
422данные не прошли валидацию (FastAPI возвращает сам)

Для своих типов исключений можно зарегистрировать обработчик через @app.exception_handler(MyError).

Тестирование: TestClient

FastAPI позволяет тестировать приложение без запуска сервера — через TestClient. Тест для приложения из предыдущего раздела сохраните в файл test_main.py рядом с main.py:

from fastapi.testclient import TestClient

from main import app

client = TestClient(app)


def test_get_book():
    response = client.get("/books/1")
    assert response.status_code == 200
    assert response.json() == {"id": 1, "title": "Мастер и Маргарита"}


def test_book_not_found():
    response = client.get("/books/999")
    assert response.status_code == 404
    assert response.json() == {"detail": "Книга не найдена"}

Запуск:

pip install pytest
pytest

TestClient работает как HTTP-клиент: client.get(), client.post(url, json=...), headers=.... Если в приложении есть lifespan, создавайте клиент через with TestClient(app) as client: — тогда выполнится код запуска и остановки. Зависимости в тестах подменяют через app.dependency_overrides, например, чтобы подставить тестовую базу.

Важно: начиная со Starlette 1.2 (май 2026) TestClient переходит с httpx на пакет httpx2. Если установлен только httpx, при импорте появится предупреждение StarletteDeprecationWarning: Using httpx with starlette.testclient is deprecated; install httpx2 instead. Тесты при этом работают; чтобы убрать предупреждение, выполните pip install httpx2.

Запуск: fastapi dev, fastapi run и uvicorn

КомандаНазначение
fastapi dev main.pyразработка: автоперезагрузка, адрес 127.0.0.1:8000
fastapi run main.pyпродакшн: без перезагрузки, адрес 0.0.0.0:8000
fastapi run main.py --workers 4несколько процессов-воркеров
uvicorn main:app --reloadто же, что fastapi dev, напрямую через Uvicorn
uvicorn main:app --host 0.0.0.0 --port 80Uvicorn на всех интерфейсах и нужном порту

Команда fastapi — обёртка над Uvicorn: она сама находит файл (main.py, app.py, app/main.py и другие) и объект приложения app. В записи main:app для Uvicorn до двоеточия — модуль, после — имя переменной с приложением.

Порт меняется опцией --port 8080, адрес — --host. В продакшне перед приложением обычно ставят Nginx или другой обратный прокси, который отвечает за HTTPS и раздачу статики.

Структура проекта и APIRouter

Пока эндпоинтов немного, всё живёт в main.py. Когда их становится больше, код разбивают на модули с APIRouter:

project/
├── app/
│   ├── __init__.py
│   ├── main.py
│   └── routers/
│       ├── __init__.py
│       └── books.py
└── test_books.py
# app/routers/books.py
from fastapi import APIRouter, HTTPException

router = APIRouter(prefix="/books", tags=["books"])

BOOKS = {1: {"id": 1, "title": "Мастер и Маргарита"}}


@router.get("/")
def list_books():
    return list(BOOKS.values())


@router.get("/{book_id}")
def get_book(book_id: int):
    if book_id not in BOOKS:
        raise HTTPException(status_code=404, detail="Книга не найдена")
    return BOOKS[book_id]
# app/main.py
from fastapi import FastAPI

from app.routers import books

app = FastAPI()
app.include_router(books.router)

APIRouter работает так же, как app: те же декораторы get, post и параметры. prefix="/books" добавляется ко всем путям роутера, а tags группирует эндпоинты в /docs. Запускается такой проект командой fastapi dev без аргументов — она сама найдёт app/main.py.

В больших проектах рядом с routers/ обычно появляются models.py или schemas.py для Pydantic-моделей, db.py для подключения к базе и config.py для настроек на pydantic-settings.

Документация и уроки FastAPI

  • Официальная документация — fastapi.tiangolo.com. Раздел «Tutorial — User Guide» — фактически пошаговый курс: от первого эндпоинта до безопасности, баз данных и тестов. У документации есть русская версия — её переводят с помощью ИИ под контролем людей, поэтому при расхождениях сверяйтесь с английским оригиналом.
  • Книга Билла Любановича «FastAPI: веб-разработка на Python» (издательство «Питер») — русский перевод книги «FastAPI: Modern Python Web Development» (O’Reilly). Подходит для системного чтения, но фреймворк обновляется часто: сверяйте синтаксис с документацией.
  • Практика. Лучший урок — небольшой проект: API для списка задач или каталога книг с базой данных, тестами и документацией в /docs.

Если Python пока даётся с трудом, начните с основ: Python для начинающих — FastAPI активно использует аннотации типов, классы и декораторы.

Частые ошибки

422 Unprocessable Entity на любой запрос

Данные не прошли валидацию. Прочитайте detail в ответе: loc покажет, где ошибка (path, query, body, header), msg — что не так. Частая причина — данные отправлены как форма или query-параметры, а эндпоинт ждёт JSON в теле. В Python-клиентах используйте json=..., а не data=....

«‘uvicorn’ не является внутренней или внешней командой»

Не активировано виртуальное окружение или пакет не установлен. Активируйте окружение или запустите сервер через интерпретатор: python -m uvicorn main:app --reload. Подробности — в статье про установку FastAPI.

Изменения в коде не применяются

Сервер запущен без перезагрузки: fastapi run или uvicorn без --reload. Для разработки используйте fastapi dev.

Запрос без слэша возвращает 307

Маршрут объявлен как /books/, а запрос идёт на /books. FastAPI отвечает редиректом 307 Temporary Redirect на адрес со слэшем. Браузер и TestClient следуют за ним сами, но некоторые клиенты нет — используйте пути в том виде, в каком они объявлены.

Сервер «зависает» под нагрузкой

Внутри async def вызывается блокирующий код. Замените функцию на def или используйте асинхронные библиотеки.

Чек-лист первого проекта

  • Python 3.10+ и отдельное виртуальное окружение
  • pip install "fastapi[standard]", зависимости зафиксированы в requirements.txt
  • входные данные описаны Pydantic-моделями, ответы — моделями без секретных полей
  • ошибки возвращаются через HTTPException с понятным detail
  • общий код вынесен в зависимости Depends
  • блокирующий код не вызывается внутри async def
  • эндпоинты разнесены по APIRouter, есть тесты на TestClient
  • для продакшна — fastapi run или Uvicorn за обратным прокси

Частые вопросы

Как включить CORS в FastAPI?

Если фронтенд открыт с другого адреса (например, http://localhost:5173), браузер заблокирует его запросы к API без заголовков CORS. Подключите CORSMiddleware и перечислите разрешённые источники:

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:5173", "https://example.com"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

Значение allow_origins=["*"] нельзя сочетать с allow_credentials=True: браузер не примет такой ответ для запросов с cookie.

Как принять файл от клиента?

Объявите параметр типа UploadFile — FastAPI прочитает его из формы multipart/form-data (нужен пакет python-multipart, он входит в fastapi[standard]):

from fastapi import UploadFile


@app.post("/upload")
async def upload(file: UploadFile):
    content = await file.read()
    return {"filename": file.filename, "size": len(content)}

Отправка report.csv размером 8 байт вернёт {"filename": "report.csv", "size": 8}.

Как выполнить действие после отправки ответа?

Для коротких задач вроде записи в лог или отправки письма есть BackgroundTasks: функция запустится после того, как клиент получил ответ.

from fastapi import BackgroundTasks


def write_log(message: str):
    with open("log.txt", "a", encoding="utf-8") as f:
        f.write(message + "\n")


@app.post("/orders/")
def create_order(background_tasks: BackgroundTasks):
    background_tasks.add_task(write_log, "заказ создан")
    return {"status": "accepted"}

Задача выполняется в том же процессе, поэтому для долгих и важных операций берут очередь задач — например, Celery.

Как вернуть HTML или текст вместо JSON?

Укажите класс ответа: @app.get("/hello", response_class=HTMLResponse) и верните строку с разметкой — клиент получит Content-Type: text/html; charset=utf-8. Для простого текста есть PlainTextResponse, для файлов — FileResponse, все они импортируются из fastapi.responses. Полноценные страницы удобнее собирать шаблонами Jinja2 через Jinja2Templates.

Как отключить /docs в продакшне?

Передайте None в адреса документации при создании приложения:

app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None)

После этого /docs, /redoc и /openapi.json возвращают 404. Обычно значения берут из настроек, чтобы в разработке документация оставалась включённой.

Итог

FastAPI превращает аннотации типов Python в валидацию, сериализацию и документацию API. Для старта достаточно трёх вещей: операций пути с параметрами, Pydantic-моделей для тела и ответа и команды fastapi dev. Дальше подключаются зависимости, база данных, тесты и разбиение на роутеры.

Следующий шаг — установить и запустить FastAPI у себя, затем разобраться с Pydantic. Все материалы раздела собраны на странице FastAPI для начинающих.

  • #FastAPI
  • #Python
  • #Pydantic
  • #Uvicorn
  • #REST API
  • #Веб-разработка
↑ ↓ выборEnter открытьEsc закрыть