Fullstack-разработка: Django REST Framework и React

Этот курс посвящен созданию современных веб-приложений, объединяющих мощный бэкенд на Python и динамичный фронтенд на JavaScript. Вы научитесь проектировать API, управлять состоянием приложения и настраивать безопасное взаимодействие между клиентом и сервером.

1. Создание надежного API с помощью Django и Django REST Framework

Создание надежного API с помощью Django и Django REST Framework

Добро пожаловать в курс «Fullstack-разработка: Django REST Framework и React». В этой серии статей мы пройдем путь от создания пустого проекта до развертывания полноценного веб-приложения, где мощный бэкенд на Python встречается с динамичным фронтендом на JavaScript.

В первой статье мы сосредоточимся на фундаменте нашего приложения — серверной части (Backend). Мы разберем, как спроектировать и реализовать API (Application Programming Interface), который будет служить надежным источником данных для нашего будущего интерфейса на React.

Зачем нам связка Django и React?

Традиционно Django использовался как монолитный фреймворк: он обрабатывал запросы, обращался к базе данных и сам генерировал HTML-страницы, которые отправлялись пользователю. Этот подход называется Server-Side Rendering (SSR).

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

Именно здесь появляется Django REST Framework (DRF). Он превращает Django из генератора HTML в генератора данных (обычно в формате JSON). React запрашивает данные, Django их отдает, а React сам решает, как их отобразить.

!Схема взаимодействия: React отправляет запросы, Django обрабатывает их через DRF и общается с базой данных, возвращая ответ в формате JSON.

Подготовка окружения

Прежде чем писать код, нам нужно подготовить рабочее место. Мы будем использовать Python, поэтому убедитесь, что он установлен на вашем компьютере. Хорошей практикой является использование виртуального окружения, чтобы зависимости проекта не конфликтовали с другими проектами.

Создадим папку проекта и виртуальное окружение:

Активируем его (команда зависит от вашей ОС):

* Windows: venv\Scripts\activate * macOS/Linux: source venv/bin/activate

Теперь установим Django и Django REST Framework:

Создадим сам проект Django и первое приложение, которое мы назовем api:

Обратите внимание на точку в конце первой команды — она создает проект в текущей папке, не создавая лишней вложенности.

Настройка проекта

Теперь нам нужно «сообщить» Django о том, что мы установили DRF и создали приложение api. Откройте файл backend/settings.py и найдите список INSTALLED_APPS. Добавьте туда наши компоненты:

Проектирование модели данных

Любое API начинается с данных. Допустим, мы создаем приложение для ведения заметок. Нам нужна модель Note.

Откройте файл api/models.py и создайте структуру нашей таблицы:

Здесь мы определили три поля:

  • title: заголовок заметки (строка ограниченной длины).
  • content: текст заметки (текстовое поле неограниченной длины).
  • created_at: дата и время создания (заполняется автоматически при создании).
  • После создания модели необходимо создать миграции и применить их к базе данных:

    Магия сериализации

    Это один из самых важных разделов статьи. База данных хранит информацию в своем формате. Python работает с объектами классов. А React (JavaScript) понимает формат JSON (JavaScript Object Notation).

    Сериализация — это процесс перевода данных из сложных структур (например, объектов моделей Django) в формат, который можно легко передать по сети (например, JSON) и сохранить.

    Десериализация — обратный процесс: получение JSON от клиента и превращение его в объект Python для сохранения в базу данных.

    В Django REST Framework за это отвечают Сериализаторы. Создайте файл api/serializers.py:

    Класс ModelSerializer делает за нас огромную работу. Он автоматически анализирует модель Note и создает поля для сериализатора, соответствующие полям модели.

    Создание представлений (Views)

    Теперь, когда мы умеем превращать данные в JSON, нам нужно создать эндпоинты (точки входа), к которым будет обращаться React. В DRF есть множество способов написать представления, но самый быстрый и чистый для стандартных задач — использование Generic Views.

    Мы хотим реализовать две функции:

  • Получить список всех заметок и создать новую.
  • Получить, изменить или удалить конкретную заметку.
  • Откройте api/views.py:

    Посмотрите, насколько лаконичен код. Наследуясь от ListCreateAPIView, мы автоматически получаем логику для обработки GET-запросов (вернуть список) и POST-запросов (создать запись). Аналогично RetrieveUpdateDestroyAPIView берет на себя всю рутину по поиску объекта по ID, его обновлению или удалению.

    Настройка маршрутизации (URLs)

    Последний шаг — связать наши представления с URL-адресами. Сначала создадим файл api/urls.py (его изначально нет):

    Теперь подключим URL-адреса приложения api к главному файлу маршрутизации проекта backend/urls.py:

    Тестирование API

    Запустим сервер разработки:

    Теперь, если вы перейдете в браузере по адресу http://127.0.0.1:8000/api/notes/, вы увидите не просто JSON, а Browsable API (интерактивный API) — мощный инструмент DRF. Это веб-страница, которая позволяет вам:

  • Видеть список заметок (пока он пуст).
  • Видеть форму внизу страницы для отправки POST-запроса.
  • Попробуйте ввести заголовок и текст в форму и нажать кнопку POST. Вы увидите, как новая запись добавилась в базу данных и отобразилась в списке.

    !Интерфейс Browsable API позволяет тестировать запросы прямо в браузере без написания фронтенда.

    Как это работает под капотом?

    Когда вы отправляете данные через форму:

  • Запрос попадает в urls.py, который направляет его в NoteListCreate view.
  • View понимает, что это POST-запрос.
  • Данные передаются в NoteSerializer.
  • Сериализатор проверяет валидность данных (например, не пустой ли заголовок).
  • Если все хорошо, сериализатор вызывает метод save() модели Note.
  • Данные сохраняются в SQL-базе данных.
  • Сериализатор берет созданный объект, превращает его обратно в JSON и возвращает клиенту с кодом 201 Created.
  • CORS: Подготовка к встрече с React

    Есть один нюанс, который часто вызывает головную боль у новичков. Браузеры имеют механизм безопасности, называемый CORS (Cross-Origin Resource Sharing). Он запрещает сайту на одном домене (например, наш React будет на localhost:3000) делать запросы к сайту на другом домене (наш Django на localhost:8000), если сервер явно это не разрешит.

    Чтобы подружить их, установим специальную библиотеку:

    Добавим её в settings.py:

    Теперь наш API полностью готов к приему гостей.

    Заключение

    Мы создали надежный фундамент для нашего приложения. Мы настроили Django, подключили Django REST Framework, описали структуру данных и создали точки входа API, которые умеют читать и записывать информацию. Более того, мы подготовили сервер к безопасной коммуникации с внешним фронтендом.

    В следующей статье мы переключимся на фронтенд-разработку: создадим приложение на React и научим его отображать данные, которые мы только что подготовили.

    2. Архитектура React-приложения: компоненты, хуки и управление состоянием

    Архитектура React-приложения: компоненты, хуки и управление состоянием

    В предыдущей статье мы успешно создали серверную часть нашего приложения на Django. У нас есть база данных, настроенные модели и API, готовое отдавать данные в формате JSON. Теперь пришло время покинуть уютный мир Python и перейти на «клиентскую сторону» — во фронтенд-разработку.

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

    Философия React: Декларативный подход

    Прежде чем создавать файлы, важно понять, чем React отличается от классической разработки на jQuery или чистом JavaScript. В традиционном подходе мы часто используем императивный стиль: мы говорим браузеру, как изменить страницу шаг за шагом (найди элемент, удали класс, добавь текст).

    React использует декларативный подход. Мы описываем, как должен выглядеть интерфейс в зависимости от текущего состояния данных (State). Когда данные меняются, React сам вычисляет, какие изменения нужно внести в DOM (Document Object Model), чтобы картинка совпала с данными.

    !Слева: Императивный подход (инструкции). Справа: Декларативный подход (описание результата).

    Инициализация проекта

    Для создания React-приложения существует множество инструментов. Долгое время стандартом был create-react-app, но сегодня индустрия переходит на более быстрые сборщики, такие как Vite. Однако, для совместимости с большинством учебных материалов, мы рассмотрим структуру, типичную для обоих инструментов.

    Предположим, мы инициализировали проект в папке frontend рядом с нашей папкой backend:

    Компонентная архитектура

    Главный строительный блок React — это компонент. Компонент — это изолированный кусок кода, отвечающий за отрисовку определенной части интерфейса. Это может быть кнопка, форма ввода, шапка сайта или целая страница.

    Функциональные компоненты

    В современном React компоненты — это просто JavaScript-функции, которые возвращают разметку (JSX). Раньше использовались классовые компоненты, но сейчас стандартом являются функциональные компоненты с хуками.

    Рассмотрим пример компонента для отображения одной заметки (NoteItem):

    Обратите внимание на синтаксис, похожий на HTML. Это JSX (JavaScript XML). Он позволяет писать разметку прямо внутри JavaScript. Однако есть отличия: например, вместо class используется className, так как class — зарезервированное слово в JS.

    Иерархия компонентов

    React-приложение — это дерево компонентов. В корне обычно находится компонент App, в который вложены другие компоненты.

    !Иерархия компонентов: от родительского App к дочерним элементам.

    Для нашего приложения заметок структура может выглядеть так:

    * App (Главный контейнер) * Header (Заголовок и меню) * NoteList (Список всех заметок) * NoteItem (Карточка отдельной заметки) * NoteItem * ... * NoteForm (Форма создания новой заметки)

    Props: Передача данных

    Как данные попадают в компоненты? Через Props (свойства). Это аргументы функции-компонента. Данные в React текут строго в одну сторону: от родителя к ребенку (сверху вниз).

    Родитель (NoteList) передает данные ребенку (NoteItem):

    Здесь мы используем метод массива .map(), чтобы превратить массив данных в массив компонентов. Атрибут key обязателен при рендеринге списков — он помогает React эффективно обновлять интерфейс, понимая, какой именно элемент изменился.

    State: Управление состоянием

    Пропсы доступны только для чтения. Компонент не может изменить свои пропсы. Но что, если нам нужно, чтобы интерфейс реагировал на действия пользователя (ввод текста, нажатие кнопок)? Здесь на сцену выходит State (состояние).

    Для работы с состоянием в функциональных компонентах используется хук useState.

    Хук useState

    Хук — это специальная функция, которая позволяет «подцепиться» к возможностям React. Все хуки начинаются с префикса use.

    Разберем синтаксис:

  • useState(0): Мы инициализируем состояние значением 0.
  • const [count, setCount]: Хук возвращает массив из двух элементов. Первый — текущее значение, второй — функция для его обновления. Мы используем деструктуризацию массива, чтобы дать им имена.
  • Важно: Никогда не изменяйте состояние напрямую (например, count = 5). React не узнает об этом изменении и не перерисует компонент. Всегда используйте функцию-сеттер (setCount).

    Side Effects: Взаимодействие с внешним миром

    Компоненты React должны быть «чистыми»: при одних и тех же входных данных они должны возвращать один и тот же результат. Но реальные приложения требуют побочных эффектов (Side Effects): запросов к API, подписок на события, изменения заголовка документа.

    Для этого используется хук useEffect.

    Хук useEffect

    useEffect говорит React сделать что-то после того, как компонент отрисовался. Это идеальное место для получения данных с нашего Django-сервера.

    Второй аргумент useEffect — это массив зависимостей: * [] (пустой массив): Эффект сработает только один раз (при появлении компонента). * [id] (массив с переменными): Эффект сработает при старте и каждый раз, когда изменится переменная id. Отсутствие аргумента: Эффект будет срабатывать при каждом* рендере (опасно, может вызвать бесконечный цикл).

    Организация архитектуры проекта

    Когда приложение растет, хранить все в одной папке становится неудобно. Для Fullstack-проектов хорошей практикой является разделение ответственности.

    Рекомендуемая структура папок внутри src:

  • components/: Переиспользуемые UI-элементы (кнопки, карточки, инпуты). Они обычно «глупые» — просто получают пропсы и рисуют их.
  • pages/: Компоненты-страницы (Главная, Страница заметки, Логин). Они «умные» — знают о маршрутизации, делают запросы к API и управляют состоянием страницы.
  • api/ (или services/): Логика общения с бэкендом. Вместо того чтобы писать fetch внутри компонентов, мы выносим это в отдельные функции.
  • context/: Глобальное состояние (например, данные авторизованного пользователя).
  • Пример разделения логики (API Service)

    Создадим файл src/api/notesApi.js:

    Теперь наш компонент станет чище:

    Подъем состояния (Lifting State Up)

    Частая проблема новичков: как передать данные от ребенка к родителю или между братьями? В React данные не могут подниматься вверх напрямую.

    Решение: Подъем состояния. Мы храним состояние в их общем родителе и передаем функцию изменения состояния вниз как пропс.

    !Подъем состояния: данные перемещаются через общего родителя.

    Пример: Форма создания заметки (NoteForm) и список заметок (NoteList) находятся в App. Когда в форме создается заметка, App должен узнать об этом и обновить список.

  • App хранит список заметок.
  • App создает функцию addNote.
  • App передает addNote в NoteForm.
  • NoteForm вызывает props.addNote(newData).
  • App обновляет свой стейт, и NoteList перерисовывается с новыми данными.
  • Заключение

    Мы разобрали фундамент React-разработки: * Компоненты позволяют разбивать интерфейс на независимые блоки. * Props обеспечивают передачу данных сверху вниз. * State (useState) делает приложение интерактивным. * Effects (useEffect) позволяют взаимодействовать с API и внешним миром.

    Понимание этих концепций критически важно перед тем, как мы начнем соединять наш Django-бэкенд с React-фронтендом в единое целое. В следующей статье мы реализуем полноценный обмен данными: настроим CORS, сделаем реальные запросы на создание и удаление заметок и обработаем ошибки сети.

    3. Интеграция фронтенда и бэкенда: асинхронные запросы и настройка CORS

    Интеграция фронтенда и бэкенда: асинхронные запросы и настройка CORS

    Добро пожаловать обратно в курс «Fullstack-разработка: Django REST Framework и React». К этому моменту мы проделали большую работу: построили надежный API на Django, который умеет хранить и отдавать данные, и спроектировали интерфейс на React, готовый эти данные отображать.

    Однако сейчас наши приложения живут на разных «островах». Django работает на порту 8000, а React — на порту 3000 (или 5173, если вы используете Vite). Они не видят друг друга. В этой статье мы построим мост между ними. Мы разберем, как браузеры защищают нас от злоумышленников (и почему это мешает разработке), научимся писать асинхронный код и реализуем полноценный обмен данными.

    Проблема безопасности: CORS

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

    Чтобы предотвратить это, браузеры используют Same-Origin Policy (Политику одинакового источника). Она гласит: скрипт, загруженный с одного источника (домена, протокола и порта), не может обращаться к ресурсам другого источника без явного разрешения.

    В нашем случае: * Фронтенд: http://localhost:5173 * Бэкенд: http://localhost:8000

    Для браузера это два совершенно разных сайта, потому что у них разные порты. Когда React пытается запросить данные у Django, браузер блокирует этот запрос с ошибкой CORS Error.

    !Визуализация того, как политика CORS блокирует запросы между разными портами и как заголовки сервера разрешают доступ.

    Решение на стороне Django

    CORS (Cross-Origin Resource Sharing) — это механизм, который позволяет серверу сообщить браузеру: «Этому источнику можно доверять». Мы уже касались этого в первой статье, но давайте убедимся, что все настроено верно, так как это самая частая причина проблем при интеграции.

    В файле backend/settings.py должны быть выполнены три условия:

  • Установлена библиотека django-cors-headers.
  • В MIDDLEWARE добавлен corsheaders.middleware.CorsMiddleware (обязательно выше CommonMiddleware).
  • Настроен список разрешенных источников:
  • Теперь, когда React постучится к Django, сервер ответит специальным заголовком Access-Control-Allow-Origin: http://localhost:5173, и браузер пропустит данные.

    Асинхронность в JavaScript

    Прежде чем писать код запросов, нужно понять природу JavaScript. Это однопоточный язык. Это значит, что он может делать только одну вещь за раз. Если мы отправим запрос на сервер и будем ждать ответа 2 секунды, весь интерфейс «зависнет»: кнопки перестанут нажиматься, анимации остановятся.

    Чтобы этого избежать, JavaScript использует асинхронность. Мы отправляем запрос и говорим: «Когда ответ придет, выполни этот код, а я пока займусь другими делами».

    Promise (Обещание)

    Результатом асинхронной операции является объект Promise. Он может находиться в трех состояниях:

  • Pending (Ожидание): Запрос отправлен, ждем.
  • Fulfilled (Выполнено): Успех, данные получены.
  • Rejected (Отклонено): Ошибка (нет интернета, сервер упал).
  • Async / Await

    Раньше для работы с промисами использовали цепочки .then(), но современный стандарт — это синтаксис async/await. Он позволяет писать асинхронный код так, будто он синхронный, что значительно упрощает чтение.

    Сравните:

    Ключевое слово await «замораживает» выполнение функции (но не всего браузера!), пока промис не выполнится.

    Получение данных (GET)

    Давайте реализуем получение списка заметок в нашем компоненте App.jsx (или NotesPage.jsx). Нам понадобятся три состояния:

  • notes: сами данные.
  • isLoading: индикатор загрузки (показывать спиннер, пока ждем).
  • error: текст ошибки, если что-то пошло не так.
  • Обратите внимание на блок finally. Это идеальное место, чтобы убрать индикатор загрузки, независимо от результата запроса.

    Отправка данных (POST)

    Теперь научимся создавать заметки. Для этого нам нужно отправить POST-запрос с JSON-телом. Важный момент: сервер Django ожидает, что мы укажем заголовок Content-Type: application/json, иначе он не сможет прочитать данные.

    Добавим форму и функцию отправки:

    Здесь мы используем прием оптимистичного обновления (почти). Мы получаем ответ от сервера (созданную заметку с присвоенным ID) и добавляем её в текущий массив notes через setNotes([...notes, newNote]). Нам не нужно заново запрашивать весь список заметок у сервера — это экономит трафик и время.

    Удаление данных (DELETE)

    Последний штрих — удаление. Нам нужно отправить запрос на URL вида /api/notes/ID/ с методом DELETE.

    В JSX это будет выглядеть так:

    Метод .filter() создает новый массив, в который попадают все заметки, кроме той, у которой id совпадает с удаленным. React видит новый массив и перерисовывает список.

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

    В примерах выше мы использовали try...catch. Это критически важно. Если сервер недоступен (например, вы забыли запустить Django командой runserver), fetch выбросит исключение. Без catch ваше приложение «упадет» с белым экраном.

    Хорошей практикой является создание универсального сервиса для API, как мы обсуждали в прошлой статье, чтобы не дублировать заголовки и обработку ошибок в каждом компоненте.

    Заключение

    Поздравляю! Теперь у вас есть полноценное Fullstack-приложение.

  • Django слушает запросы и управляет базой данных.
  • React отрисовывает интерфейс и реагирует на действия пользователя.
  • Fetch API переносит данные между ними.
  • CORS настроен так, чтобы разрешать это общение.
  • Вы научились не просто отображать статику, но и управлять потоками данных: загружать, создавать и удалять объекты, синхронизируя состояние клиента и сервера. Это и есть суть работы Fullstack-разработчика.

    В следующих материалах можно углубиться в вопросы аутентификации (JWT), чтобы каждый пользователь видел только свои заметки, но фундамент уже заложен.

    4. Реализация системы аутентификации и авторизации через JWT токены

    Реализация системы аутентификации и авторизации через JWT токены

    Добро пожаловать в четвертую часть нашего курса «Fullstack-разработка: Django REST Framework и React». В предыдущих статьях мы построили API для заметок и создали интерфейс для работы с ними. Однако у нашего приложения есть критический недостаток: оно публичное. Любой, кто знает адрес вашего API, может прочитать, изменить или удалить все заметки.

    В реальном мире данные должны быть защищены. Пользователь должен видеть только свои заметки. Для этого нам нужно внедрить систему аутентификации (проверка, кто вы такой) и авторизации (проверка, что вам можно делать).

    Сегодня мы реализуем современный стандарт защиты веб-приложений — JWT (JSON Web Tokens).

    Почему JWT, а не сессии?

    Традиционно веб-сайты использовали сессии. Когда вы входили в систему, сервер создавал запись в своей памяти (или базе данных) и отправлял вам session_id в куках (cookies). При каждом запросе браузер отправлял этот ID, и сервер искал его у себя в памяти.

    Этот подход отлично работает для монолитов, но имеет недостатки для SPA (Single Page Application) и мобильных приложений:

  • Нагрузка на сервер: Сервер обязан хранить данные о каждом залогиненном пользователе.
  • Масштабируемость: Если у вас несколько серверов, им нужно синхронизировать сессии между собой.
  • Кросс-доменность: Куки привязаны к домену, что усложняет работу, если фронтенд и бэкенд находятся на разных адресах (как у нас).
  • JWT (JSON Web Token) решает эти проблемы. Это stateless (без сохранения состояния) подход. Сервер не хранит информацию о том, что вы залогинены. Вместо этого он выдает вам «цифровой паспорт» с печатью. Вы предъявляете этот паспорт при каждом запросе, и сервер просто проверяет подлинность печати.

    !Сравнение аутентификации на основе сессий и токенов.

    Анатомия JWT

    JWT — это длинная строка, разделенная двумя точками на три части: Header.Payload.Signature.

  • Header (Заголовок): Содержит информацию о типе токена и алгоритме шифрования.
  • Payload (Полезная нагрузка): Содержит данные (claims). Например, ID пользователя, его имя и срок действия токена. Важно: эта часть не зашифрована, а просто закодирована в Base64. Никогда не передавайте здесь пароли!
  • Signature (Подпись): Это результат шифрования заголовка и полезной нагрузки с использованием секретного ключа, который знает только сервер.
  • Если злоумышленник попытается изменить Payload (например, поменять ID пользователя с 5 на 1), подпись перестанет совпадать, и сервер отвергнет токен.

    Access и Refresh токены

    Для безопасности обычно используют пару токенов:

    * Access Token (Токен доступа): Живет недолго (например, 5-30 минут). Именно он используется для доступа к API. * Refresh Token (Токен обновления): Живет долго (например, 1-7 дней). Используется только для получения нового Access Token, когда старый истечет.

    Настройка Бэкенда (Django)

    Для работы с JWT в Django REST Framework стандартом де-факто является библиотека djangorestframework-simplejwt.

    Шаг 1: Установка

    Активируйте виртуальное окружение и установите пакет:

    Шаг 2: Настройка settings.py

    Откройте backend/settings.py. Нам нужно сообщить DRF, что мы хотим использовать JWT для аутентификации.

    Найдите или создайте словарь REST_FRAMEWORK:

    Также можно настроить время жизни токенов (опционально, но полезно знать):

    Шаг 3: Маршруты для получения токенов

    Нам нужны эндпоинты, куда React будет отправлять логин и пароль, чтобы получить токены. Откройте api/urls.py (или главный urls.py):

    Теперь по адресу /api/token/ можно получить пару токенов, отправив POST-запрос с username и password.

    Шаг 4: Защита представлений

    Сейчас наш API все еще открыт. Давайте закроем его. В файле api/views.py добавим проверку прав.

    Внимание: Чтобы этот код заработал, в модели Note должно быть поле author. Если вы его не создавали ранее, добавьте его в models.py и сделайте миграции. Если вы хотите пока пропустить привязку к автору, просто добавьте permission_classes и не меняйте get_queryset.

    Настройка Фронтенда (React)

    Теперь наш API отвечает ошибкой 401 Unauthorized, если мы пытаемся получить заметки без токена. Научим React проходить фейс-контроль.

    Логика авторизации

    Нам нужно создать форму входа. Когда пользователь вводит данные:

  • Отправляем POST на /api/token/.
  • Получаем access и refresh токены.
  • Сохраняем их (для простоты используем localStorage, хотя для продакшена безопаснее httpOnly cookies).
  • Перенаправляем пользователя на главную страницу.
  • Создание компонента Login

    Создайте файл src/pages/Login.jsx:

    Отправка токена с запросами

    Самое важное: теперь каждый запрос к защищенным ресурсам должен содержать заголовок Authorization. Формат заголовка: Bearer <ваш_токен>.

    Вернемся к нашему компоненту App.jsx (или NotesPage.jsx) и модифицируем функцию fetchNotes:

    !Процесс отправки авторизованного запроса.

    Обработка выхода (Logout)

    Выход из системы при использовании JWT — это просто удаление токенов на стороне клиента. Серверу не нужно знать о выходе (так как он stateless).

    Проблема истечения токена

    Access token живет недолго (обычно 5 минут). Что произойдет через 5 минут? Пользователь получит ошибку 401. Заставлять его логиниться каждые 5 минут — плохая идея.

    Для этого существует механизм Refresh Token Rotation. На фронтенде обычно используют «интерцепторы» (перехватчики) запросов (например, в библиотеке axios), которые работают так:

  • Делаем запрос.
  • Если получили ошибку 401, отправляем Refresh token на /api/token/refresh/.
  • Получаем новый Access token.
  • Повторяем исходный запрос с новым токеном.
  • Реализация интерцепторов на чистом fetch довольно громоздка, поэтому в реальных проектах часто переходят на axios, но логика остается прежней.

    Заключение

    Мы сделали огромный шаг вперед. Теперь наше приложение:

  • Безопасно: API защищено от несанкционированного доступа.
  • Персонализировано: Мы можем фильтровать данные для конкретного пользователя.
  • Масштабируемо: Использование JWT позволяет легко отделять фронтенд от бэкенда.
  • В следующей, заключительной статье курса, мы подготовим наше приложение к реальной жизни: соберем статические файлы React, настроим Django для отдачи index.html и обсудим деплой проекта на сервер.

    5. Подготовка к продакшену: сборка статики, Docker и деплой приложения

    Подготовка к продакшену: сборка статики, Docker и деплой приложения

    Поздравляем! Вы прошли долгий путь. Мы начали с пустого проекта, создали мощный API на Django, разработали интерактивный интерфейс на React и защитили данные с помощью JWT. Сейчас у вас есть два работающих приложения: одно на порту 8000, другое на порту 5173 (или 3000). Они работают на вашем компьютере, в режиме разработки.

    Но «на моем компьютере работает» — это не то, что нужно пользователям. В этой финальной статье курса мы превратим наш разрозненный код в единый, готовый к бою продукт. Мы узнаем, как собрать React-приложение в статические файлы, упаковать всё в контейнеры Docker и настроить профессиональный веб-сервер Nginx.

    Разница между Dev и Prod

    Прежде чем писать конфигурационные файлы, важно понять, чем среда разработки (Development) отличается от продакшена (Production).

  • Производительность: В режиме разработки Django и React запускают тяжелые отладочные серверы. Они следят за изменением файлов и перезагружаются. В продакшене это недопустимо — это медленно и небезопасно.
  • Безопасность: В Dev-режиме мы видим подробные тексты ошибок (Traceback). В Prod-режиме пользователь должен видеть только красивую страницу 404 или 500, а детали ошибок должны писаться в логи.
  • Статика: React в разработке генерирует JS «на лету». В продакшене это должен быть набор оптимизированных, минифицированных файлов.
  • Шаг 1: Сборка React-приложения

    Браузеры не понимают JSX и сложную структуру React-проекта напрямую. Им нужны обычные HTML, CSS и JavaScript файлы. Процесс превращения исходного кода в эти файлы называется сборкой (build).

    Откройте терминал в папке frontend и выполните команду:

    После завершения процесса вы увидите новую папку dist (или build). Загляните внутрь. Вы увидите файл index.html и папку assets с файлами, имена которых содержат странные хеши (например, index-d4f2a.js).

    Это и есть ваше готовое фронтенд-приложение. Оно больше не требует Node.js для работы. Это просто статика, которую может отдать любой веб-сервер, даже самый простой.

    Шаг 2: Философия Docker

    Чтобы наше приложение работало одинаково на вашем ноутбуке, на сервере коллеги и в облаке, мы будем использовать Docker. Docker позволяет упаковать приложение и все его зависимости (Python, Node.js, библиотеки) в контейнер.

    Контейнер — это изолированная коробка. Если приложение работает в контейнере у вас, оно гарантированно будет работать в контейнере на сервере.

    !Контейнеризация позволяет изолировать сервисы и их зависимости.

    Шаг 3: Dockerfile для Django

    Создадим инструкцию, как собирать контейнер для нашего бэкенда. В корне папки backend создайте файл с именем Dockerfile (без расширения):

    Обратите внимание на последнюю строку. Мы не используем python manage.py runserver. Вместо этого мы используем Gunicorn. Это WSGI-сервер промышленного уровня, который умеет обрабатывать множество запросов параллельно. Не забудьте добавить gunicorn в ваш requirements.txt.

    Шаг 4: Nginx как дирижер оркестра

    Теперь самое интересное. У нас есть статика React и API Django. Нам нужен единый веб-сервер, который будет принимать все запросы от пользователей и распределять их.

    Мы будем использовать Nginx. Он будет работать как Reverse Proxy (обратный прокси):

  • Если запрос приходит на /api/... или /admin/... -> отправляем его в контейнер Django.
  • Любой другой запрос -> отдаем файлы React (index.html).
  • Это решает проблему CORS раз и навсегда, так как для браузера всё происходит на одном домене и порту.

    Создайте в корне проекта папку nginx и файл nginx.conf внутри:

    Строка try_files uri/ /index.html; критически важна для React Router. Если пользователь обновит страницу /notes/5, Nginx не найдет такой папки, но благодаря этой настройке он отдаст index.html, и React сам разберется, что показать.

    Шаг 5: Docker Compose — собираем всё вместе

    Чтобы запустить Django, базу данных и Nginx одновременно, мы используем Docker Compose. Создайте файл docker-compose.yml в корне всего проекта (рядом с папками backend и frontend):

    Что здесь происходит?

  • db: Запускает PostgreSQL. Данные сохраняются в postgres_data, чтобы не исчезнуть при перезагрузке.
  • backend: Собирает наш образ Django. Подключается к базе данных db (Docker сам настраивает сеть, поэтому хост называется просто db).
  • nginx: Берет официальный образ Nginx, прокидывает порт 80 наружу. Мы «подкладываем» ему наш конфиг и папку dist из React-проекта.
  • Подготовка Django к продакшену

    В settings.py нужно внести изменения, чтобы брать настройки из переменных окружения, которые мы передали в docker-compose:

    Также важно настроить сбор статики Django (для админки), чтобы она попадала в общую папку:

    Запуск и Деплой

    Теперь магия. Чтобы запустить весь проект, вам нужно всего лишь выполнить:

    Docker скачает образы, соберет ваш код, создаст сеть и запустит сервисы. Откройте http://localhost. Вы увидите ваше React-приложение. Попробуйте войти — запросы пойдут через Nginx в Django. Всё работает как единое целое.

    Деплой на сервер (VPS)

    Процесс деплоя на реальный сервер (например, Ubuntu) выглядит так:

  • Арендуйте VPS.
  • Установите на него Docker и Docker Compose.
  • Скопируйте файлы проекта (через git clone или scp).
  • Создайте файл .env с секретными переменными (пароли, ключи).
  • Запустите docker-compose up -d (флаг -d запускает в фоновом режиме).
  • Выполните миграции и сбор статики внутри контейнера:
  • Заключение курса

    Мы прошли огромный путь. Вы создали полноценное Fullstack-приложение с нуля. Вы научились:

    * Проектировать базы данных и API на Django REST Framework. * Создавать компонентные интерфейсы на React. * Связывать их через асинхронные запросы. * Защищать приложение с помощью JWT. * Упаковывать и разворачивать проект с помощью Docker и Nginx.

    Это фундаментальные навыки современного веб-разработчика. Дальше вас ждет углубление в каждую из тем: оптимизация SQL-запросов, продвинутые хуки React, CI/CD пайплайны и облачные архитектуры. Но база у вас уже есть, и она надежна.

    Удачи в разработке!