Integrating a React Frontend into a Django Application
This article explains how to integrate a React frontend with a Django application using Django REST Framework, Vite, and django-cors-headers. It covers project setup, API creation, React data fetching, CORS and CSRF, authentication options, deployment approaches, common problems, and recommended frontend-backend boundaries.
Integrating a React Frontend into a Django Application
Django and React solve different parts of a web application.
Django is commonly responsible for:
- database models
- business logic
- authentication
- permissions
- server-side validation
- API endpoints
- administrative tools
React is commonly responsible for:
- interactive user interfaces
- browser-side state
- reusable UI components
- client-side navigation
- API requests
- updating pages without full reloads
A common integration uses Django as an API backend and React as a separate frontend application.
The basic flow is:
⧉
1 2 3 4 5 6 7 | |
React requests data from Django. Django validates the request, communicates with the database, and returns JSON. React then displays the returned data.
This article demonstrates a basic integration using:
- Django
- Django REST Framework
- React
- Vite
django-cors-headers
Django REST Framework provides serializers, API views, viewsets, authentication support, and URL routers for building web APIs. Vite provides a development server and React project template for modern frontend applications.
Integration Approaches
There are two common ways to combine Django and React.
Separate Frontend and Backend
React and Django run as separate applications.
⧉
1 2 3 4 5 6 7 | |
During development:
⧉
1 2 | |
React communicates with Django through API requests.
This approach provides:
- independent frontend and backend development
- Vite’s development server and hot reloading
- a clear API boundary
- flexible deployment options
- easier replacement of either frontend or backend
It also requires handling:
- CORS
- API authentication
- separate development processes
- environment-specific API URLs
This is the approach used in the main example.
React Built into Django
React can also be built into static JavaScript files that Django serves.
⧉
1 2 3 4 5 | |
This approach can simplify deployment because Django and React are delivered from the same origin.
However, frontend development still normally uses Vite’s development server. The compiled production assets are copied or generated into a location managed by Django’s static-file system.
Django’s staticfiles application can discover additional static directories through STATICFILES_DIRS, while collectstatic gathers production assets into STATIC_ROOT.
Example Application
The example will create a simple task application.
Django will provide an API with endpoints for:
- listing tasks
- creating tasks
- updating tasks
- deleting tasks
React will:
- request tasks from Django
- display the task list
- submit new tasks
- mark tasks as complete
- delete tasks
Project Structure
The completed project will resemble:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Creating the Django Backend
Create the main project directory:
⧉
1 2 | |
Create and activate a Python virtual environment:
⧉
1 | |
On Linux or macOS:
⧉
1 | |
On Windows PowerShell:
⧉
1 | |
Install Django, Django REST Framework, and the CORS package:
⧉
1 | |
Create the Django project:
⧉
1 2 | |
Create an application:
⧉
1 | |
Configuring the Django Applications
Open backend/config/settings.py.
Add the task app, REST Framework, and CORS support:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Add the CORS middleware near the beginning of the middleware list:
⧉
1 2 3 4 5 6 7 8 9 10 | |
The CORS middleware should appear before middleware that may generate responses, such as CommonMiddleware, so it can add the required headers.
Allow the React development server:
⧉
1 2 3 | |
The origin must include the scheme and port:
⧉
1 | |
Do not write only:
⧉
1 | |
For local development, the React server and Django server use different origins because they run on different ports.
Avoid enabling every origin in production unless the API is intentionally public:
⧉
1 | |
An explicit allowlist is safer for most applications.
Creating the Task Model
Open tasks/models.py:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Create and apply the migration:
⧉
1 2 | |
Register the model in tasks/admin.py:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Create an administrator account if needed:
⧉
1 | |
Creating a Serializer
Django model instances cannot be sent directly to React as JSON.
A serializer converts model instances into Python data that can be rendered as JSON. It also validates incoming request data before creating or updating model instances.
Create tasks/serializers.py:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
ModelSerializer provides a shortcut for creating serializers based on Django models.
Creating the API ViewSet
Open tasks/views.py:
⧉
1 2 3 4 5 6 7 8 9 | |
ModelViewSet provides standard API actions for:
- listing objects
- retrieving one object
- creating objects
- updating objects
- partially updating objects
- deleting objects
The resulting HTTP operations are:
| HTTP method | Endpoint | Action |
|---|---|---|
GET |
/api/tasks/ |
List tasks |
POST |
/api/tasks/ |
Create a task |
GET |
/api/tasks/1/ |
Retrieve task 1 |
PUT |
/api/tasks/1/ |
Replace task 1 |
PATCH |
/api/tasks/1/ |
Partially update task 1 |
DELETE |
/api/tasks/1/ |
Delete task 1 |
Viewsets group related API behavior, while routers generate their associated URL patterns.
Creating the API URLs
Create tasks/urls.py:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Open the project URL configuration in config/urls.py:
⧉
1 2 3 4 5 6 7 8 | |
Run the Django development server:
⧉
1 | |
Open:
⧉
1 | |
Django REST Framework should display the browsable API.
Testing the API
Create a task with a command-line request:
⧉
1 2 3 4 5 | |
Retrieve all tasks:
⧉
1 | |
A response may look like:
⧉
1 2 3 4 5 6 7 8 | |
Creating the React Frontend
Return to the main project directory:
⧉
1 | |
Create a React project with Vite:
⧉
1 | |
Move into the frontend directory:
⧉
1 | |
Install the dependencies:
⧉
1 | |
Start the React development server:
⧉
1 | |
Vite supports a React starter template and provides a development server with React Fast Refresh.
The frontend normally becomes available at:
⧉
1 | |
At this point, two servers should be running:
⧉
1 2 | |
Creating an API Module
Create frontend/src/api.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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | |
Keeping API requests in a separate module prevents networking code from being repeated throughout the React components.
Creating the React Component
Replace frontend/src/App.jsx:
⧉
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | |
The component uses:
useState()to store tasks, form input, errors, and loading stateuseEffect()to load tasks after the component is mountedfetch()through the API module- state updates after successful API requests
Configuring the API URL
Create frontend/.env.development:
⧉
1 | |
Access the value in JavaScript with:
⧉
1 | |
Environment variable names exposed to Vite client code must begin with:
⧉
1 | |
Do not place secret keys, database credentials, private API tokens, or Django’s SECRET_KEY in frontend environment variables.
Values bundled into React code can be inspected by users in the browser.
Using a Vite Development Proxy
CORS can be avoided during local development by proxying API requests through Vite.
Open frontend/vite.config.js:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
The API module can then use a relative URL:
⧉
1 | |
A request to:
⧉
1 | |
is forwarded by Vite to:
⧉
1 | |
This proxy is only a development convenience. Production routing still needs to be configured through the deployment environment.
CORS and CSRF Are Different
CORS and CSRF solve different problems.
CORS controls whether browser JavaScript from one origin may read responses from another origin.
CSRF protection prevents another site from making unwanted authenticated requests on behalf of a user.
Enabling CORS does not disable the need for CSRF protection.
This distinction becomes especially important when React uses Django session authentication.
Authentication Options
A React frontend commonly uses one of the following approaches.
Session Authentication
Django stores the user’s authenticated state in a server-side session, and the browser sends a session cookie.
Advantages include:
- integration with Django’s existing authentication
- integration with the Django admin
- server-controlled sessions
- familiar login and logout behavior
For cross-origin requests, JavaScript must include credentials:
⧉
1 2 3 | |
Django must allow credentials:
⧉
1 | |
The frontend origin may also need to be trusted for CSRF checks:
⧉
1 2 3 | |
Unsafe requests such as POST, PUT, PATCH, and DELETE require a valid CSRF token when using session authentication.
Token Authentication
The client sends an authentication token in a request header.
A typical header looks like:
⧉
1 | |
Token authentication can be convenient for API clients, but token storage and expiration need careful design.
JSON Web Tokens
JWT-based authentication is commonly added through a third-party package.
It can support short-lived access tokens and refresh tokens, but it also introduces decisions about:
- token storage
- token rotation
- revocation
- expiration
- refresh behavior
- protection against token theft
JWT is not automatically better than Django sessions. Choose the authentication model that matches the application’s deployment and security requirements.
For a browser frontend served from the same site as Django, session authentication is often a practical option.
Adding Session Authentication and CSRF
Suppose React and Django use Django’s session system.
Create a view that ensures the CSRF cookie is set:
⧉
1 2 3 4 5 6 7 8 9 | |
Add the URL:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 | |
React can call it before submitting protected requests:
⧉
1 2 3 4 5 6 | |
A helper can read the CSRF cookie:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
Include it in unsafe requests:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
Django recommends sending the token through the X-CSRFToken header for AJAX requests.
Do not solve CSRF errors by applying csrf_exempt to every API view. That removes an important security control.
Protecting the API
The initial example allows unrestricted access.
A real application should define authentication and permissions.
For example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
The model would need an owner:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Set the owner when creating a task:
⧉
1 2 3 4 | |
The serializer should not accept arbitrary owners from the frontend:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Filtering by the current user prevents one user from retrieving another user’s tasks.
Authentication verifies who the user is. Permissions and queryset restrictions determine which data the user may access.
Returning Validation Errors
Suppose the task title cannot be shorter than three characters.
Add serializer validation:
⧉
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 | |
Django REST Framework may return:
⧉
1 2 3 4 5 | |
React should display these errors instead of assuming that every failed request has the same structure.
A more useful error parser might be:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Production Option 1: Deploy Separately
The frontend and backend can be deployed separately:
⧉
1 2 3 4 5 | |
The React production environment might contain:
⧉
1 | |
Django would allow the frontend origin:
⧉
1 2 3 | |
For session-based authentication:
⧉
1 2 3 4 5 | |
Cookie settings may also need review:
⧉
1 2 | |
Production authentication involving different sites or subdomains requires careful cookie, SameSite, HTTPS, CORS, and CSRF configuration.
Production Option 2: Serve the React Build with Django
React can be compiled and served alongside Django.
Build the React project:
⧉
1 2 | |
Vite normally creates:
⧉
1 | |
The generated directory contains files such as:
⧉
1 2 3 4 5 | |
One integration strategy is to configure Vite to place generated files in Django-controlled template and static directories.
For example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Django can then be configured to find the generated assets:
⧉
1 2 3 | |
A Django template can serve the React entry page, but Vite’s generated asset filenames are hashed. A robust integration normally uses one of these strategies:
- copy and transform
index.html - read Vite’s build manifest
- use a Django-Vite integration package
- configure a reverse proxy to serve the React build
- deploy the frontend separately
Manually hard-coding generated asset filenames is fragile because the names can change after each build.
Serving a Single-Page Application
A React single-page application may use client-side routes:
⧉
1 2 3 4 | |
When a user directly opens:
⧉
1 | |
the server must still return React’s index.html. React Router can then interpret the route in the browser.
This usually requires a fallback rule.
A reverse proxy such as Nginx may:
- send
/api/requests to Django - serve static frontend assets directly
- return
index.htmlfor unmatched frontend routes
A conceptual configuration is:
⧉
1 2 3 4 | |
Do not send API or admin routes to the React fallback.
Static and Uploaded Media Files
React build files are static files.
User uploads are media files.
These should be treated separately.
Typical Django settings include:
⧉
1 2 3 4 5 | |
For production static assets, Django’s documented workflow is to run collectstatic and configure a web server or static-file service to serve the collected directory.
React may display uploaded media using URLs returned by the Django API:
⧉
1 2 3 4 5 | |
Development Commands
The Django backend and React frontend usually run in separate terminals.
Terminal one:
⧉
1 2 | |
Terminal two:
⧉
1 2 | |
The development workflow becomes:
⧉
1 2 3 4 5 6 7 | |
Common Problems
CORS Errors
Example browser message:
⧉
1 | |
Check:
django-cors-headersis installed"corsheaders"is inINSTALLED_APPSCorsMiddlewareis placed correctly- the exact frontend origin is in
CORS_ALLOWED_ORIGINS - the origin includes the correct scheme and port
- the Django server has been restarted
Do not confuse:
⧉
1 | |
with:
⧉
1 | |
Browsers treat them as different origins.
A 404 API Response
Verify:
- the project includes the app URLs
- the router registered the viewset
- the frontend uses the correct
/api/prefix - trailing slashes match Django’s URL configuration
- the object ID exists
React Receives HTML Instead of JSON
A response beginning with:
⧉
1 | |
usually means the request reached:
- a frontend fallback page
- a Django error page
- a login redirect
- the wrong server
- the wrong URL
Inspect the browser network panel and verify the response URL, status, and content type.
403 CSRF Verification Failed
When using session authentication, verify:
- the CSRF cookie was set
credentials: "include"is present- the
X-CSRFTokenheader is included - the React origin is trusted
- the request uses HTTPS in production
- the cookie settings match the deployment
Do not disable CSRF protection as the default fix.
Request Data Is Rejected
Inspect the JSON response from Django REST Framework.
A 400 Bad Request often contains useful serializer errors:
⧉
1 2 3 4 5 | |
React should expose these messages to the user or developer.
Changes Do Not Appear
Check that:
- both development servers are running
- React is calling the intended backend
- the API base URL is correct
- the browser is not using stale data
- state is updated after successful requests
- the Django database contains the expected records
Common Beginner Mistakes
Putting Database Logic in React
React should not connect directly to the database.
Use:
⧉
1 | |
Django should enforce validation, permissions, and business rules.
Trusting Frontend Validation
React validation improves the user experience, but it is not a security boundary.
A user can submit requests without using the React interface.
Always validate important data in Django.
Allowing Every CORS Origin
Avoid using:
⧉
1 | |
as a permanent production solution.
Allow only the origins that need access.
Hard-Coding Development URLs Everywhere
Avoid repeating:
⧉
1 | |
throughout components.
Use one API module and an environment variable.
Ignoring Request Failures
Do not assume every request succeeds:
⧉
1 2 | |
Check:
⧉
1 2 3 4 5 | |
Treating CORS as Authentication
CORS does not decide whether a user is allowed to access an API.
It controls browser cross-origin access.
The API still needs:
- authentication
- permissions
- object ownership checks
- validation
Exposing Secrets in React
Do not include secrets in:
- React source files
VITE_environment variables- compiled JavaScript
- browser storage
Anything sent to the browser should be considered visible to the user.
Returning All Database Objects
Avoid:
⧉
1 | |
for private user-owned data.
Filter by the current user:
⧉
1 2 3 4 | |
Disabling CSRF Without Understanding It
Avoid adding csrf_exempt merely to make a request work.
Determine whether the application uses:
- session authentication and CSRF
- token authentication
- another deliberate authentication method
Recommended Project Boundary
A useful separation is:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
Some validation may exist in both places.
For example, React can immediately warn that a title is empty, while Django must still reject an empty title if someone bypasses the React interface.
Basic Integration Checklist
⧉
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 | |
Mini Reference
⧉
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 | |
Basic backend request:
⧉
1 | |
Basic React request:
⧉
1 2 3 4 5 | |
Basic API viewset:
⧉
1 2 3 | |
Basic CORS configuration:
⧉
1 2 3 | |
A common Django and React architecture uses Django as an API backend and React as a browser frontend.
The main integration steps are:
- Create the Django models.
- Expose the data through Django REST Framework.
- Create the React application with Vite.
- Request the API from React.
- Configure CORS when the applications use different origins.
- add authentication, permissions, and CSRF handling.
- Choose a production deployment strategy.
The most important principle is that React should not replace Django’s server-side responsibilities.
React controls the interface. Django remains responsible for data integrity, authentication, permissions, and business rules.
For a small application, start with a simple JSON API and a few React components. Add routers, authentication methods, deployment tooling, and more advanced state management only when the project has a clear need for them.
Join the Newsletter
Practical insights on Django, backend systems, deployment, architecture, and real-world development — delivered without noise.
Get updates when new guides, learning paths, cheat sheets, and field notes are published.
No spam. Unsubscribe anytime.
There is no third-party involved so don't worry - we won't share your details with anyone.