FastAPI: руководство для начинающих
FastAPI на Python с нуля: что это за фреймворк, установка, маршруты и параметры, Pydantic-модели, Depends, работа с БД, тесты и запуск через fastapi dev.
Содержание · 20 разделов
- Что такое FastAPI
- Установка FastAPI
- Первое приложение и запуск
- Path-параметры
- Query-параметры
- Тело запроса и Pydantic-модели
- Модель ответа: response_model
- Автоматическая документация: /docs и /redoc
- Зависимости: Depends
- async def или def
- Работа с базой данных
- Обработка ошибок: HTTPException
- Тестирование: TestClient
- Запуск: fastapi dev, fastapi run и uvicorn
- Структура проекта и APIRouter
- Документация и уроки FastAPI
- Частые ошибки
- 422 Unprocessable Entity на любой запрос
- «‘uvicorn’ не является внутренней или внешней командой»
- Изменения в коде не применяются
- Запрос без слэша возвращает 307
- Сервер «зависает» под нагрузкой
- Чек-лист первого проекта
- Частые вопросы
- Как включить CORS в FastAPI?
- Как принять файл от клиента?
- Как выполнить действие после отправки ответа?
- Как вернуть HTML или текст вместо JSON?
- Как отключить /docs в продакшне?
- Итог
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
После запуска приложения доступны три адреса:
| Адрес | Что там |
|---|---|
/docs | Swagger UI: список эндпоинтов, схемы, кнопка «Try it out» |
/redoc | ReDoc: та же схема в формате справочника |
/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 80 | Uvicorn на всех интерфейсах и нужном порту |
Команда 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
- #Веб-разработка