# Рекомендации по миграции бэкенда на Node.js (Express + TypeORM) ## Обзор текущей архитектуры ### Текущий стек технологий - **Backend**: FastAPI 0.104.1 (Python 3.11) - **База данных**: PostgreSQL 15 + SQLAlchemy 2.0 + Alembic - **Аутентификация**: JWT (access/refresh токены) + bcrypt - **Frontend**: Vanilla JavaScript SPA с динамической загрузкой модулей - **Инфраструктура**: Docker Compose, Nginx, Alpine Linux - **Документация API**: Автоматическая через FastAPI (Swagger/ReDoc) ### Структура бэкенда ``` backend/ ├── app/ │ ├── __init__.py │ ├── main.py # Точка входа, настройка FastAPI │ ├── auth.py # JWT аутентификация, хеширование паролей │ ├── database.py # Подключение к БД, сессии SQLAlchemy │ ├── models.py # SQLAlchemy ORM модели (4 сущности) │ ├── schemas.py # Pydantic схемы валидации (11 классов) │ ├── crud.py # CRUD операции (135 строк бизнес-логики) │ ├── middleware/ │ │ └── cors.py # CORS настройки │ └── routes/ │ ├── auth.py # Аутентификация (регистрация, вход, выход, me) │ ├── courses.py # Курсы (список, детали) │ ├── favorites.py # Избранные курсы (получение, добавление, удаление) │ ├── progress.py # Прогресс обучения (CRUD) │ └── stats.py # Статистика системы ├── alembic/ # Миграции базы данных ├── init.sql # Инициализация БД с тестовыми данными ├── requirements.txt # Python зависимости (13 пакетов) └── Dockerfile # Docker образ Python 3.11 ``` ### Ключевые сущности данных 1. **User** (`users`): пользователи системы (email, хеш пароля, активность) 2. **Course** (`courses`): обучающие курсы (название, описание, длительность, уровень) 3. **FavoriteCourse** (`favorite_courses`): связь многие-ко-многим пользователей и курсов (избранное) 4. **Progress** (`progress`): прогресс обучения по курсам (пройдено уроков, всего уроков) ### API Endpoints | Метод | Путь | Описание | Аутентификация | |-------|------|----------|----------------| | POST | `/api/auth/register` | Регистрация пользователя | Нет | | POST | `/api/auth/login` | Вход, получение JWT токенов | Нет | | POST | `/api/auth/logout` | Выход (символический) | Да | | GET | `/api/auth/me` | Информация о текущем пользователе | Да | | GET | `/api/courses` | Список курсов с фильтрацией по уровню | Нет | | GET | `/api/courses/{id}` | Детали курса | Нет | | GET | `/api/favorites` | Избранные курсы пользователя | Да | | POST | `/api/favorites` | Добавить курс в избранное | Да | | DELETE | `/api/favorites/{id}` | Удалить курс из избранного | Да | | GET | `/api/progress` | Прогресс по всем курсам пользователя | Да | | GET | `/api/progress/{id}` | Прогресс по конкретному курсу | Да | | POST | `/api/progress/{id}` | Обновить прогресс по курсу | Да | | DELETE | `/api/progress/{id}` | Удалить прогресс по курсу | Да | | GET | `/api/stats` | Статистика системы (пользователи, курсы) | Нет | ### Аутентификация и авторизация - **JWT токены**: access token (30 мин) + refresh token (7 дней) - **Хранение токенов**: фронтенд сохраняет access token в localStorage - **Передача токенов**: заголовок `Authorization: Bearer ` - **Хеширование паролей**: bcrypt через библиотеку passlib - **Защита эндпоинтов**: зависимость `get_current_user` в FastAPI ### Инфраструктура - **Docker Compose**: 3 сервиса (postgres, backend, frontend) - **Сеть**: изолированная сеть `auth_network` - **База данных**: персистентный volume `postgres_data` - **Проксирование**: Nginx раздает статику и проксирует API запросы к бэкенду - **Hot reload**: для разработки через `uvicorn --reload` ## Анализ для миграции на Node.js ### Преимущества текущей архитектуры 1. **Чистая слоистая архитектура**: модели, схемы, CRUD, маршруты 2. **Автоматическая валидация**: Pydantic схемы с детальными правилами 3. **Автоматическая документация**: Swagger UI через FastAPI 4. **Асинхронная обработка**: поддержка async/await в FastAPI 5. **Типизация**: Python type hints + Pydantic ### Сложности миграции 1. **Потеря автоматической документации** (Swagger) - потребуется Swagger UI для Express 2. **Потеря автоматической валидации** - необходимо реализовать валидацию вручную 3. **Различия в ORM**: SQLAlchemy vs TypeORM (разные подходы к отношениям, миграциям) 4. **Асинхронность**: FastAPI использует настоящую асинхронность, Express - callback/promise 5. **Система зависимостей**: FastAPI Dependency Injection vs Express middleware ### Совместимость с фронтендом - **API интерфейс должен остаться неизменным** для минимизации изменений фронтенда - **Структура ответов**: все эндпоинты возвращают `SuccessResponse` или массивы - **Коды ошибок**: HTTP статусы и структура `ErrorResponse` должны сохраниться - **Аутентификация**: тот же механизм JWT с access token в localStorage ## Рекомендуемый стек Node.js ### Основные технологии | Компонент | Технология | Обоснование | |-----------|------------|-------------| | **Фреймворк** | Express 4.x | Минималистичный, близкий к текущей архитектуре, большое сообщество | | **ORM** | TypeORM 0.3.x | TypeScript-ориентированный, поддержка PostgreSQL, миграции, ActiveRecord/DataMapper паттерны | | **Валидация** | class-validator + class-transformer | Аналог Pydantic, декларативная валидация через декораторы | | **Аутентификация** | jsonwebtoken + bcrypt | Стандартные библиотеки для JWT и хеширования паролей | | **Конфигурация** | dotenv + config | Управление переменными окружения в разных средах | | **Документация API** | swagger-jsdoc + swagger-ui-express | Автоматическая генерация Swagger документации | | **Миграции** | TypeORM migrations | Встроенная система миграций TypeORM | | **Логирование** | winston + morgan | Структурированное логирование + HTTP логгирование | | **Тестирование** | Jest + Supertest | Unit и интеграционные тесты | ### Альтернативы - **NestJS**: более структурированный, но более сложный для миграции - **Prisma**: современный ORM, но менее зрелый для сложных отношений - **Fastify**: более производительный, но менее распространенный ## Пошаговый план миграции ### Этап 1: Подготовка и настройка окружения 1. **Создание нового бэкенда на Node.js** в отдельной директории (например, `backend-node`) 2. **Инициализация проекта**: `npm init`, установка зависимостей 3. **Настройка TypeScript**: tsconfig, структура проекта 4. **Конфигурация Docker**: создание Dockerfile для Node.js 5. **Обновление docker-compose.yml**: замена сервиса backend ### Этап 2: Настройка базы данных и TypeORM 1. **Определение сущностей TypeORM**: преобразование SQLAlchemy моделей в TypeORM Entity 2. **Настройка подключения к БД**: использование того же PostgreSQL 3. **Создание миграций**: перенос init.sql в миграции TypeORM 4. **Тестирование подключения**: проверка работы с существующей БД ### Этап 3: Реализация базовой инфраструктуры 1. **Настройка Express**: middleware (CORS, парсинг JSON, логирование) 2. **Реализация структуры ответов**: SuccessResponse, ErrorResponse 3. **Настройка Swagger документации**: описание всех эндпоинтов 4. **Создание системы конфигурации**: переменные окружения ### Этап 4: Реализация аутентификации 1. **Создание JWT сервиса**: генерация, верификация токенов 2. **Реализация хеширования паролей**: bcrypt 3. **Создание middleware аутентификации**: аналог `get_current_user` 4. **Тестирование аутентификации**: регистрация, вход, защита эндпоинтов ### Этап 5: Реализация бизнес-логики 1. **Репозитории/сервисы**: перенос CRUD операций из `crud.py` 2. **Контроллеры**: реализация всех эндпоинтов из routes 3. **Валидация запросов**: class-validator для DTO 4. **Обработка ошибок**: централизованный обработчик ошибок ### Этап 6: Интеграция и тестирование 1. **Поэтапная замена эндпоинтов**: можно запустить оба бэкенда параллельно 2. **Тестирование фронтенда**: проверка всех сценариев 3. **Нагрузочное тестирование**: сравнение производительности 4. **Миграция данных**: перенос существующих данных (если требуется) ### Этап 7: Деплой и мониторинг 1. **Обновление Docker Compose**: замена образа бэкенда 2. **Настройка мониторинга**: логи, метрики, health checks 3. **Резервное копирование**: стратегия бэкапов PostgreSQL 4. **Документация**: обновление ARCHITECTURE.md ## Структура проекта Node.js ``` backend-node/ ├── src/ │ ├── index.ts # Точка входа │ ├── app.ts # Настройка Express │ ├── config/ │ │ ├── index.ts # Конфигурация приложения │ │ ├── database.ts # Конфигурация БД │ │ └── jwt.ts # Конфигурация JWT │ ├── entities/ # TypeORM сущности │ │ ├── User.ts │ │ ├── Course.ts │ │ ├── FavoriteCourse.ts │ │ └── Progress.ts │ ├── migrations/ # Миграции TypeORM │ │ ├── 0001-initial-schema.ts │ │ └── 0002-seed-data.ts │ ├── middlewares/ │ │ ├── auth.middleware.ts # Аутентификация │ │ ├── error.middleware.ts # Обработка ошибок │ │ ├── validation.middleware.ts # Валидация DTO │ │ └── cors.middleware.ts # CORS │ ├── controllers/ # Контроллеры (маршруты) │ │ ├── auth.controller.ts │ │ ├── courses.controller.ts │ │ ├── favorites.controller.ts │ │ ├── progress.controller.ts │ │ └── stats.controller.ts │ ├── services/ # Бизнес-логика │ │ ├── auth.service.ts │ │ ├── courses.service.ts │ │ ├── favorites.service.ts │ │ ├── progress.service.ts │ │ └── stats.service.ts │ ├── repositories/ # Работа с БД (опционально) │ │ ├── user.repository.ts │ │ ├── course.repository.ts │ │ └── base.repository.ts │ ├── dtos/ # Data Transfer Objects │ │ ├── auth.dto.ts │ │ ├── courses.dto.ts │ │ ├── favorites.dto.ts │ │ ├── progress.dto.ts │ │ └── common.dto.ts │ ├── utils/ │ │ ├── api-response.ts # SuccessResponse, ErrorResponse │ │ ├── logger.ts # Winston логгер │ │ └── helpers.ts # Вспомогательные функции │ └── types/ # TypeScript типы │ └── index.ts ├── tests/ # Тесты │ ├── unit/ │ └── integration/ ├── docker/ # Docker конфигурация │ └── Dockerfile ├── .env.example # Пример переменных окружения ├── package.json ├── tsconfig.json ├── ormconfig.ts # Конфигурация TypeORM └── README.md ``` ## Примеры кода ### Сущность User (TypeORM) ```typescript import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, OneToMany } from 'typeorm'; import { FavoriteCourse } from './FavoriteCourse'; import { Progress } from './Progress'; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column({ type: 'varchar', length: 255, unique: true }) email: string; @Column({ type: 'varchar', length: 255 }) hashedPassword: string; @CreateDateColumn({ type: 'timestamptz' }) createdAt: Date; @Column({ type: 'boolean', default: true }) isActive: boolean; @OneToMany(() => FavoriteCourse, favorite => favorite.user) favoriteCourses: FavoriteCourse[]; @OneToMany(() => Progress, progress => progress.user) progress: Progress[]; } ``` ### DTO с валидацией (class-validator) ```typescript import { IsEmail, IsString, MinLength, MaxLength, Matches } from 'class-validator'; export class UserCreateDto { @IsEmail() email: string; @IsString() @MinLength(8) @MaxLength(100) @Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, { message: 'Password must contain at least one uppercase letter, one lowercase letter and one number' }) password: string; } export class UserLoginDto { @IsEmail() email: string; @IsString() password: string; } ``` ### Контроллер аутентификации ```typescript import { Request, Response, NextFunction } from 'express'; import { AuthService } from '../services/auth.service'; import { UserCreateDto, UserLoginDto } from '../dtos/auth.dto'; import { successResponse, errorResponse } from '../utils/api-response'; import { validate } from '../middlewares/validation.middleware'; export class AuthController { private authService: AuthService; constructor() { this.authService = new AuthService(); } register = [ validate(UserCreateDto), async (req: Request, res: Response, next: NextFunction) => { try { const userData: UserCreateDto = req.body; await this.authService.register(userData); return successResponse(res, { message: 'User registered successfully', data: { email: userData.email } }); } catch (error) { next(error); } } ]; login = [ validate(UserLoginDto), async (req: Request, res: Response, next: NextFunction) => { try { const credentials: UserLoginDto = req.body; const result = await this.authService.login(credentials); return successResponse(res, { message: 'Login successful', data: result }); } catch (error) { next(error); } } ]; getMe = [ // authMiddleware применяется в маршрутах async (req: Request, res: Response, next: NextFunction) => { try { const user = (req as any).user; // После authMiddleware return successResponse(res, { data: { email: user.email, id: user.id, createdAt: user.createdAt, isActive: user.isActive } }); } catch (error) { next(error); } } ]; } ``` ### Сервис аутентификации ```typescript import { Repository } from 'typeorm'; import { AppDataSource } from '../config/database'; import { User } from '../entities/User'; import { UserCreateDto, UserLoginDto } from '../dtos/auth.dto'; import { JwtService } from './jwt.service'; import { PasswordService } from './password.service'; import { BadRequestError, UnauthorizedError } from '../utils/errors'; export class AuthService { private userRepository: Repository; private jwtService: JwtService; private passwordService: PasswordService; constructor() { this.userRepository = AppDataSource.getRepository(User); this.jwtService = new JwtService(); this.passwordService = new PasswordService(); } async register(userData: UserCreateDto): Promise { // Проверка существования пользователя const existingUser = await this.userRepository.findOne({ where: { email: userData.email } }); if (existingUser) { throw new BadRequestError('Email already registered'); } // Хеширование пароля const hashedPassword = await this.passwordService.hashPassword(userData.password); // Создание пользователя const user = this.userRepository.create({ email: userData.email, hashedPassword }); await this.userRepository.save(user); } async login(credentials: UserLoginDto): Promise<{ accessToken: string; refreshToken: string; user: any }> { // Поиск пользователя const user = await this.userRepository.findOne({ where: { email: credentials.email } }); if (!user) { throw new UnauthorizedError('Incorrect email or password'); } // Проверка пароля const isValidPassword = await this.passwordService.verifyPassword( credentials.password, user.hashedPassword ); if (!isValidPassword) { throw new UnauthorizedError('Incorrect email or password'); } // Генерация токенов const accessToken = this.jwtService.generateAccessToken(user.email); const refreshToken = this.jwtService.generateRefreshToken(user.email); return { accessToken, refreshToken, user: { email: user.email, id: user.id, createdAt: user.createdAt } }; } } ``` ## Миграция данных ### Стратегии миграции 1. **Постепенная миграция**: запуск Node.js бэкенда параллельно с Python, переключение по эндпоинтам 2. **Полная замена**: остановка Python бэкенда, запуск Node.js с переносом данных 3. **Канареечное развертывание**: направление части трафика на Node.js бэкенд ### Перенос существующих данных 1. **Схема БД остается той же**, только ORM меняется 2. **Данные пользователей**: пароли захешированы bcrypt - совместимы 3. **Курсы и связи**: прямокопирование таблиц 4. **Миграционный скрипт**: можно написать на TypeScript с использованием TypeORM ### Пример миграционного скрипта ```typescript // migrate-data.ts import { createConnection } from 'typeorm'; import { User, Course, FavoriteCourse, Progress } from './src/entities'; async function migrate() { // Подключение к существующей БД const connection = await createConnection({ type: 'postgres', host: 'localhost', port: 5432, username: 'auth_user', password: 'auth_password', database: 'auth_learning', entities: [User, Course, FavoriteCourse, Progress], synchronize: false // Не создавать таблицы автоматически }); // Проверка данных const userCount = await connection.manager.count(User); console.log(`Users in database: ${userCount}`); // Данные уже существуют, можно закрыть соединение await connection.close(); } ``` ## Инфраструктура ### Dockerfile для Node.js ```dockerfile FROM node:18-alpine WORKDIR /app # Установка зависимостей COPY package*.json ./ RUN npm ci --only=production # Копирование исходного кода COPY . . # Сборка TypeScript RUN npm run build # Создание непривилегированного пользователя RUN addgroup -g 1000 -S nodejs && \ adduser -S nodejs -u 1000 -G nodejs USER nodejs EXPOSE 3000 CMD ["node", "dist/index.js"] ``` ### Обновленный docker-compose.yml ```yaml version: '3.8' services: postgres: # Остается без изменений backend: build: ./backend-node container_name: auth_learning_backend_node environment: NODE_ENV: production DATABASE_URL: postgresql://${POSTGRES_USER:-auth_user}:${POSTGRES_PASSWORD:-auth_password}@postgres:5432/${POSTGRES_DB:-auth_learning} JWT_SECRET: ${JWT_SECRET:-your-jwt-secret-change-in-production} ACCESS_TOKEN_EXPIRE_MINUTES: ${ACCESS_TOKEN_EXPIRE_MINUTES:-30} REFRESH_TOKEN_EXPIRE_DAYS: ${REFRESH_TOKEN_EXPIRE_DAYS:-7} ports: - "3000:3000" depends_on: postgres: condition: service_healthy networks: - auth_network # Для разработки с hot-reload: # command: npm run dev frontend: # Остается без изменений, но нужно обновить nginx.conf для проксирования на порт 3000 networks: auth_network: driver: bridge volumes: postgres_data: ``` ### Обновление nginx.conf ```nginx location /api/ { proxy_pass http://backend:3000; # Изменен порт с 8000 на 3000 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } ``` ## Тестирование ### Стратегия тестирования 1. **Юнит-тесты**: сервисы, утилиты (Jest) 2. **Интеграционные тесты**: API эндпоинты (Supertest) 3. **E2E тесты**: полный поток с фронтендом (Cypress/Playwright) 4. **Нагрузочное тестирование**: сравнение производительности с FastAPI ### Пример теста API ```typescript import request from 'supertest'; import { app } from '../src/app'; describe('Auth API', () => { describe('POST /api/auth/register', () => { it('should register a new user', async () => { const response = await request(app) .post('/api/auth/register') .send({ email: 'test@example.com', password: 'Test1234' }); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.data.email).toBe('test@example.com'); }); }); }); ``` ## Риски и их минимизация ### Технические риски | Риск | Вероятность | Влияние | Стратегия минимизации | |------|-------------|---------|----------------------| | **Несовместимость API** | Низкая | Высокое | Точное копирование структур ответов, тестирование каждого эндпоинта | | **Производительность** | Средняя | Среднее | Нагрузочное тестирование, оптимизация запросов к БД | | **Потеря данных** | Низкая | Критическое | Резервное копирование БД перед миграцией, поэтапное переключение | | **Ошибки аутентификации** | Средняя | Высокое | Тщательное тестирование JWT, проверка совместимости bcrypt хешей | ### Организационные риски 1. **Время миграции**: оценка 2-4 недели для одного разработчика 2. **Обучение команды**: если команда не знает TypeScript/Node.js 3. **Поддержка двух кодовых баз**: в период параллельной работы ## Преимущества перехода на Node.js ### Технические преимущества 1. **Единый язык**: JavaScript/TypeScript на фронтенде и бэкенде 2. **Большая экосистема**: npm с огромным количеством пакетов 3. **Высокая производительность**: Node.js хорошо справляется с I/O операциями 4. **Меньшее потребление памяти**: по сравнению с Python + FastAPI ### Бизнес-преимущества 1. **Упрощение найма**: больше разработчиков знают JavaScript чем Python 2. **Более быстрое развитие**: возможность переиспользования кода между фронтендом и бэкендом 3. **Лучшая масштабируемость**: Node.js лучше подходит для микросервисной архитектуры 4. **Снижение затрат на инфраструктуру**: более эффективное использование ресурсов ## Рекомендации по внедрению ### Приоритетность задач 1. **Высокий приоритет**: - Настройка базовой инфраструктуры (Express, TypeORM, Docker) - Реализация аутентификации (JWT, bcrypt) - Миграция основных сущности (User, Course) 2. **Средний приоритет**: - Реализация оставшихся эндпоинтов - Настройка Swagger документации - Интеграционное тестирование 3. **Низкий приоритет**: - Оптимизация производительности - Расширенные функции (пагинация, сортировка) - Мониторинг и логирование ### Контрольные точки 1. **Неделя 1**: Базовая инфраструктура, подключение к БД, простые CRUD операции 2. **Неделя 2**: Аутентификация, защита эндпоинтов, основные маршруты 3. **Неделя 3**: Тестирование, интеграция с фронтендом, документация 4. **Неделя 4**: Постепенное переключение, мониторинг, оптимизация ### Критерии успеха 1. **Все существующие API эндпоинты работают** без изменений фронтенда 2. **Производительность не хуже** FastAPI версии 3. **100% покрытие тестами** критической функциональности 4. **Полная документация** API и архитектуры ## Заключение Миграция бэкенда с FastAPI на Express + TypeORM является технически выполнимой задачей с четкими преимуществами для долгосрочного развития проекта. Рекомендуемый подход - поэтапная миграция с сохранением обратной совместимости API. Ключевые факторы успеха: 1. **Сохранение API интерфейса** для минимизации изменений фронтенда 2. **Тщательное тестирование** каждого эндпоинта 3. **Поэтапное развертывание** с возможностью отката 4. **Документирование** всех изменений и решений Предложенный стек (Express + TypeORM) обеспечивает хороший баланс между производительностью, поддерживаемостью и скоростью разработки, делая его подходящим выбором для данного проекта.