Trick to use Django to serve React app

Contents


Django backend and React.js frontend

Instead of using template language in view part of MVC in frameworks, modern way is to separate apps into backend and frontend. The idea is using javascript fetch() or Axios.get()/Axios.post() in frontend to call APIs in backend frameworks e.g. Django’s Django Rest Framework which is the one I’m going to talk about or Node’s Express.js framework which I will discuss in future.

How they serve using template


I found the common ways need to set up a few steps e.g. adding function base view to render frontend index.html in `views.py `,

Tip

The code in views.py looks like

1
2
def index(request):
   return render(request, "build/index.html")

In urls.py

1
2
3
4
5
from django.urls import path
from demo.views import index
urlpatterns = [
    path("", index, name="index")
]

installing django-cors-headers package to fix CORS issue and then in `setting.py`, adding frontend directory path to TEMPLATES and frontend static folder in build to STATICFILES_DIRS .
 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
29
30
31
32
33
MIDDLEWARE = [
   'corsheaders.middleware.CorsMiddleware',  # added to solve CORS
   'django.middleware.common.CommonMiddleware',  # added to solve CORS
   'django.middleware.security.SecurityMiddleware',
   'django.contrib.sessions.middleware.SessionMiddleware',
   'django.middleware.common.CommonMiddleware',
   'django.middleware.csrf.CsrfViewMiddleware',
   'django.contrib.auth.middleware.AuthenticationMiddleware',
   'django.contrib.messages.middleware.MessageMiddleware',
   'django.middleware.clickjacking.XFrameOptionsMiddleware',
]


TEMPLATES = [
   {
       'BACKEND': 'django.template.backends.django.DjangoTemplates',
       'DIRS': [os.path.join(BASE_DIR, 'frontend')]  # added the root folder of frontend here
       ,
       'APP_DIRS': True,
       'OPTIONS': {
           'context_processors': [
               'django.template.context_processors.debug',
               'django.template.context_processors.request',
               'django.contrib.auth.context_processors.auth',
               'django.contrib.messages.context_processors.messages',
           ],
       },
   },
]
STATIC_URL = '/static/'
STATICFILES_DIRS = (
   os.path.join(BASE_DIR, 'frontend', "build", "static"),  # update the STATICFILES_DIRS
)

I feel this is too much trouble. And the static content might not be rendering correctly e.g. images might not display due to static path issue.


How I serve using serve function


I figured out an easier way to do it. The trick is create a my_serve function in urls.py . I refer to the serve function in django.conf.urls.static and rewrite it to make it work as using it directly doesn’t work as expected.


In urls.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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from django.conf import settings
from django.conf.urls.static import static, serve
from django.contrib import admin
from django.urls import path, re_path, include
from rest_framework.routers import DefaultRouter
from django.http import (
    FileResponse, Http404, HttpResponse, HttpResponseNotModified,
)
import posixpath
from pathlib import Path
from django.utils._os import safe_join
import mimetypes
from django.utils.http import http_date
from django.utils.translation import gettext as _
import demo.views
def my_serve(request, path, document_root=None, show_indexes=False):
    path = posixpath.normpath(path).lstrip('/')
    fullpath = Path(safe_join(document_root, path))
    if fullpath.is_dir():
        fullpath = Path(safe_join(document_root, 'index.html'))
      
    if not fullpath.exists():
        raise Http404(_('"%(path)s" does not exist') % {'path': fullpath})
    # Respect the If-Modified-Since header.
    statobj = fullpath.stat()
 
    content_type, encoding = mimetypes.guess_type(str(fullpath))
    content_type = content_type or 'application/octet-stream'
    response = FileResponse(fullpath.open('rb'), content_type=content_type)
    response["Last-Modified"] = http_date(statobj.st_mtime)
    if encoding:
        response["Content-Encoding"] = encoding
    return response

router = DefaultRouter()
router.register(r'testApi', demo.views.TestViewSet)

urlpatterns = [
    re_path(r'^api/v1/', include(router.urls)),
    path('admin/', admin.site.urls),
    re_path(r'^(?P<path>.*)$', my_serve, { 'document_root': settings.FRONTEND_ROOT }),
]  

in setting.py


1
2
3
4
5

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

FRONTEND_ROOT = os.path.abspath(os.path.join(BASE_DIR, '..', 'frontend', 'build'))


Doing it this way, I only need to change one place i.e. adding FRONTEND_ROOT in setting.py. Then Django hands over all the routing control to react router.