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

Минимальное FastAPI-приложение — это объект FastAPI(), к которому через декораторы привязаны функции-обработчики, запущенный ASGI-сервером uvicorn.

Три кирпича любого приложения: экземпляр app = FastAPI(), декоратор маршрута вроде @app.get("/") и ASGI-сервер uvicorn, который принимает реальные HTTP-соединения.

Давайте соберём первое приложение и поймём, что делает каждая строка. FastAPI сам по себе не открывает сетевой порт — он лишь описывает, как обрабатывать запросы. Принимать соединения должен ASGI-сервер; стандартный выбор — uvicorn. Он слушает порт, получает сырой HTTP, превращает его в ASGI-события и передаёт вашему app.

Вот канонический пример. Поскольку это код FastAPI, он показан как обычный текст:

from fastapi import FastAPI

app = FastAPI(title="Моё первое API")

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

@app.get("/ping")
async def ping():
    return {"status": "ok"}

Запускаем сервер командой. Современный способ — встроенный CLI fastapi, классический — напрямую uvicorn:

fastapi dev main.py
uvicorn main:app --reload

Здесь main — имя файла без .py, app — имя переменной с приложением. Флаг --reload перезапускает сервер при изменении кода (только для разработки). После запуска FastAPI бесплатно отдаёт интерактивную документацию по адресу /docs (Swagger UI) и /redoc.

Как работает под капотом

Декоратор @app.get("/ping") не вызывает функцию — он регистрирует её в таблице маршрутов приложения, связывая пару «метод + путь» с обработчиком и его сигнатурой. Когда придёт запрос, Starlette найдёт совпадение в этой таблице. Концептуально таблица маршрутов — это просто словарь. Смоделируем мини-роутер на stdlib:

routes = {}

def get(path):
    def decorator(func):
        routes[("GET", path)] = func   # регистрация, как делает @app.get
        return func
    return decorator

@get("/")
def root():
    return {"message": "Привет"}

@get("/ping")
def ping():
    return {"status": "ok"}

def handle(method, path):
    handler = routes.get((method, path))
    if handler is None:
        return 404, {"detail": "Not Found"}
    return 200, handler()

print(handle("GET", "/"))
print(handle("GET", "/ping"))
print(handle("GET", "/missing"))

Попробуй сам ▶ Так же FastAPI ищет обработчик: совпало — вызывает, не совпало — отдаёт 404.

А что значит «вернуть словарь»? FastAPI берёт результат функции и сериализует его в JSON, проставляя заголовок Content-Type: application/json. Это та же логика, что и стандартный json.dumps:

import json

result = {"message": "Привет, FastAPI", "items": [1, 2, 3]}
body = json.dumps(result, ensure_ascii=False)
print("тело ответа:", body)
print("длина в байтах:", len(body.encode("utf-8")))

Попробуй сам ▶ Возврат словаря из обработчика — это, по сути, json.dumps под капотом.

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

Типичная путаница новичков: в команде запуска перепутать имя файла и имя переменной (main:app — это файл:переменная, а не наоборот). Ещё одна — оставить --reload в продакшене, где он не нужен и ест ресурсы. Третья — пытаться «запустить FastAPI» без сервера: сам по себе модуль ничего не слушает, нужен uvicorn или CLI.

Best practices

  • Давайте приложению осмысленные title, version, description — они попадут в /docs.
  • В разработке используйте fastapi dev или uvicorn --reload; в проде — fastapi run или uvicorn под управлением gunicorn/процесс-менеджера.
  • Сразу открывайте /docs — это лучший способ проверить и продемонстрировать API.

ASGI против WSGI — почему это важно

FastAPI работает поверх ASGI, а не классического WSGI, и это не формальность. WSGI — старый синхронный стандарт связи между веб-сервером и Python-приложением: один запрос — один поток, который занят целиком до ответа. ASGI — его асинхронный наследник: он умеет приостанавливать обработку в точках ожидания и обслуживать тысячи соединений малым числом потоков, а заодно поддерживает веб-сокеты и события жизненного цикла. Именно поэтому FastAPI нельзя запустить под обычным WSGI-сервером вроде классического gunicorn без ASGI-воркера — нужен uvicorn (или gunicorn с UvicornWorker). Понимание этого слоя объясняет, почему «просто запустить файл» недостаточно: приложение лишь описывает обработку в терминах ASGI-событий, а превращать реальные TCP-соединения в эти события должен ASGI-сервер.

Итог: приложение — это FastAPI() плюс зарегистрированные декораторами маршруты, запущенное ASGI-сервером. Декоратор только регистрирует функцию, а возврат словаря превращается в JSON-ответ.

Проверьте себя
1. Что делает декоратор @app.get("/ping") с функцией?
AСразу вызывает её и кэширует результат
BРегистрирует её в таблице маршрутов, связывая метод GET и путь /ping с этим обработчиком
CПревращает её в синхронную
DОткрывает сетевой порт
2. Что означает app в команде uvicorn main:app --reload?
AНазвание проекта
BИмя переменной с экземпляром FastAPI внутри файла main.py
CИмя файла
DВерсию фреймворка