Home Blog Create React App and Django

DEV

Create React App and Django

Posted by gavin on April 10, 2017, 5:02 p.m.

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:

$ pip install django
$ django-admin startproject myapp
$ cd myapp

Then generate the react app in a subdirectory:

$ yarn global add create-react-app
$ create-react-app frontend

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

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.