Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Build a CRUD application using Django and React
Mangabo Kolawole
Mangabo KolawoleSubscriber

Posted on • Edited on

     

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
Enter fullscreen modeExit fullscreen mode

Once the installation is finished, we can now create the project and start working.

django-admin startproject restaurant .
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

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']
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

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']
Enter fullscreen modeExit fullscreen mode

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()
Enter fullscreen modeExit fullscreen mode

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'))]
Enter fullscreen modeExit fullscreen mode

If you haven’t started you server yet.

python manage.py runserver
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

Then, add it to theINSTALLED_APPS.

# restaurant/settings.pyINSTALLED_APPS=[...'corsheaders',...]
Enter fullscreen modeExit fullscreen mode

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',...]
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

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",]
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

Then openhttp://localhost:3000/ to see your app.
We can add now the dependencies of this project.

yarn add axios bootstrap react-router-dom
Enter fullscreen modeExit fullscreen mode

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.

Directory image

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",};
Enter fullscreen modeExit fullscreen mode

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"));
Enter fullscreen modeExit fullscreen mode

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;
Enter fullscreen modeExit fullscreen mode

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>
Enter fullscreen modeExit fullscreen mode

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 API
  • deleted 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(// ...);};
Enter fullscreen modeExit fullscreen mode

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">&times;</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>
Enter fullscreen modeExit fullscreen mode

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(// ...);};
Enter fullscreen modeExit fullscreen mode

For this script, we’ll have two states :

  • menu which will contain by default the value ofinitialMenuState object
  • submitted 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">&times;</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>
Enter fullscreen modeExit fullscreen mode

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(// ...);};
Enter fullscreen modeExit fullscreen mode

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">&times;</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>
Enter fullscreen modeExit fullscreen mode

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.
Check the demo

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)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
mastacheata profile image
Mastacheata
Software engineer with focus on web from GermanyEspecially into ReactJS/Redux
  • Location
    Cologne, Germany
  • Education
    M.Eng. Information Systems Engineering
  • Work
    Senior 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.

CollapseExpand
 
koladev profile image
Mangabo Kolawole
Software Engineer | Technical Writer | Book Author
  • Email
  • Location
    Remote
  • Education
    Studied CS
  • Work
    Software 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. 😉

CollapseExpand
 
adir1661 profile image
adir abargil
  • Joined

It might be a good idea to update the react router to its new version :)

CollapseExpand
 
koladev profile image
Mangabo Kolawole
Software Engineer | Technical Writer | Book Author
  • Email
  • Location
    Remote
  • Education
    Studied CS
  • Work
    Software Engineer
  • Joined

Great idea! Ty

CollapseExpand
 
mrtwister96 profile image
Schalk Olivier
  • Joined

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 ?

CollapseExpand
 
koladev profile image
Mangabo Kolawole
Software Engineer | Technical Writer | Book Author
  • Email
  • Location
    Remote
  • Education
    Studied CS
  • Work
    Software Engineer
  • Joined

A little bit strange. Have you install the CORS package on the Django server side?

CollapseExpand
 
danielphilip12 profile image
danielphilip12
  • Education
    University of Washington
  • Joined
• Edited on• Edited

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.

CollapseExpand
 
adii9 profile image
Aditya Mathur
Full stack developer building amazing mobile & web applications & passionate about writing technical blogs.
  • Location
    India
  • Work
    Full 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__'
Enter fullscreen modeExit fullscreen mode
CollapseExpand
 
furtleo profile image
Leonardo Furtado
Currently learning more about Go, tests and devops.

Still stuck with cors problem

CollapseExpand
 
koladev profile image
Mangabo Kolawole
Software Engineer | Technical Writer | Book Author
  • Email
  • Location
    Remote
  • Education
    Studied CS
  • Work
    Software Engineer
  • Joined

Can you show me your CORS configuration onsettings.py?

Also, on which address the react server is running?

CollapseExpand
 
lorenepecci profile image
Lorene Pecci
  • Education
    Trybe
  • Joined

Hi, I'm starting to code and I want to know why do you use django instead of node.js? Thanks.

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Software Engineer | Technical Writer | Book Author
  • Location
    Remote
  • Education
    Studied CS
  • Work
    Software Engineer
  • Joined

More fromMangabo Kolawole

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp