
Build a CRUD application using Django and React
As a developer, CRUD operations are one of the most basic concepts to know. Today, I'll show you how to build a REST API using Django and Django Rest and a SPA with React, which we'll use to perform the CRUD operations.
Requirements
For this tutorial, you’ll need to have a basics understanding of Django models, Django Restserializers, andViewSets.
Project Setup
First of all, we must set up the development environment. Pick up your favorite terminal and make sure you havevirtualenv installed.
Once it’s done, create an environment and install Django and Django rest framework.
virtualenv --python=/usr/bin/python3.8 venvsource venv/bin/activatepip install django django-rest-framework
Once the installation is finished, we can now create the project and start working.
django-admin startproject restaurant .
Note: Don’t forget the dot at the end of this command. It will generate the directories and files in the current directory instead of generating them in a new directoryrestaurant
.
To make sure that the project has been well initiated, trypython manage.py runserver
. And hit127.0.0.1:8000
.
Now let’s create a Django app.
python manage.py startapp menu
So make sure to add themenu
app andrest_framework
in theINSTALLED_APPS
insettings.py
file.
#restaurant/settings.pyINSTALLED_APPS=['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','rest_framework','menu']
Good. We can start working on the logic we want to achieve in this tutorial. So, we’ll writeMenu
:
- Model
- Serializer
- ViewSet
- And finally, configure routes.
Model
TheMenu
model will only contain 5 fields.
#menu/models.pyfromdjango.dbimportmodelsclassMenu(models.Model):name=models.CharField(max_length=255)description=models.TextField()price=models.IntegerField()created=models.DateTimeField(auto_now_add=True)updated=models.DateTimeField(auto_now=True)def__str__(self):returnself.name
Once it’s done, let’s create a migration and then apply it.
Migrations are Django’s way of propagating changes you make to your models (adding a field, deleting a model, etc.) into your database schema.
python manage.py makemigrationspython manage.py migrate
Serializers
Serializers allows us to convert complex Django complex data structures such asquerysets
or model instances in Python native objects that can be easily converted JSON/XML format.
Here, we’ll create a serializer to convert our data into JSON format.
#menu/serializers.pyfromrest_frameworkimportserializersfrommenu.modelsimportMenuclassMenuSerializer(serializers.ModelSerializer):classMeta:model=Menufields=['id','name','description','price','created','updated']
Viewsets
Viewsets here can be referred to in Django as Controllers if you are coming from another framework.
ViewSet is a concept developed by DRF which consists of grouping a set of views for a given model in a single Python class. This set of views corresponds to the predefined actions of CRUD type (Create, Read, Update, Delete), associated with HTTP methods. Each of these actions is a ViewSet instance method. Among these default actions, we find:
- list
- retrieve
- update
- destroy
- partial_update
- create
#menu/viewsets.pyfromrest_frameworkimportviewsetsfrommenu.modelsimportMenufrommenu.serializersimportMenuSerializerclassMenuViewSet(viewsets.ModelViewSet):serializer_class=MenuSerializerdefget_queryset(self):returnMenu.objects.all()
Great. We have the logic set, but we must add the API endpoints.
First create a file,routers.py
.
#./routers.pyfromrest_frameworkimportroutersfrommenu.viewsetsimportMenuViewSetrouter=routers.SimpleRouter()router.register(r'menu',MenuViewSet,basename='menu')#restaurant/urls.pyfromdjango.contribimportadminfromdjango.urlsimportpath,includefromroutersimportrouterurlpatterns=[# path('admin/', admin.site.urls),path('api/',include((router.urls,'restaurant'),namespace='restaurant'))]
If you haven’t started you server yet.
python manage.py runserver
Then hithttp://127.0.0.1:8000/api/menu/
in your browser.
Your browsable API is ready. 🙂
Let’s add CORS responses. Adding CORS headers allows your resources to be accessed on other domains.
pip install django-cors-headers
Then, add it to theINSTALLED_APPS
.
# restaurant/settings.pyINSTALLED_APPS=[...'corsheaders',...]
You will also need to add a middleware class to listen in on responses.
#restaurant/settings.pyMIDDLEWARE=['corsheaders.middleware.CorsMiddleware','django.middleware.security.SecurityMiddleware','django.contrib.sessions.middleware.SessionMiddleware','django.middleware.common.CommonMiddleware',...]
For this tutorial, we’ll allow all origins to make cross-site HTTP requests.
However, it’s dangerous and you should never do this in production.
# restaurant/settings.pyCORS_ORIGIN_ALLOW_ALL=True
In a production environment, you can useCORS_ALLOWED_ORIGINS
instead.
CORS_ALLOWED_ORIGINS=["https://example.com","https://sub.example.com","http://localhost:3000","http://127.0.0.1:3000",]
For more information, you can refer to thedocumentation.
React.js CRUD REST API Consumption
Make sure you have the latest version of create-react-app installed.
yarn create-react-app restaurant-menu-frontcd restaurant-menu-frontyarn start
Then openhttp://localhost:3000/ to see your app.
We can add now the dependencies of this project.
yarn add axios bootstrap react-router-dom
With this line of command, we installed :
- axios : a promised based HTTP client
- bootstrap: a library to quickly prototype an app without writing too much CSS
- react-router-dom : a React library for routes in our application.
Inside thesrc/
folder, make sure you have the following files and directories.
In thesrc/components/
directory, we have three components :
AddMenu.js
UpdateMenu.js
MenuList.js
And insrc/services/
directory, createmenu.service.js
and the following lines :
exportconstbaseURL="http://localhost:8000/api";exportconstheaders={"Content-type":"application/json",};
Make sure to importreact-router-dom
in yourindex.js
file and wrapApp
inBrowserRouter
object.
importReactfrom"react";importReactDOMfrom"react-dom";import{BrowserRouter}from"react-router-dom";import"./index.css";importAppfrom"./App";ReactDOM.render(<BrowserRouter><App/></BrowserRouter>,document.getElementById("root"));
Once it’s done, we can modify theApp.js
file by importingbootstrap
, writing routes, and build the home page and the navigation bar.
importReactfrom"react";import"bootstrap/dist/css/bootstrap.min.css";import{Switch,Route,Link}from"react-router-dom";import{AddMenu}from"./components/AddMenu";import{MenuList}from"./components/MenuList";import{UpdateMenu}from"./components/UpdateMenu";functionApp(){return(<div><navclassName="navbar navbar-expand navbar-dark bg-info"><ahref="/"className="navbar-brand">RestaurantMenu</a><divclassName="navbar-nav mr-auto"><liclassName="nav-item"><Linkexactto={"/add/"}className="nav-link">Add</Link></li></div></nav><divclassName="container m-10">// Add the routes</div></div>);}exportdefaultApp;
The navbar is done and we have imported bootstrap and the components we’ll need to write the routes that should map to a component we created.
<Switch><Routeexactpath={["/","/menus"]}component={MenuList}/><Routeexactpath="/add/"component={AddMenu}/><Routepath="/menu/:id/update/"component={UpdateMenu}/></Switch>
The next step is to write the CRUD logic and the HTML for our components.
Let’s start by listing the menu from the API inMenuList.js
.
For this script, we’ll have two states :
menus
which will store the response object from the APIdeleted
that will contain a Boolean object to show a message
And three methods :
retrieveAllMenus()
to retrieve all menus from the API and set the response objects in menus by usingsetMenus
.deleteMenu()
to delete a menu and set thedeleted
state totrue
, which will help us show a simple message every time a menu is deleted.handleUpdateClick()
to navigate to a new page to update a menu.
importaxiosfrom"axios";importReact,{useState,useEffect,useRef}from"react";import{baseURL,headers}from"./../services/menu.service";import{useHistory}from"react-router-dom";exportconstMenuList=()=>{const[menus,setMenus]=useState([]);consthistory=useHistory();constcountRef=useRef(0);const[deleted,setDeleted]=useState(false);useEffect(()=>{retrieveAllMenus();},[countRef]);constretrieveAllMenus=()=>{axios.get(`${baseURL}/menu/`,{headers:{headers,},}).then((response)=>{setMenus(response.data);}).catch((e)=>{console.error(e);});};constdeleteMenu=(id)=>{axios.delete(`${baseURL}/menu/${id}/`,{headers:{headers,},}).then((response)=>{setDeleted(true);retrieveAllMenus();}).catch((e)=>{console.error(e);});};consthandleUpdateClick=(id)=>{history.push(`/menu/${id}/update/`);};return(// ...);};
Once it’s done, let’s implement therender()
method:
<divclassName="row justify-content-center"><divclassName="col"> {deleted&& (<divclassName="alert alert-danger alert-dismissible fade show"role="alert"> Menu deleted!<buttontype="button"className="close"data-dismiss="alert"aria-label="Close"><spanaria-hidden="true">×</span></button></div> )} {menus&& menus.map((menu, index) => (<divclassName="card my-3 w-25 mx-auto"><divclassName="card-body"><h2className="card-title font-weight-bold">{menu.name}</h2><h4className="card-subtitle mb-2">{menu.price}</h4><pclassName="card-text">{menu.description}</p></div><divclassNameName="card-footer"><divclassName="btn-group justify-content-around w-75 mb-1 "data-toggle="buttons"><span><buttonclassName="btn btn-info"onClick={()=> handleUpdateClick(menu.id)} > Update</button></span><span><buttonclassName="btn btn-danger"onClick={()=> deleteMenu(menu.id)} > Delete</button></span></div></div></div> ))}</div></div>
Add a menu
TheAddMenu.js
component has a Form to submit a new menu. It contains three fields :name
,description
&price
.
importaxiosfrom"axios";importReact,{useState}from"react";import{baseURL,headers}from"./../services/menu.service";exportconstAddMenu=()=>{constinitialMenuState={id:null,name:"",description:"",price:0,};const[menu,setMenu]=useState(initialMenuState);const[submitted,setSubmitted]=useState(false);consthandleMenuChange=(e)=>{const{name,value}=e.target;setMenu({...menu,[name]:value});};constsubmitMenu=()=>{letdata={name:menu.name,description:menu.description,price:menu.price,};axios.post(`${baseURL}/menu/`,data,{headers:{headers,},}).then((response)=>{setMenu({id:response.data.id,name:response.data.name,description:response.data.description,price:response.data.price,});setSubmitted(true);console.log(response.data);}).catch((e)=>{console.error(e);});};constnewMenu=()=>{setMenu(initialMenuState);setSubmitted(false);};return(// ...);};
For this script, we’ll have two states :
menu
which will contain by default the value ofinitialMenuState
objectsubmitted
that will contain a Boolean object to show a message when a menu is successfully added.
And three methods :
handleInputChange()
to track the value of the input and set the state for change.saveMenu()
to send aPOST
request to the API.newMenu()
to allow the user to add a new menu again once the success message has been shown.
<divclassName="submit-form"> {submitted ? (<div><divclassName="alert alert-success alert-dismissible fade show"role="alert"> Menu Added!<buttontype="button"className="close"data-dismiss="alert"aria-label="Close"><spanaria-hidden="true">×</span></button></div><buttonclassName="btn btn-success"onClick={newMenu}> Add</button></div> ) : (<div><divclassName="form-group"><labelhtmlFor="name">Name</label><inputtype="text"className="form-control"id="name"requiredvalue={menu.name}onChange={handleMenuChange}name="name"/></div><divclassName="form-group"><labelhtmlFor="description">Description</label><inputtype="text"className="form-control"id="description"requiredvalue={menu.description}onChange={handleMenuChange}name="description"/></div><divclassName="form-group"><labelhtmlFor="price">Price</label><inputtype="number"className="form-control"id="price"requiredvalue={menu.price}onChange={handleMenuChange}name="price"/></div><buttononClick={submitMenu}className="btn btn-success"> Submit</button></div> )}</div>
Update a Menu
The component will be a little bit identical toAddMenu
component, however, it will contain a get method to retrieve the current value of the object by making aGET
request to the API with theid
of the object.
We use theuseHistory()
hook to pass theid
to theUpdateMenu
component and retrieve it withuseParams
hook.
importaxiosfrom"axios";importReact,{useState,useEffect,useRef}from"react";import{useParams}from"react-router-dom";import{baseURL,headers}from"./../services/menu.service";exportconstUpdateMenu=()=>{constinitialMenuState={id:null,name:"",description:"",price:0,};let{id}=useParams();const[currentMenu,setCurrentMenu]=useState(initialMenuState);const[submitted,setSubmitted]=useState(false);constcountRef=useRef(0);useEffect(()=>{retrieveMenu();},[countRef]);consthandleMenuChange=(e)=>{const{name,value}=e.target;setCurrentMenu({...currentMenu,[name]:value});};constretrieveMenu=()=>{axios.get(`${baseURL}/menu/${id}/`,{headers:{headers,},}).then((response)=>{setCurrentMenu({id:response.data.id,name:response.data.name,description:response.data.description,price:response.data.price,});console.log(currentMenu);}).catch((e)=>{console.error(e);});};constupdateMenu=()=>{letdata={name:currentMenu.name,description:currentMenu.description,price:currentMenu.price,};axios.put(`${baseURL}/menu/${id}/`,data,{headers:{headers,},}).then((response)=>{setCurrentMenu({id:response.data.id,name:response.data.name,description:response.data.description,price:response.data.price,});setSubmitted(true);console.log(response.data);}).catch((e)=>{console.error(e);});};constnewMenu=()=>{setCurrentMenu(initialMenuState);setSubmitted(false);};return(// ...);};
And this is the code inside thereturn
:
<divclassName="submit-form"> {submitted ? (<div><divclassName="alert alert-success alert-dismissible fade show"role="alert"> Menu Updated!<buttontype="button"className="close"data-dismiss="alert"aria-label="Close"><spanaria-hidden="true">×</span></button></div><buttonclassName="btn btn-success"onClick={newMenu}> Update</button></div> ) : (<div><divclassName="form-group"><labelhtmlFor="name">Name</label><inputtype="text"className="form-control"id="name"requiredvalue={currentMenu.name}onChange={handleMenuChange}name="name"/></div><divclassName="form-group"><labelhtmlFor="description">Description</label><inputtype="text"className="form-control"id="description"requiredvalue={currentMenu.description}onChange={handleMenuChange}name="description"default/></div><divclassName="form-group"><labelhtmlFor="price">Price</label><inputtype="number"className="form-control"id="price"requiredvalue={currentMenu.price}onChange={handleMenuChange}name="price"/></div><buttononClick={updateMenu}className="btn btn-success"> Submit</button></div> )}</div>
And we are set now.
If you click onUpdate
button on a menu card, you’ll be redirected to a new page, with this component, with the default values in the fields.
Conclusion
In this article, We learned to build a CRUD application web with Django and React. And as every article can be made better so your suggestion or questions are welcome in the comment section. 😉
Check the code of all this article in thisrepo.
This article has been originally posted on myblog
Top comments(11)

- LocationCologne, Germany
- EducationM.Eng. Information Systems Engineering
- WorkSenior Software engineer at Ambient Innovation GmbH
- Joined
As someone with a security research background it really hurts to see you noticed the cors problem, but still used the bad solution anyways.
My suggestion for the future: implement a secure version in your tutorials and point people at the documentation for how to change it.
Bonus points: You might get more interaction with your tutorials when people who modify it get stuck at the security and ask you for what they might have done wrong 😉
Other than that this is a really great tutorial for getting started with react and drf.
I've learned both on the job and only in an already existing project. I would've been glad for such an entry level tutorial 2 years ago.

- Email
- LocationRemote
- EducationStudied CS
- WorkSoftware Engineer
- Joined
Thanks for your comment.
And I knew it would embarrass some readers to see that I’ve consciously neglect this CORS problem. Actually I love your suggestion and I think that I’ll modify the article to fit it, because it will help beginners to learn too.
Thanks a lot. 😉

- Email
- LocationRemote
- EducationStudied CS
- WorkSoftware Engineer
- Joined
Great idea! Ty

Great tutorial thanks, I just get one small issue and I am not sure why.
when I include the headers on the axios requests:
...axios
.get(${baseURL}/menu/
, {
headers: {
headers,
},
})....
I get the below error:
Access to XMLHttpRequest at 'localhost:8000/api/menu/4/' from origin 'localhost:3000' has been blocked by CORS policy: Request header field headers is not allowed by Access-Control-Allow-Headers in preflight response.
As soon as I remove the headers from the axios requests:
...axios
.get(${baseURL}/menu/
)
.then(...
all is working 100%, what am I doing wrong lol ?

- Email
- LocationRemote
- EducationStudied CS
- WorkSoftware Engineer
- Joined
A little bit strange. Have you install the CORS package on the Django server side?

I got the same issue. When I changed the headers to not use the variable (and just put the "Content-type": "application/json" in directly), it worked.
This solution did not work for the add menu function with the post request. There, I had to remove the header completely to get it to work.

- LocationIndia
- WorkFull Stack Developer
- Joined
This is a great post!!
You can serialize all the fields without mentioning all of them in your serializer class like :
class MenuSerializer(serializers.ModelSerializer): class Meta: model = Menu fields = '__all__'

- Email
- LocationBrazil
- EducationFederal University of Pará
- WorkSoftware Engineer
- Joined
Still stuck with cors problem

- Email
- LocationRemote
- EducationStudied CS
- WorkSoftware Engineer
- Joined
Can you show me your CORS configuration onsettings.py
?
Also, on which address the react server is running?
For further actions, you may consider blocking this person and/orreporting abuse