Home » Создание приложения To-Do с Django и React
Создание приложения To-Do с Django и React

Создание приложения To-Do с Django и React

Создание full-stack приложения с Django и React — это классическая задача, которая рано или поздно встаёт перед каждым разработчиком, который хочет понять, как работает современная веб-разработка. To-Do приложение — идеальный пример для изучения архитектуры REST API, взаимодействия backend и frontend, а также развёртывания в продакшн. Если ты админишь сервера и хочешь быстро понять, как поднять такой стек, настроить окружение и избежать типичных граблей — эта статья для тебя.

Мы разберём не только техническую реализацию, но и практические моменты развёртывания, оптимизации и мониторинга. Покажу, как правильно настроить nginx, gunicorn, и что делать с CORS, который всегда становится головной болью. Плюс рассмотрим альтернативы и сравним производительность разных подходов.

Как это работает: архитектура и принципы

Связка Django + React работает по принципу разделения ответственности: Django выступает как API-сервер, предоставляющий данные через REST endpoints, а React обрабатывает всю клиентскую логику и UI. Это классический подход API-first, который позволяет масштабировать компоненты независимо.

Архитектура выглядит так:

  • Backend (Django + DRF) — обрабатывает бизнес-логику, работает с базой данных, предоставляет REST API
  • Frontend (React) — отвечает за интерфейс пользователя, делает HTTP-запросы к API
  • База данных — PostgreSQL (рекомендую) или SQLite для разработки
  • Reverse proxy — nginx для статики и проксирования запросов

Основные преимущества такого подхода:

  • Независимое развёртывание и масштабирование
  • Возможность использовать API для мобильных приложений
  • Чёткое разделение команд frontend и backend разработчиков
  • Простота тестирования каждого компонента

Быстрая настройка окружения

Для начала нужен сервер. Рекомендую взять VPS с минимум 2GB RAM и SSD. Для продакшн-нагрузок лучше рассмотреть выделенный сервер.

Устанавливаем зависимости на Ubuntu/Debian:


# Обновляем систему
sudo apt update && sudo apt upgrade -y

# Устанавливаем Python, Node.js и необходимые пакеты
sudo apt install python3 python3-pip python3-venv nodejs npm postgresql postgresql-contrib nginx git -y

# Проверяем версии
python3 --version
node --version
npm --version

Создаём структуру проекта:


mkdir todo-app && cd todo-app
mkdir backend frontend

Настройка Django backend

Поднимаем Django с Django REST Framework:


cd backend
python3 -m venv venv
source venv/bin/activate

# Устанавливаем зависимости
pip install django djangorestframework django-cors-headers psycopg2-binary gunicorn

# Создаём проект
django-admin startproject todoapi .
cd todoapi
python manage.py startapp todos

Настраиваем settings.py:


# settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'corsheaders',
    'todos',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# CORS настройки
CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",  # React dev server
    "http://127.0.0.1:3000",
]

# REST Framework настройки
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ],
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 20
}

# База данных
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'todoapp',
        'USER': 'todouser',
        'PASSWORD': 'password',
        'HOST': 'localhost',
        'PORT': '5432',
    }
}

Создаём модель для задач:


# todos/models.py
from django.db import models

class Todo(models.Model):
    PRIORITY_CHOICES = [
        ('low', 'Low'),
        ('medium', 'Medium'),
        ('high', 'High'),
    ]
    
    title = models.CharField(max_length=200)
    description = models.TextField(blank=True)
    completed = models.BooleanField(default=False)
    priority = models.CharField(max_length=10, choices=PRIORITY_CHOICES, default='medium')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        ordering = ['-created_at']
    
    def __str__(self):
        return self.title

Сериализатор и views:


# todos/serializers.py
from rest_framework import serializers
from .models import Todo

class TodoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Todo
        fields = '__all__'

# todos/views.py
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import Todo
from .serializers import TodoSerializer

class TodoViewSet(viewsets.ModelViewSet):
    queryset = Todo.objects.all()
    serializer_class = TodoSerializer
    
    @action(detail=True, methods=['post'])
    def toggle_complete(self, request, pk=None):
        todo = self.get_object()
        todo.completed = not todo.completed
        todo.save()
        return Response({'status': 'completed toggled'})
    
    @action(detail=False)
    def completed(self, request):
        completed_todos = Todo.objects.filter(completed=True)
        serializer = self.get_serializer(completed_todos, many=True)
        return Response(serializer.data)

URL-конфигурация:


# todos/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import TodoViewSet

router = DefaultRouter()
router.register(r'todos', TodoViewSet)

urlpatterns = [
    path('api/', include(router.urls)),
]

# todoapi/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('todos.urls')),
]

Настройка React frontend

Создаём React приложение:


cd ../frontend
npx create-react-app todo-frontend
cd todo-frontend
npm install axios react-router-dom

Создаём API сервис:


// src/services/api.js
import axios from 'axios';

const API_URL = 'http://localhost:8000/api';

const api = axios.create({
  baseURL: API_URL,
  headers: {
    'Content-Type': 'application/json',
  },
});

export const todoAPI = {
  getTodos: () => api.get('/todos/'),
  createTodo: (data) => api.post('/todos/', data),
  updateTodo: (id, data) => api.put(`/todos/${id}/`, data),
  deleteTodo: (id) => api.delete(`/todos/${id}/`),
  toggleComplete: (id) => api.post(`/todos/${id}/toggle_complete/`),
};

export default api;

Основной компонент приложения:


// src/components/TodoApp.js
import React, { useState, useEffect } from 'react';
import { todoAPI } from '../services/api';
import TodoList from './TodoList';
import TodoForm from './TodoForm';

const TodoApp = () => {
  const [todos, setTodos] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchTodos();
  }, []);

  const fetchTodos = async () => {
    try {
      const response = await todoAPI.getTodos();
      setTodos(response.data.results || response.data);
      setLoading(false);
    } catch (err) {
      setError('Failed to fetch todos');
      setLoading(false);
    }
  };

  const createTodo = async (todoData) => {
    try {
      const response = await todoAPI.createTodo(todoData);
      setTodos([response.data, ...todos]);
    } catch (err) {
      setError('Failed to create todo');
    }
  };

  const updateTodo = async (id, todoData) => {
    try {
      const response = await todoAPI.updateTodo(id, todoData);
      setTodos(todos.map(todo => 
        todo.id === id ? response.data : todo
      ));
    } catch (err) {
      setError('Failed to update todo');
    }
  };

  const deleteTodo = async (id) => {
    try {
      await todoAPI.deleteTodo(id);
      setTodos(todos.filter(todo => todo.id !== id));
    } catch (err) {
      setError('Failed to delete todo');
    }
  };

  const toggleComplete = async (id) => {
    try {
      await todoAPI.toggleComplete(id);
      setTodos(todos.map(todo =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      ));
    } catch (err) {
      setError('Failed to toggle todo');
    }
  };

  if (loading) return 
Loading...
; if (error) return
Error: {error}
; return (

Todo App

); }; export default TodoApp;

Развёртывание в продакшн

Настраиваем PostgreSQL:


sudo -u postgres psql
CREATE DATABASE todoapp;
CREATE USER todouser WITH PASSWORD 'password';
ALTER ROLE todouser SET client_encoding TO 'utf8';
ALTER ROLE todouser SET default_transaction_isolation TO 'read committed';
ALTER ROLE todouser SET timezone TO 'UTC';
GRANT ALL PRIVILEGES ON DATABASE todoapp TO todouser;
\q

Выполняем миграции:


cd backend
source venv/bin/activate
python manage.py makemigrations
python manage.py migrate
python manage.py collectstatic

Создаём systemd сервис для gunicorn:


# /etc/systemd/system/todoapi.service
[Unit]
Description=Todo API gunicorn daemon
After=network.target

[Service]
User=www-data
Group=www-data
WorkingDirectory=/path/to/todo-app/backend
ExecStart=/path/to/todo-app/backend/venv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/path/to/todo-app/backend/todoapi.sock todoapi.wsgi:application

[Install]
WantedBy=multi-user.target

Настраиваем nginx:


# /etc/nginx/sites-available/todoapp
server {
    listen 80;
    server_name your-domain.com;
    
    location /api/ {
        include proxy_params;
        proxy_pass http://unix:/path/to/todo-app/backend/todoapi.sock;
    }
    
    location /admin/ {
        include proxy_params;
        proxy_pass http://unix:/path/to/todo-app/backend/todoapi.sock;
    }
    
    location /static/ {
        alias /path/to/todo-app/backend/static/;
    }
    
    location / {
        root /path/to/todo-app/frontend/todo-frontend/build;
        index index.html;
        try_files $uri $uri/ /index.html;
    }
}

Запускаем сервисы:


sudo systemctl daemon-reload
sudo systemctl start todoapi
sudo systemctl enable todoapi
sudo systemctl restart nginx

Сравнение альтернативных решений

Решение Преимущества Недостатки Производительность
Django + React Мощный ORM, отличная документация, большая экосистема Медленнее FastAPI, больше boilerplate Хорошая для средних нагрузок
FastAPI + React Высокая производительность, автодокументация, async из коробки Меньше готовых решений, молодая экосистема Отличная для высоких нагрузок
Express.js + React Один язык для всего стека, быстрая разработка Слабее типизация, менее структурирован Средняя, зависит от оптимизации
Django + Next.js SSR из коробки, отличная оптимизация Сложнее настройка, больше зависимостей Отличная для SEO

Оптимизация и мониторинг

Для мониторинга производительности добавляем middleware:


# utils/middleware.py
import time
import logging

logger = logging.getLogger(__name__)

class ResponseTimeMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        start_time = time.time()
        response = self.get_response(request)
        duration = time.time() - start_time
        
        if duration > 1:  # Логируем медленные запросы
            logger.warning(f"Slow request: {request.path} took {duration:.2f}s")
        
        response['X-Response-Time'] = str(duration)
        return response

Кеширование с Redis:


# settings.py
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        }
    }
}

# views.py
from django.core.cache import cache
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page

@method_decorator(cache_page(60 * 5), name='list')  # 5 минут кеш
class TodoViewSet(viewsets.ModelViewSet):
    # ...

Автоматизация развёртывания

Создаём скрипт для автоматического развёртывания:


#!/bin/bash
# deploy.sh

set -e

echo "Starting deployment..."

# Обновляем код
git pull origin main

# Backend
cd backend
source venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --noinput

# Frontend
cd ../frontend/todo-frontend
npm install
npm run build

# Перезапускаем сервисы
sudo systemctl restart todoapi
sudo systemctl reload nginx

echo "Deployment completed successfully!"

Для непрерывной интеграции создаём GitHub Action:


# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Deploy to server
      uses: appleboy/ssh-action@v0.1.4
      with:
        host: ${{ secrets.HOST }}
        username: ${{ secrets.USERNAME }}
        key: ${{ secrets.SSH_KEY }}
        script: |
          cd /path/to/todo-app
          ./deploy.sh

Интересные возможности и нестандартные применения

Этот стек открывает несколько интересных возможностей:

  • Real-time уведомления с Django Channels и WebSocket
  • Микросервисная архитектура — можно разделить на отдельные сервисы по доменам
  • GraphQL API вместо REST с помощью Graphene-Django
  • Контейнеризация с Docker для упрощения развёртывания
  • Автоматическое тестирование с pytest и Jest

Необычные кейсы использования:

  • Система управления серверами через веб-интерфейс
  • Дашборд для мониторинга логов и метрик
  • Интерфейс для управления CI/CD пайплайнами
  • Система управления конфигурациями

Интеграция с другими инструментами

Для расширения функциональности можно добавить:

  • Celery для фоновых задач
  • Elasticsearch для полнотекстового поиска
  • Prometheus + Grafana для мониторинга
  • Sentry для отслеживания ошибок
  • JWT аутентификация с помощью django-rest-auth

Пример интеграции с Celery для отправки уведомлений:


# tasks.py
from celery import shared_task
from django.core.mail import send_mail

@shared_task
def send_reminder_email(todo_id):
    todo = Todo.objects.get(id=todo_id)
    send_mail(
        'Reminder: Task Due',
        f'Your task "{todo.title}" is due soon!',
        'from@example.com',
        ['user@example.com'],
    )

Заключение и рекомендации

Связка Django + React остаётся одним из самых надёжных решений для создания современных веб-приложений. Она подходит для проектов любого масштаба — от простых CRUD-приложений до сложных корпоративных систем.

Когда использовать:

  • Нужна быстрая разработка с богатой экосистемой
  • Планируется сложная бизнес-логика на backend
  • Требуется админ-панель из коробки
  • Команда знакома с Python и JavaScript

Когда рассмотреть альтернативы:

  • Критична производительность (FastAPI + React)
  • Нужен SSR для SEO (Next.js)
  • Микросервисная архитектура (отдельные API на разных технологиях)

Для продакшн-развёртывания обязательно настрой мониторинг, логирование и автоматическое резервное копирование. Используй официальную документацию Django и React для углубленного изучения.

Помни: хорошая архитектура важнее модных технологий. Начни с простого MVP, а потом масштабируй по мере роста нагрузки.


В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.

Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.

Leave a reply

Your email address will not be published. Required fields are marked