Create React App is a project generator for React apps, that sets up a build process for React and modern JavaScript with no configuration. It is a great way to get set up with all the tools necessary to write modern JavaScript. While it doesn't require any configuration if you deploy the React app as a static site, it does require some setup to integrate into a Django project.
To start, generate a Django project:
<pre>$ pip install django
$ django-admin startproject myapp
$ cd myapp
</pre>
Then generate the react app in a subdirectory:
<pre>$ yarn global add create-react-app
$ create-react-app frontend
</pre>
Now we have a Django project with a React app in the frontend/
subdirectory.
To make them work together, we need to figure out how to let the React app talk
to Django over an API, and how to serve the files for the React app itself in a
way that makes sense within a Django project.
Development setup
For development, will have to run two different servers -- one for Django, and
one for React. Add "proxy":
"http://localhost:8000"
to your package.json
(within the frontend directory). This will proxy
requests from the React app to Django's runserver. Then start both servers in
separate terminals:
./manage.py runserver
cd frontend && yarn start
</pre>
Access the frontend like you do in a regular create-react-app project, at
http://localhost:3000
. Any API requests the frontend makes to
http://localhost:3000
will get proxied to Django. This works as long as the
frontend uses only relative URLS, and doesn't follow links provided by the
backend (Common with HATEOAS and Django Rest Framework (DRF)). If the frontend code
does follow API links, they will be directly to runserver
(http://localhost:8000
), making them cross-origin requests. To make this work
we'll need a way to add CORS headers only in local development.
We can do this by writing a middleware. In myapp/middleware.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def dev_cors_middleware(get_response):
"""
Adds CORS headers for local testing only to allow the frontend, which is served on
localhost:3000, to access the API, which is served on localhost:8000.
"""
def middleware(request):
response = get_response(request)
response['Access-Control-Allow-Origin'] = 'http://localhost:3000'
response['Access-Control-Allow-Methods'] = 'GET, POST, PUT, PATCH, OPTIONS, DELETE, HEAD'
response['Access-Control-Allow-Headers'] = 'Content-Type, X-CSRFToken'
response['Access-Control-Allow-Credentials'] = 'true'
return response
return middleware
|
Make sure this is only used for local development. I do this by manipulating
the value of MIDDLEWARE
in my settings_local.py
:
1 | MIDDLEWARE.append('myapp.middleware.dev_cors_middleware')
|
Now the frontend will be able to make requests to both http://localhost:3000
and http://localhost:8000
, so the links generated by DRF will work. In
production, we'll use a different solution that puts the React app and the API
on the same domain.
Production setup
In production, we can't use two different URLs and run the yarn server. We will need to run the create-react-build process to create a production build, then serve that through Django in order to get everything onto the same domain.
yarn run build
outputs its build artifacts into frontend/build/
. We can
configure Django's staticfiles to serve the JS and CSS from create-react-app's
build with these settings:
1 2 3 4 5 | REACT_APP_DIR = os.path.join(BASE_DIR, 'frontend')
STATICFILES_DIRS = [
os.path.join(REACT_APP_DIR, 'build', 'static'),
]
|
Now collectstatic will automatically find the static build artifacts, and
deploy them however you have configured staticfiles. The only thing left is to
serve the entry point to the React app, index.html
. We can't use staticfiles
for this because it needs to be served at the root of our site, not under
STATIC_URL
.
We can do this with a Django view, in myapp/views.py
:
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 | import logging
from django.views.generic import View
from django.http import HttpResponse
from django.conf import settings
class FrontendAppView(View):
"""
Serves the compiled frontend entry point (only works if you have run `yarn
run build`).
"""
def get(self, request):
try:
with open(os.path.join(settings.REACT_APP_DIR, 'build', 'index.html')) as f:
return HttpResponse(f.read())
except FileNotFoundError:
logging.exception('Production build of app not found')
return HttpResponse(
"""
This URL is only used when you have built the production
version of the app. Visit http://localhost:3000/ instead, or
run `yarn run build` to test the production version.
""",
status=501,
)
|
This view must be installed with a catch-all urlpattern in order for pushState
routing to work. In myapp/urls.py
:
1 2 3 4 5 6 7 8 | from django.conf.urls import url
from . import views
urlpatterns = [
# ... the rest of the urlpatterns ...
# must be catch-all for pushState to work
url(r'^', views.FrontendAppView.as_view()),
]
|
Now once we configure out deployment process to run the create-react-app build script, users who visit our site will be served the React app. Other Django URLs like the API and admin will still continue to work, as long as their urlpatterns come before the catch all.
Fusionbox is a world-class custom Django development company with hundreds of Django applications under our belt. We've also contributed to the Django Core. Contact us today to learn how we can help you with your Django project.