- Home »

Создание приложения 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, а потом масштабируй по мере роста нагрузки.
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.