Добавление React приложения в Django
Содержание
С опытом приходит понимание, что хорошо организованный проект гораздо легче не только в начальной разработке но и в дальнейшей поддержке. Когда каждый файл находится на своём месте и из названия каждой папки и файла в структуре понятно за что он отвечает не только тебе, но и твоим коллегам. Рассмотрим мои изыскания по организации проекта React + Django в одном репозитории.
Определение условий для проекта
Нашими граничными условиями со стороны Django будут:
- возможность деплоить на продуктив и стейджинг с минимальными изменениями конфигурации от среды разработки
- среда разработки не должна подразумевать сложной конфигурации. В идеале, всё должно работать через один порт который создаётся через runserver
- желательна поддержка ASGI для разработки с использованием Websockets
- гармонично должно вписываться в иерархию файлов Django
Граничные условия со стороны React:
- работа всего через create-react-app
- следовательно отсюда — минимальная конфигурация Webpack
- вся сборка должна выполняться внутри одной папки и не расползаться по Django проекту (это относится к node_modules и результатам сборки в среде разработки)
- минимальные (а лучше всего нулевые) изменения для работы в продуктиве
- CI/CD friendly. Система должна без плясок с бубном интегрироваться с системами CI/CD
Эти условия у меня сформировались ещё за время работы со связкой Laravel + React. И для Django хочется сделать как минимум так же, или лучше.
Существующие варианты интеграции Django и React.js
Как любой здравомыслящий в меру ленивый программист я начал с исследования поисковой выдачи гугла. То что было найдено я могу разделить на два класса.
Первый. Это создание проекта с помощью create-react-app, затем приложение React запускается своими методами на своём 3000 порту, а Django работает на своём и мы редактируем CSRF заголовки, чтобы браузер не сходил с ума по поводу того что приложение потребляет данные с другого порта.
Второй. Это ручками создаём React проект и настраиваем в нём всё как нам надо руками в конфигурационном файле Webpack.
Оба варианта нам не совсем подходят, каждый по своему. Общего во всех примерах я выловил, что мы используем отдельное приложение Django и внутри мы разворачиваем уже наше React код. Приложение во всех примерах называется frontend. Ну ок, уже хороший старт.
Подготовка установки Django для проверки интеграции
Стандартно разворачиваем Django и добавляем в него DjangoRestFramework. Исходный код финальной версии проекта доступен на GitHub
1 2 3 |
python3 -m venv venv source venv/bin/activate django-admin startproject react_django . |
Создаём приложения для демонстрационных ендпойнтов
1 |
django-admin startapp demo_endpoints |
Редактируем react_django/settings.py и добавляем строчки в INSTALLED_APPS:
1 2 3 4 5 6 7 8 9 10 |
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', # добавляем Django Rest Framework 'demo_endpoints.apps.DemoEndpointsConfig', # активируем приложение эндпойнтов ] |
Создаём модель для проверки взаимодействия. Редактируем файл demo_endpoints/models.py
1 2 3 4 5 |
from django.db import models class Demo(models.Model): name = models.CharField(max_length=100) |
И применяем изменение в базе данных:
1 2 |
python manage.py makemigrations python manage.py migrate |
Затем прикручиваем нашу модель к DRF. Начинаем с сериализера. Для этого редактируем файл demo_endpoints/serializers.py
1 2 3 4 5 6 7 8 |
from rest_framework import serializers from .models import Demo class DemoSerializer(serializers.ModelSerializer): class Meta: model = Demo fields = ('id', 'name') |
Дальше настраиваем вид в файле demo_endpoints/views.py
1 2 3 4 5 6 7 8 |
from .models import Demo from .serializers import DemoSerializer from rest_framework import generics class DemoListCreate(generics.ListCreateAPIView): queryset = Demo.objects.all() serializer_class = DemoSerializer |
И настраиваем маршрутеризацию всего нашего проекта в файле react_django/urls.py
1 2 3 4 5 6 |
from django.urls import path, include urlpatterns = [ path('', include('demo_endpoints.urls')), ] |
И маршрутеризация всего нашего приложения с демонстрационными данными в файле demo_endpoints/urls.py
1 2 3 4 5 6 7 |
from django.urls import path from . import views urlpatterns = [ path('api/demo/', views.DemoListCreate.as_view() ), ] |
Проверяем python manage.py runserver и добавляем несколько записей на свой вкус.
Интеграция React
Следующее у нас — это создание Django приложения frontend и установка в него React.
1 2 3 |
django-admin startapp frontend mkdir -p ./frontend/{static,templates}/frontend cd frontend |
В эту же папку устанавливаем React.js
1 |
npx create-react-app frontend |
Нам приходится сделать ещё одну папку с именем frontend, так как create-react-app не позволяет создавать проект в директории где есть ещё файлы. Поэтому после создания переносим вес файлы и удалем лишнюю папку
1 2 3 |
mv -f frontend/* . mv -f frontend/.* . rm -rf frontend |
Теперь нам нужно сконфигурировать сборку React таким образом, чтобы файлы сборки попадали в директорию статичных файлов приложения Django.
Для продуктивной сборки мы добавим опцию BUILD_PATH. Для этого изменим файл package.json
1 2 3 4 5 6 |
"scripts": { "start": "react-scripts start", "build": "BUILD_PATH='static/frontend' react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, |
Для режима разработки нам нужно будет добавить режим «наблюдения» и сборки без запуска сервера в нашу систему.
Для этого создаём папку scripts и складываем в него файл watch.js основа которого взята здесь.
Добавляем в package.json
1 2 3 4 5 6 7 |
"scripts": { "start": "react-scripts start", "build": "BUILD_PATH='static/frontend' react-scripts build", "watch": "BUILD_PATH='static/frontend' PUBLIC_URL='http://127.0.0.1:8000/static/frontend/' node scripts/watch.js", "test": "react-scripts test", "eject": "react-scripts eject" }, |
Интеграция Django
Для отображения файла в продуктиве мы просто настраиваем nginx на нашу папку со статичными файлами и вроде бы всё.
Для работы в режиме разработки нам нужно чтобы файлы обновлялись и чтобы корневой урл открывал наше приложение. Для этого вполне подходит просто отображение статических файлов.
Наш проект должен открыться по адресу:
http://127.0.0.1:8000/static/frontend/index.html
Модифицируем файл frontend/src/App.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import React, {useEffect, useState} from 'react'; import logo from './logo.svg'; import './App.css'; function App() { const [entitiesList, setEntitiesList] = useState([]); let list = 'data is loading'; useEffect(() => { fetch('/api/demo/') .then(response => response.json()) .then(data => { setEntitiesList(data);}); }, []); list = entitiesList.map( i => (<div>{i.id}: {i.name}</div>)); return ( <div className="App"> <h1>The content of the demo endpoint:</h1> { list } </div> ); } export default App; |
Запустим сборку проекта командой yarn watch, перезагрузим страницу и увидим наш работающий эндпойнт
Итоги проведённой интеграции
В общем и целом, можно считать, что все пункты выполнены из нашего начального списка. Результат работы опубликован на GitHub. У нас получилась интеграция, которая работает в продуктиве и в среде разработки. Не ломает и не лезет внутрь фреймворков, всё работает через открытые механизмы расширения.
Из будущих проблем — это поддержка скрипта watch.js, при смене внутренностей react-scripts он может поломаться.
Следующим этапом разработки данной интеграции для меня было бы интересно сделать возможность прокидывать авторизацию из Django в React приложение без пляски с JWT или какими-то другими способами авторизации.
Так же было бы очень удобно иметь возможность автоматического обновления страницы после пересборки React приложения в среде разработки.