====== Posada en producció d'un projecte Django + ReactJS ====== Hi ha diverses eines per a poder realitzar aquest desplegament. Les opcions més habituals son: * Apache2 amb el plugin ''mod_wsgi''. * Nginx + servidor d'aplicacions com uWSGI o gunicorn. En aquest article ho farem amb Apache + ''mod_wsgi''. {{ django:django_react_produccio.png }} \\ ===== Conceptes previs ===== En un projecte amb Django i React, cal tenir clar que hi ha una part de processament al servidor (Django) que ha de respondre les peticions que requereixen accés i manipulació de les dades a la BD (bàsicament serà l'API i l'//admin panel//), i una part que s'executa al client (React) i que des del punt de vista del servidor son simples arxius estàtics. A més d'això, per la part de servidor també caldrà servir els arxius estàtics de la part Django (al menys per l'//admin panel//) i les possibles càrregues (//uploads//) que es solen parlar com d'arxius //media//. {{ django:diagrama_django_produccio.png }} Per altra banda, cal fer servir un motor de BD adequat com Postgre, MySQL, MariaDB o Oracle. **SQLite no és una opció per producció**, tot i que va bé per desenvolupament i una part del testing. En resum: * **Django**: * Rutes de processament: * ''/api'' : React necessita interactuar amb l'app per aquesta via. * ''/admin'' : l'//admin panel// de Django que ens permet tenir un CRUD fàcil de tots els elements de la BD sense haver de realitzar gaire codi. * ''/static'': arxius estàtics, bàsicament per a l'//admin panel//. * ''/media'': arxius media (càrregues, uploads). * **ReactJS**: es compila l'aplicació que queda reduïda a uns pocs arxius estàtics, amb un sol punt d'entrada. * ''/'': arxius estàtics, únic arxiu a ''index.html'' * ''/assets'': resta d'arxius estàtics \\ ===== Prerequisits ===== Abans de començar necessitem disposar de: * Un **projecte Django amb React**, ja sigui en un sol repo en o en repositoris separats. Si no en tens cap, pots fer servir [[https://github.com/emieza/django-react|aquest repo de minibiblio]]. * Un **servidor** operatiu amb accés SSH i amb un domnini configurat que l'hi apunta elmeudomini.com . L'article es fa amb un **Debian o Ubuntu LTS**. Assumim que el servidor que disposem és dedicat únicament a aquesta app. És possible configurar diverses aplicacions en un server, però en ares de la simplicitat ho plategem així. \\ ===== MariaDB ===== Configurem primer de tot la BD de producció. Farem servir MariaDB però es podrien seguir pràcticament les mateixes passes per MySQL. Per a PostgreSQL o Oracle canviara força coses de la creació de l'espai d'usuari i esquemes. $ sudo apt update $ sudo apt install mariadb-server $ sudo mysql mysql> create user biblio@localhost identified by "biblio123"; mysql> grant all on biblio.* to biblio@localhost; mysql> create database biblio; Mai poseu una //password// tan evident com "biblio123" en producció. Això està bé en development local, però no en cap servidor públic, per molt que els ports de la BD no siguin públics des de fora. Amb això ja tindríem instal·lada la BD, amb un usuari ''biblio'' i amb accés a la DB ''biblio''. \\ ===== Django ===== Instal·lem dependències del sistema. Utilitzarem [[Python venv]] per a la gestió de les biblioteques de codi. Haurem d'afegir el paquet ''mysqlclient'' de Python que potser no es feia servir en desenvolupament. Assumim que el projecte Django utilitza un plugin com ''django-environ'' o ''django-dotenv'' per a la gestió de les credencials. Si no ho tens, pots fer un cop d'ull a l'article [[Django React#django-environ i el vcs]]. També assumim que instal·larem l'aplicació Django al //home directory// de l'usuari principal del sistema, com ''super'' o ''isard''. També seria adequat fer-ho en una carpeta de sistema (p.ex. ''/var/www/'') si es tractés d'un entorn multiusuari, situació menys freqüent actualment ja que es sol tenir un contenidor LXC o VM específic per a cada aplicació. $ sudo apt install default-libmysqlclient-dev python3-dev python3-venv git $ git clone https://github.com/elmeuusuari/projecte-django-react $ cd projecte-django-react $ python3 -m venv env $ source env/bin/activate (env) $ pip install -r requirements.txt (env) $ pip install mysqlclient (env) $ cp .env.example .env Ajusta els valors de ''.env'', en particular activa la línia d'exemple per a MariaBD i comenta la de ''sqlite''. També ajusta el domini **elmeudomini.com** com pertoqui: SECRET_KEY=sdjfhisdfg67sdtf78ewhf8w3efheh7uf9iehfj98er DATABASE_URL=mysql://biblio:biblio123@127.0.0.1:3306/biblio #DATABASE_URL=sqlite:///db.sqlite3 ALLOWED_HOSTS=elmeudomini.com CORS_ALLOWED_ORIGINS=http://localhost:5173,http://127.0.0.1:5173,https://elmeudomini.com Fixeu-vos que ALLOWED_HOSTS hem tret el domini wildcard "*" i només deixem el domini real en producció. Son mesures de seguretat importants. Ara ja podem acabar de fer comprovacions i crear les taules de la BD: (env) $ ./manage.py check (env) $ ./manage.py migrate (env) $ ./manage.py createsuperuser === Tests === Podeu fer també un ''runserver'' i fer proves amb el servidor de desenvolupament si podeu accedir al port 8000 (sempre i quan tingueu aquest port obert externament): (env) $ ./manage.py runserver Amb el servidor de desenvolupament obert podeu provar d'accedir (amb una altra //shell//) a l'API o l'//admin panel// amb: $ curl localhost:8000/api/llibres $ curl localhost:8000/admin \\ ===== ReactJS ===== Assumim que disposem de la part de React del projecte a la carpeta ''/react'', tot i que podria ser perfectament un repositori a part Instal·lem NodeJS versió LTS. Primer instal·lem la versió que hi hagi als repos del sistema operatiu que tinguem, i després fem un //upgrade// amb l'eina ''n'': sudo apt install nodejs npm sudo npm i -g n sudo n lts Tanqueu el terminal o la connexió SSH perquè els canvis de la versió de NodeJS prenguin efecte. Aneu a la carpeta de React, configurem i compilem: $ cd /ruta/a/la/carpeta/react $ npm install $ cp src/config.js.example src/config.js Editeu ''src/config.js'', bàsicament canviar les URLs d'accés amb l'esquema complert de **https://elmeudomini.com** . Un cop configurat amb els dominis adequats, podem compilar l'app React. $ npm run build El ''npm run build'' crearà una carpeta ''dist/'' on hi haurà els arxius estàtics per al desplegament, en particular el punt d'entrada únic ''index.html''. \\ ===== Configurar Apache2 ===== \\ === Permisos de carpetes === Probablement haureu clonat el repositori del projecte al //home directory// de l'usuari que tingueu, per exemeple ''super'' o ''isard''. Aquesta carpeta del //home directory// com ''/home/super/'' sol tenir permisos restringits per protegir les dades en sistemes multiusuari, però com que no és el nostre cas (assumim un entorn monousuari), podem **obrir els permisos per facilitar que Apache pugui arribar i llegir les carpetes del projecte**: $ chmod 755 ~ \\ === Arxius estàtics Django === Necessitem recollir tots els arxius estàtics del projecte Django per servir-los a la URL ''/static''. La instrucció ''collectstatic'' ens ho facilitarà, però abans caldrà modificar ''settings.py'' per indicar el nom de la carpeta d'arxius estàtics. Localitzeu la línia amb: STATIC_URL = 'static/' I afegiu a darrera el nom de la carpeta o vulgueu els arxius estàtics (podeu triar el nom): STATIC_ROOT = 'static/' Ara ja podeu recollir tots els arxius estàtics de l'app Django i del seu //admin panel//: (env) $ ./manage.py collectstatic \\ === Instal·lem Apache === Ara sí! Instal·lem Apache i el mòdul ''mod_wsgi'' que ens permetrà connectar Apache a l'app Python. WSGI és un estàndard de comunicació entre servidors i aplicacions web (Web Server Gateway Interface), tot que es fa servir bàsicament en aquestes apps Python com Django, Flask o FastAPI. $ sudo apt install apache2 libapache2-mod-wsgi-py3 $ sudo a2enmod rewrite \\ === Configuració Apache === La configuració, com hem vist al principi, no és senzilla. Recopilem el què anem a fer: * **Django**: * Rutes de processament: * ''/api'' : React necessita interactuar amb l'app per aquesta via. * ''/admin'' : l'//admin panel// de Django que ens permet tenir un CRUD fàcil de tots els elements de la BD sense haver de realitzar gaire codi. * ''/static'': arxius estàtics, bàsicament per a l'//admin panel//. * ''/media'': arxius media (càrregues, uploads). * **ReactJS**: es compila l'aplicació que queda reduïda a uns pocs arxius estàtics, amb un sol punt d'entrada. * ''/'': arxius estàtics, únic arxiu a ''index.html'' * ''/assets'': resta d'arxius estàtics Assumim que tenim una configuració monousuari i monoaplicació (una VM o CT dedicat per a aquesta app). En aquest cas podeu implementar directament a la site per defecte d'Apache ''/etc/apache2/sites-available/000-default.conf'': # Si només tenim 1 domini, no cal anomenar-lo #ServerName www.example.com WSGIDaemonProcess myapp python-home=/home/super/django-react/env python-path=/home/super/django-react WSGIProcessGroup myapp WSGIScriptAlias / /home/super/django-react/djangoreact/wsgi.py Require all granted Alias /static/ /home/super/django-react/static/ Require all granted Alias /media/ /home/super/django-react/media/ Require all granted # REACT FRONTEND Alias /assets /home/super/django-react/react/dist/assets/ Alias /index.html /home/super/django-react/react/dist/index.html AliasMatch ^/$ /home/super/django-react/react/dist/index.html Require all granted # Regla de reescriptura per a React Router # Les rutes conegudes no entren: /admin /api /static /media i /assets # La resta de rutes van a dist/index.html (React Router) RewriteEngine On RewriteCond %{REQUEST_URI} !^/admin RewriteCond %{REQUEST_URI} !^/api RewriteCond %{REQUEST_URI} !^/static RewriteCond %{REQUEST_URI} !^/media RewriteCond %{REQUEST_URI} !^/assets RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ /home/super/django-react/react/dist/index.html [L] ServerAdmin webmaster@localhost DocumentRoot /var/www/html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined Reinicia Apache amb: $ sudo systemctl restart apache2.service \\ ===== Comprovacions ===== Si no et dona errors al reiniciar Apache, prova les següents comprovacions. Django funciona a ''/api'' i ''/admin'': $ curl https://elmeudomini.com/api/llibres $ curl https://elmeudomini.com/admin Si no es veu des de fora amb el domini, intenta comprovar que sí que funciona des de dins: $ curl http://localhost/api/llibres $ curl http://localhost/admin En particular, comprova que els arxius estàtics es veuen quan anem a ''/admin''. Funciona el //frontend// de React a https://elmeudomini.com (amb el navegador). Si no es veu des de fora, intenta la comprovació interna: $ curl http://localhost Si apareix la pàgina de React però no carrega les dades de l'API, obre la consola de Javascript del navegador i investiga el problema que estigui succeint (CORS, etc.) \\ ===== Media Uploads ===== Comproveu que es pot accedir a ''/admin'' i que podeu afegir llibres amb imatges. Si us dona problemes, segurament és que necessitareu donar permisos a ''/media'' perquè Apache hi pugui escriure: $ sudo chown -R www-data /home/super/django-react/media