Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
May 19, 2022 05:09 pm GMT

Build a CRUD application using Django, React & Docker - 2022

As a developer, CRUD operations are one of the most fundamental concepts. Today, we'll learn 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.

Project Setup

Firstly, we must set up the development environment. Pick up your favorite terminal and make sure you have virtualenv installed.
Once its done, create an environment and install Django and Django rest framework.

virtualenv --python=/usr/bin/python3.10 venvsource venv/bin/activatepip install django django-rest-framework

After the installation of the packages, we can create the project and start working.

django-admin startproject restaurant .

Note: Dont forget the dot at the end of this command. It will generate the directories and files in the current directory instead of developing them in a new directory, restaurant.
To ensure that the project has been well initiated, try python manage.py runserver. And hit 127.0.0.1:8000.

Now lets create a Django app.

python manage.py startapp menu

So make sure to add the menu app and rest_framework in the INSTALLED_APPS in settings.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, well write Menu :

  • Model
  • Serializer
  • ViewSet
  • And finally, configure routes.

Model

The Menu model will only contain 5 fields.

#menu/models.pyfrom django.db import modelsclass Menu(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):        return self.name

Once its done, lets create a migration and apply it.

Migrations are Djangos way of propagating changes made to the models (adding a field, deleting a field, deleting a table, creating a table, etc.) into your database.

python manage.py makemigrationspython manage.py migrate

Serializers

Serializers allows us to convert complex Django complex data structures such as querysets or model instances in Python native objects that can be converted to JSON/XML format.
Well create a serializer to convert our data into JSON format.

#menu/serializers.pyfrom rest_framework import serializersfrom menu.models import Menuclass MenuSerializer(serializers.ModelSerializer):    class Meta:        model = Menu        fields = ['id', 'name', 'description', 'price', 'created', 'updated']  

Viewsets

Viewsets can be referred to 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.pyfrom rest_framework import viewsetsfrom menu.models import Menufrom menu.serializers import MenuSerializerclass MenuViewSet(viewsets.ModelViewSet):    serializer_class = MenuSerializer    def get_queryset(self):        return Menu.objects.all()

Great. We have the logic set, but we must add the API endpoints.

First, create a file, routers.py.

#./routers.pyfrom rest_framework import routersfrom menu.viewsets import MenuViewSetrouter = routers.SimpleRouter()router.register(r'menu', MenuViewSet, basename='menu')#restaurant/urls.pyfrom django.contrib import adminfrom django.urls import path, includefrom routers import routerurlpatterns = [    # path('admin/', admin.site.urls),    path('api/', include((router.urls, 'restaurant'), namespace='restaurant'))]

If you havent started your server yet.

python manage.py runserver

Then hit http://127.0.0.1:8000/api/menu/ in your browser.
Your browsable API is ready.

Lets add CORS responses. Adding CORS headers allows other domains to access the API ressources.

    pip install django-cors-headers

Then, add it to the INSTALLED_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',    ...]

We'll allow requests coming from localhost:3000 and 127.0.0.1:3000 because the frontend React server will run at these addresses.

# restaurant/settings.py# CORS HEADERSCORS_ALLOWED_ORIGINS = [    'http://127.0.0.1:3000',    'http://localhost:3000']

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 open http://localhost:3000/ to check the running application.
We can now add 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 prototype an app without writing too much CSS
  • react-router-dom : a React library for routes in our application.

Inside the src/ folder, ensure you have the following files and directories.

Directory image

In the src/components/ directory, we have three components :

  • AddMenu.js
  • UpdateMenu.js
  • MenuList.js

And in src/services/ directory, create menu.service.js and the following lines :

    export const baseURL = "http://localhost:8000/api";    export const headers = {      "Content-type": "application/json",    };

Make sure to import react-router-dom in your index.js file and wrap App in BrowserRouter object.

import React from "react";import ReactDOM from "react-dom/client";import { BrowserRouter } from "react-router-dom";import "./index.css";import App from "./App";const root = ReactDOM.createRoot(document.getElementById("root"));root.render(  <React.StrictMode>    <BrowserRouter>      <App />    </BrowserRouter>  </React.StrictMode>);

Once its done, we can change the App.js file by importing bootstrap, writing routes, and building the home page and the navigation bar.

```javascriptimport React from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import { Routes, Route, Link } from "react-router-dom";

import { AddMenu } from "./components/AddMenu";
import { MenuList } from "./components/MenuList";
import { UpdateMenu } from "./components/UpdateMenu";

function App() {
return (




Restaurant Menu


  • Add a menu


  •   <div className="container m-10">

    // Adding the routes



    );
    }

    export default App;

    Well need to write the routes that should map to a component we created.```javascript<div className="container m-10">  <Routes>    <Route path="/" element={<MenuList />} />    <Route path="/add/" element={<AddMenu />} />    <Route path="/menu/:id/update/" element={<UpdateMenu />} />  </Routes></div>

    The next step is to write the CRUD logic and the HTML for our components.
    Lets start by listing the menu from the API in MenuList.js.

    For this script, well 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 using setMenus .
    • deleteMenu()to delete a menu and set the deleted state to true, 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.
    import axios from "axios";import React, { useState, useEffect } from "react";import { baseURL, headers } from "./../services/menu.service";import { useNavigate } from "react-router-dom";export const MenuList = () => {  const [menus, setMenus] = useState([]);  const navigate = useNavigate();  const [deleted, setDeleted] = useState(false);  const retrieveAllMenus = () => {    axios      .get(`${baseURL}/menu/`, {        headers: {          headers,        },      })      .then((response) => {        setMenus(response.data);        console.log(menus);      })      .catch((e) => {        console.error(e);      });  };  const deleteMenu = (id) => {    axios      .delete(`${baseURL}/menu/${id}/`, {        headers: {          headers,        },      })      .then((response) => {        setDeleted(true);        retrieveAllMenus();      })      .catch((e) => {        console.error(e);      });  };  useEffect(() => {    retrieveAllMenus();  }, [retrieveAllMenus]);  const handleUpdateClick = (id) => {    navigate(`/menu/${id}/update/`);  };    return (        // ...      );    };

    Once its done, lets put in place the return() method:

        <div className="row justify-content-center">          <div className="col">            {deleted && (              <div                className="alert alert-danger alert-dismissible fade show"                role="alert"              >                Menu deleted!                <button                  type="button"                  className="close"                  data-dismiss="alert"                  aria-label="Close"                >                  <span aria-hidden="true">&times;</span>                </button>              </div>            )}            {menus &&              menus.map((menu, index) => (                <div className="card my-3 w-25 mx-auto">                  <div className="card-body">                    <h2 className="card-title font-weight-bold">{menu.name}</h2>                    <h4 className="card-subtitle mb-2">{menu.price}</h4>                    <p className="card-text">{menu.description}</p>                  </div>                  <div classNameName="card-footer">                    <div                      className="btn-group justify-content-around w-75 mb-1 "                      data-toggle="buttons"                    >                      <span>                        <button                          className="btn btn-info"                          onClick={() => handleUpdateClick(menu.id)}                        >                          Update                        </button>                      </span>                      <span>                        <button                          className="btn btn-danger"                          onClick={() => deleteMenu(menu.id)}                        >                          Delete                        </button>                      </span>                    </div>                  </div>                </div>              ))}          </div>        </div>

    Add a menu

    The AddMenu.js component has a Form to submit a new menu. It contains three fields : name, description & price .

    import axios from "axios";import React, { useState } from "react";import { baseURL, headers } from "./../services/menu.service";export const AddMenu = () => {  const initialMenuState = {    id: null,    name: "",    description: "",    price: 0,  };  const [menu, setMenu] = useState(initialMenuState);  const [submitted, setSubmitted] = useState(false);  const handleMenuChange = (e) => {    const { name, value } = e.target;    setMenu({ ...menu, [name]: value });  };  const submitMenu = () => {    let data = {      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);      });  };  const newMenu = () => {    setMenu(initialMenuState);    setSubmitted(false);  };    return (        // ...      );    };

    For this script, well have two states :

    • menu, which will contain by default the value of initialMenuState object
    • submitted will contain a Boolean object to show a message when a menu is added.

    And three methods :

    • handleInputChange() to track the input value and set the state for change.
    • saveMenu()to send a POST request to the API.
    • newMenu() allows the user to add a new menu again once the success message has been shown.
    <div className="submit-form">  {submitted ? (    <div>      <div        className="alert alert-success alert-dismissible fade show"        role="alert"      >        Menu Added!        <button          type="button"          className="close"          data-dismiss="alert"          aria-label="Close"        >          <span aria-hidden="true">&times;</span>        </button>      </div>      <button className="btn btn-success" onClick={newMenu}>        Add      </button>    </div>  ) : (    <div>      <div className="form-group">        <label htmlFor="name">Name</label>        <input          type="text"          className="form-control"          id="name"          required          value={menu.name}          onChange={handleMenuChange}          name="name"        />      </div>      <div className="form-group">        <label htmlFor="description">Description</label>        <input          type="text"          className="form-control"          id="description"          required          value={menu.description}          onChange={handleMenuChange}          name="description"        />      </div>      <div className="form-group">        <label htmlFor="price">Price</label>        <input          type="number"          className="form-control"          id="price"          required          value={menu.price}          onChange={handleMenuChange}          name="price"        />      </div>      <button        type="submit"        onClick={submitMenu}        className="btn btn-success mt-2"      >        Submit      </button>    </div>  )}</div>

    Update a Menu

    The component will be a little bit identical to AddMenu component. But, it will contain a get method to retrieve the object's current value by making a GET request to the API with the id of the object.
    We use the useHistory() hook to pass the id to the UpdateMenu component and retrieve it with useParams hook.

    import axios from "axios";import React, { useState, useEffect } from "react";import { useParams } from "react-router-dom";import { baseURL, headers } from "./../services/menu.service";export const UpdateMenu = () => {  const initialMenuState = {    id: null,    name: "",    description: "",    price: 0,  };  const { id } = useParams();  const [currentMenu, setCurrentMenu] = useState(initialMenuState);  const [submitted, setSubmitted] = useState(false);  useEffect(() => {    retrieveMenu();  }, []);  const handleMenuChange = (e) => {    const { name, value } = e.target;    setCurrentMenu({ ...currentMenu, [name]: value });  };  const retrieveMenu = () => {    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);      });  };  const updateMenu = () => {    let data = {      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);      });  };  const newMenu = () => {    setCurrentMenu(initialMenuState);    setSubmitted(false);  };  return (          // ...      );    };

    And this is the code inside the return :

    <div className="submit-form">  {submitted ? (    <div>      <div        className="alert alert-success alert-dismissible fade show"        role="alert"      >        Menu Updated!        <button          type="button"          className="close"          data-dismiss="alert"          aria-label="Close"        >          <span aria-hidden="true">&times;</span>        </button>      </div>      <button className="btn btn-success" onClick={newMenu}>        Update      </button>    </div>  ) : (    <div>      <div className="form-group">        <label htmlFor="name">Name</label>        <input          type="text"          className="form-control"          id="name"          required          value={currentMenu.name}          onChange={handleMenuChange}          name="name"        />      </div>      <div className="form-group">        <label htmlFor="description">Description</label>        <input          type="text"          className="form-control"          id="description"          required          value={currentMenu.description}          onChange={handleMenuChange}          name="description"          default        />      </div>      <div className="form-group">        <label htmlFor="price">Price</label>        <input          type="number"          className="form-control"          id="price"          required          value={currentMenu.price}          onChange={handleMenuChange}          name="price"        />      </div>      <button onClick={updateMenu} className="btn btn-success">        Submit      </button>    </div>  )}</div>

    And we are set now.

    If you click on Update button on a menu card, youll be redirected to a new page, with this component, with the default values in the fields.
    Check the demo

    Docker build (Optional)

    Docker + Docker Compose (Optional)

    Docker is an open platform for developing, shipping, and running applications inside containers.

    Why use Docker?
    It helps you separate your applications from your infrastructure and helps in delivering code faster.

    If it's your first time working with Docker, I highly recommend you go through a quick tutorial and read some documentation about it.

    Here are some great resources that helped me:

    Docker configuration for the API

    The Dockerfile represents a text document containing all the commands that could call on the command line to create an image.

    Add a Dockerfile at the root of the Django project:

    # pull official base imageFROM python:3.10-alpine# set work directoryWORKDIR /app# set environment variablesENV PYTHONDONTWRITEBYTECODE 1ENV PYTHONUNBUFFERED 1# install psycopg2 dependenciesRUN apk update \ && apk add gcc python3-dev# install python dependenciesCOPY requirements.txt /app/requirements.txtRUN pip install --upgrade pipRUN pip install --no-cache-dir -r requirements.txt# copy projectCOPY . .

    Here, we started with an Alpine-based Docker Image for Python. It's a lightweight Linux distribution designed for security and resource efficiency.
    After that, we set a working directory followed by two environment variables:

    1 - PYTHONDONTWRITEBYTECODE to prevent Python from writing .pyc files to disc
    2 - PYTHONUNBUFFERED to prevent Python from buffering stdout and stderr

    After that, we perform operations like:

    • Setting up environment variables
    • Installing the PostgreSQL server package
    • Copying their requirements.txt file to the app path, upgrading pip, and installing the python package to run our application
    • And last copying the entire project

    Also, let's add a .dockerignore file.

    envvenvDockerfile

    Docker Compose for the API

    Docker Compose is a great tool (<3). You can use it to define and run multi-container Docker applications.

    What do we need? Well, just a YAML file containing all the configuration of our application's services.
    Then, with the docker-compose command, we can create and start all those services.

    This file will be used for development.

    version: '3.9'services: api: container_name: menu_api build: . restart: always env_file: .env ports:      - "8000:8000" command: >      sh -c " python manage.py migrate &&          gunicorn restaurant.wsgi:application --bind 0.0.0.0:8000" volumes:     - .:/app

    Let's add gunicorn and some configurations before building our image.

    pip install gunicorn

    And add it as a requirement as well in the requirements.txt.
    Here's what my requirements.txt file looks like :

    django==4.0.4django-cors-headers==3.12.0djangorestframework==3.13.1gunicorn==20.1.0

    The setup is completed. Let's build our containers and test if everything works locally.

    docker-compose up -d --build

    Your project will be running on https://localhost:8000/.

    Dockerfile for the React App

    Add a Dockerfile at the root of the React project:

    FROM node:17-alpineWORKDIR /appCOPY package.json ./COPY yarn.lock ./RUN yarn install --frozen-lockfileCOPY . .

    Here, we started with an Alpine-based Docker Image for JavaScript. It's a lightweight Linux distribution designed for security and resource efficiency.

    Also, let's add a .dockerignore file.

    node_modulesnpm-debug.logDockerfileyarn-error.log

    And let's add the code for the docker-compose.yaml.

    version: "3.9"services: react-app: container_name: react_app restart: on-failure build: . volumes:      - ./src:/app/src ports:      - "3000:3000" command: >      sh -c "yarn start"

    The setup is completed. Let's build our containers and test if everything works locally.

    docker-compose up -d --build 

    Your project will be running on https://localhost:3000/. And voil! We've dockerized the API and the React applications.

    Conclusion

    In this article, We learned to build a CRUD application web with Django and React. And as every article can be made better, your suggestion or questions are welcome in the comment section.
    Check the code of all these articles in this repo.

    This article has been originally posted on my blog


    Original Link: https://dev.to/koladev/build-a-crud-application-using-django-react-docker-2022-11f4

    Share this article:    Share on Facebook
    View Full Article

    Dev To

    An online community for sharing and discovering great ideas, having debates, and making friends

    More About this Source Visit Dev To