Разработка бэкенда сайта на Python (Flask)

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка бэкенда сайта на Python (Flask)
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Разработка бэкенда сайта на Python (Flask)

Flask остаётся актуальным не потому что устарел, а потому что правильно расставляет ответственности. Это микрофреймворк: он даёт HTTP-маршрутизацию, request/response контекст и ничего лишнего. ORM, сериализацию, аутентификацию, кеширование — выбираете и собираете сами. Это слабость для новичков и сила для опытных команд, которые хотят контроль.

Flask хорошо подходит для: прототипов, небольших API, сервисов с нестандартной логикой, проектов где Django избыточен, а FastAPI — оверинжиниринг.

Application Factory и Blueprints

Правильная инициализация Flask — через фабрику. Это позволяет создавать несколько экземпляров с разными конфигурациями (для тестов особенно важно):

# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_jwt_extended import JWTManager
from flask_caching import Cache

db = SQLAlchemy()
migrate = Migrate()
jwt = JWTManager()
cache = Cache()

def create_app(config_name: str = 'development') -> Flask:
    app = Flask(__name__)
    app.config.from_object(config[config_name])

    db.init_app(app)
    migrate.init_app(app, db)
    jwt.init_app(app)
    cache.init_app(app)

    # Регистрация Blueprint
    from .api.v1 import bp as api_v1
    app.register_blueprint(api_v1, url_prefix='/api/v1')

    from .auth import bp as auth_bp
    app.register_blueprint(auth_bp, url_prefix='/api/auth')

    return app

Blueprint изолирует группу маршрутов:

# app/api/v1/products.py
from flask import Blueprint, request, jsonify, abort
from ..models import Product
from ..extensions import db, cache
from .decorators import require_auth, require_role

bp = Blueprint('products', __name__)

@bp.get('/products')
@cache.cached(timeout=300, query_string=True)
def list_products():
    page = request.args.get('page', 1, type=int)
    per_page = request.args.get('per_page', 20, type=int)
    category_id = request.args.get('category_id', type=int)

    query = Product.query.filter_by(is_active=True)
    if category_id:
        query = query.filter_by(category_id=category_id)

    pagination = query.order_by(Product.created_at.desc()).paginate(
        page=page, per_page=per_page, error_out=False
    )

    return jsonify({
        'data': [p.to_dict() for p in pagination.items],
        'pagination': {
            'page': pagination.page,
            'pages': pagination.pages,
            'total': pagination.total
        }
    })

@bp.post('/products')
@require_auth
@require_role('admin')
def create_product():
    data = request.get_json() or {}

    errors = ProductSchema().validate(data)
    if errors:
        return jsonify({'errors': errors}), 422

    product = Product(
        name=data['name'],
        price=data['price'],
        category_id=data.get('category_id')
    )
    db.session.add(product)
    db.session.commit()
    return jsonify(product.to_dict()), 201

Модели SQLAlchemy

from .extensions import db
from datetime import datetime
from slugify import slugify

class Product(db.Model):
    __tablename__ = 'products'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), nullable=False)
    slug = db.Column(db.String(255), unique=True, nullable=False)
    price = db.Column(db.Numeric(10, 2), nullable=False)
    category_id = db.Column(db.Integer, db.ForeignKey('categories.id'), nullable=True)
    attributes = db.Column(db.JSON, default=dict)
    is_active = db.Column(db.Boolean, default=True, nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    category = db.relationship('Category', back_populates='products')

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        if not self.slug:
            self.slug = slugify(self.name)

    def to_dict(self) -> dict:
        return {
            'id': self.id,
            'name': self.name,
            'slug': self.slug,
            'price': float(self.price),
            'category': self.category.name if self.category else None
        }

Валидация через Marshmallow

from marshmallow import Schema, fields, validate, validates, ValidationError

class ProductSchema(Schema):
    name = fields.Str(required=True, validate=validate.Length(min=2, max=255))
    price = fields.Float(required=True, validate=validate.Range(min=0.01))
    category_id = fields.Int(load_default=None)
    description = fields.Str(load_default=None)

    @validates('category_id')
    def validate_category(self, value):
        if value is not None:
            from ..models import Category
            if not Category.query.get(value):
                raise ValidationError('Категория не найдена')

JWT аутентификация

flask-jwt-extended — стандарт:

from flask_jwt_extended import (
    create_access_token, create_refresh_token,
    jwt_required, get_jwt_identity, get_jwt
)

@auth_bp.post('/login')
def login():
    data = request.get_json()
    user = User.query.filter_by(email=data.get('email')).first()

    if not user or not user.check_password(data.get('password')):
        return jsonify({'error': 'Invalid credentials'}), 401

    additional_claims = {'role': user.role}
    access_token = create_access_token(identity=user.id, additional_claims=additional_claims)
    refresh_token = create_refresh_token(identity=user.id)

    return jsonify({
        'access_token': access_token,
        'refresh_token': refresh_token
    })

@auth_bp.post('/refresh')
@jwt_required(refresh=True)
def refresh():
    user_id = get_jwt_identity()
    access_token = create_access_token(identity=user_id)
    return jsonify({'access_token': access_token})

# Декоратор для защиты маршрутов
def require_role(role: str):
    def decorator(fn):
        @wraps(fn)
        @jwt_required()
        def wrapper(*args, **kwargs):
            claims = get_jwt()
            if claims.get('role') != role:
                return jsonify({'error': 'Forbidden'}), 403
            return fn(*args, **kwargs)
        return wrapper
    return decorator

Загрузка файлов

import boto3
from werkzeug.utils import secure_filename
from PIL import Image
import io

ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'webp'}
s3 = boto3.client('s3')

@bp.post('/upload')
@require_auth
def upload_file():
    if 'file' not in request.files:
        return jsonify({'error': 'No file provided'}), 400

    file = request.files['file']
    ext = file.filename.rsplit('.', 1)[-1].lower()

    if ext not in ALLOWED_EXTENSIONS:
        return jsonify({'error': 'File type not allowed'}), 400

    img = Image.open(file.stream)
    img.thumbnail((1920, 1080), Image.LANCZOS)

    buffer = io.BytesIO()
    img.save(buffer, format=img.format or 'JPEG', quality=85)
    buffer.seek(0)

    filename = f"uploads/{datetime.utcnow().strftime('%Y/%m')}/{secure_filename(file.filename)}"
    s3.upload_fileobj(buffer, current_app.config['S3_BUCKET'], filename,
                      ExtraArgs={'ContentType': file.content_type})

    return jsonify({'url': f"https://{current_app.config['CDN_HOST']}/{filename}"})

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

@app.errorhandler(404)
def not_found(e):
    return jsonify({'error': 'Not found'}), 404

@app.errorhandler(422)
def unprocessable(e):
    return jsonify({'error': 'Unprocessable entity'}), 422

@app.errorhandler(Exception)
def handle_exception(e):
    if isinstance(e, HTTPException):
        return jsonify({'error': e.description}), e.code
    # Логируем и возвращаем 500
    current_app.logger.exception(e)
    return jsonify({'error': 'Internal server error'}), 500

Развёртывание

Flask запускается через Gunicorn:

gunicorn "app:create_app('production')" \
  --workers 4 \
  --worker-class gevent \
  --bind 0.0.0.0:5000 \
  --timeout 30

Для async-операций — gevent worker или переход на Flask 3.x с async views.

Сроки разработки

  • Scaffold + конфигурация + БД — 2–4 дня
  • Модели + миграции — 3–5 дней
  • API endpoints + auth — 1–2 недели
  • Тесты (pytest + flask test client) — 3–5 дней
  • Интеграции — по задаче

Небольшой или средний API для сайта: 3–7 недель. Flask проигрывает FastAPI в автодокументации и Django в набортных инструментах, но выигрывает в простоте и предсказуемости для проектов, где не нужно ни того, ни другого.