Flask остаётся самым удобным способом собрать небольшое веб-приложение на Python: минимум решений по умолчанию, понятная структура и возможность добавлять только нужные компоненты. В 2026 году он по-прежнему уместен для внутренних сервисов, API, админок и прототипов, которые затем вырастают в рабочий продукт. Ниже — установка, структура проекта, маршруты, шаблоны, работа с базой, тесты и развёртывание под Gunicorn и Nginx.
Что такое Flask и чего в нём нет
Flask — микрофреймворк: он даёт маршрутизацию, шаблонизатор Jinja2, работу с запросами и ответами, сессии и механизм расширений. В нём нет собственного ORM, форм, миграций и админки — всё это подключается отдельно. Такой подход нравится тем, кто не хочет тащить в проект лишние слои, и раздражает тех, кто ждёт готовую структуру как в Django.
Границы приложения задаёт разработчик. Если не продумать структуру заранее, через полгода файл app.py превращается в тысячу строк с маршрутами, запросами к базе и HTML. Поэтому factory-паттерн и blueprints стоит освоить с самого начала.
Установка и каркас проекта
Работайте в виртуальном окружении, чтобы зависимости не смешивались с системным Python. Для продакшена сразу добавьте Gunicorn и драйвер базы данных.
python3 -m venv .venv
. .venv/bin/activate
pip install flask gunicorn flask-sqlalchemy flask-migrate python-dotenv psycopg[binary]
flask --app app run --debug
Минимальный вариант с фабрикой приложения выглядит так. Фабрика позволяет создавать несколько экземпляров с разными настройками, что пригодится в тестах.
from flask import Flask
def create_app(config=None):
app = Flask(__name__)
app.config.from_prefixed_env()
if config:
app.config.update(config)
from views import bp
app.register_blueprint(bp)
return app
Файл views.py содержит маршруты, templates — шаблоны, static — CSS и JavaScript. Такое разделение не обязательное, но заметно упрощает сопровождение.
Маршруты, шаблоны и JSON
Blueprint группирует маршруты по смыслу: главная страница, API, административная часть. Декораторы get и post появились в современных версиях Flask и читаются короче, чем methods=[«GET»].
from flask import Blueprint, render_template, jsonify, request
bp = Blueprint("main", __name__)
@bp.get("/")
def index():
return render_template("index.html", title="Главная")
@bp.get("/api/health")
def health():
return jsonify(status="ok")
@bp.post("/api/items")
def create_item():
data = request.get_json(silent=True) or {}
if not data.get("name"):
return jsonify(error="name is required"), 400
return jsonify(id=1, name=data["name"]), 201
Шаблоны наследуются через extends и block. Автоэкранирование включено по умолчанию, поэтому выводить пользовательские строки через |safe без необходимости опасно. Для API используйте jsonify: он выставляет правильный Content-Type и сериализует данные предсказуемо.
Конфигурация и секреты
Настройки читаются из переменных окружения с префиксом FLASK_. Секретный ключ, строка подключения к базе и параметры почты не должны лежать в репозитории. Для локальной разработки используйте файл .env, который добавлен в .gitignore, а на сервере — переменные systemd или менеджера секретов.
export FLASK_SECRET_KEY="$(python -c 'import secrets; print(secrets.token_hex(32))')"
export FLASK_SQLALCHEMY_DATABASE_URI="postgresql+psycopg://app:pass@localhost/appdb"
flask --app app routes
flask --app app shell
Команда routes показывает все зарегистрированные адреса и их имена — быстрый способ найти забытый эндпоинт. Shell открывает интерактивную консоль с контекстом приложения, где удобно проверять запросы к базе.
База данных и миграции
Flask-SQLAlchemy даёт удобную обёртку над SQLAlchemy, а Flask-Migrate управляет схемой через Alembic. Модели описываются классами, миграции генерируются автоматически, но результат обязательно просматривают глазами: автогенерация не понимает переименование полей и может предложить удаление данных.
flask --app app db init
flask --app app db migrate -m "add items"
flask --app app db upgrade
flask --app app db current
Перед миграцией в продакшене сделайте дамп базы. Откат через downgrade возможен только при корректно описанной миграции, а операции с большими таблицами лучше выполнять в несколько этапов.
Ошибки, безопасность и типичные промахи
В продакшене отключите debug: интерактивный отладчик позволяет выполнять код на сервере. Обработчики ошибок должны возвращать аккуратные страницы и не раскрывать стек вызовов.
@app.errorhandler(404)
def not_found(error):
return render_template("404.html"), 404
@app.after_request
def security_headers(response):
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "SAMEORIGIN"
return response
Добавьте ограничение размера запроса через MAX_CONTENT_LENGTH, CSRF-защиту для форм через Flask-WTF и ограничение частоты запросов для публичных эндпоинтов. Если приложение стоит за reverse proxy, настройте ProxyFix, иначе Flask будет видеть внутренний IP и неверную схему.
- Секретный ключ захардкожен в коде — частая причина утечки сессий.
- Отладочный сервер оставлен в продакшене — низкая производительность и риск RCE.
- Миграции применяются вручную без резервной копии — риск потери данных.
- Тяжёлые задачи выполняются в обработчике запроса — таймауты и зависания.
Тесты
Тестовый клиент Flask не поднимает сетевой сервер и работает быстро. Фикстура создаёт приложение с временной базой, а каждый тест получает чистый контекст.
import pytest
from app import create_app
@pytest.fixture
def client():
app = create_app({"TESTING": True, "SQLALCHEMY_DATABASE_URI": "sqlite://"})
with app.test_client() as client:
yield client
def test_health(client):
response = client.get("/api/health")
assert response.status_code == 200
assert response.get_json()["status"] == "ok"
Запускайте pytest -q в CI и не забывайте про проверку прав доступа: тест, который дергает админский эндпоинт без авторизации, должен получать 401 или 403.
Развёртывание
Gunicorn запускает несколько worker-процессов и держит приложение без отладочного режима. Число worker-ов обычно считают как два на ядро плюс один, но для приложений с большим потреблением памяти значение подбирают по факту.
gunicorn -w 4 -b 127.0.0.1:8000 "app:create_app()"
systemctl status flaskapp
journalctl -u flaskapp -n 50 --no-pager
curl -i http://127.0.0.1:8000/api/health
Nginx принимает внешние соединения, отдаёт статику и проксирует запросы на localhost. Таймауты и размер тела запроса должны соответствовать возможностям приложения, иначе пользователи увидят 502 или 413.
server {
listen 80;
server_name app.example.ru;
location /static/ { root /srv/flaskapp; }
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Для диагностики смотрите журнал Gunicorn и systemd, проверяйте, что виртуальное окружение активировано в unit-файле, а рабочая директория указана явно. Ошибка «ModuleNotFoundError» почти всегда означает неверный путь или запуск не тем интерпретатором.
Итог
Flask хорош там, где нужен контроль над структурой и минимум магии. Начните с фабрики приложения и blueprints, вынесите настройки в окружение, подключите миграции и тесты, а на сервер ставьте Gunicorn за Nginx. Главные правила: никакого debug в продакшене, секреты вне репозитория, резервная копия перед миграцией и явная проверка прав доступа.