Building a FullStack Application with Django, Django REST & Next.js
Tags: python, react, javascript, webdev
Published: 2024-01-26 11:51:44

Django and Nextjs are the one most used web frameworks for backend and frontend development. Django comes with a robust set of features, security, and flexibility that allows any developer to build simple yet complex applications in a few times. On the other hand, Nextjs is absolutely the preferred framework when it comes to developing a reactive frontend with React.

Django, a Python-based framework, is known for its "batteries-included" approach. It simplifies backend development so you can focus more on writing your app without needing to reinvent the wheel. On the other hand, Next.js elevates React-based frontends, offering features like server-side rendering for faster load times and better SEO. Together, they form a powerful duo for full-stack development.

In this article, we will learn how to build a fullstack application using Django as the backend to build a REST API and then create a nice and simple frontend with Nextjs to consume the data.

The application is a simple CRUD app to manage a restaurant's menu. From the frontend, the user should be able to:

* list all menus
    
* retrieve a menu
    
* create a menu
    
* update a menu
    
* delete a menu
    

At the end of this article, you will understand how to connect a Django application and a frontend application built with Nextjs.

## Setup

To start, let's set up our project with the necessary tools and technologies. We'll be using Python 3.11 and Django 4.2 for the backend of our application. These up-to-date versions will help ensure that our backend runs smoothly and securely.

For the frontend, we'll use Next.js 13 and Node 19.

In terms of styling, we’ll use plain CSS.

## Building the Django API

Using Django only, it is quite impossible to build a robust API. You can indeed return JSON data from the views function or classes of your application, but how do you deal with permissions, authentications, parsing, throttling, data serialization, and much more?

That is where the Django REST framework comes into play. It is a framework developed with the architecture of Django to help developers build powerful and robust REST APIs.

Without too much hesitation, let's create the Django application.

### Creating the application

Ensure that Python 3.11 is installed. In the terminal of your machine, run the following commands to create a working directory, the virtual environment, and then the project.

```bash
mkdir menu-api && cd menu-api
python3.11 -m venv venv

source venv/bin/activate

pip install django djangorestframework

django-admin startproject RestaurantCore .
```

Above, we just created a new Django project called `RestaurantCore`. You will notice a new directory and files in your current working directory.

The `RestaurantCore` contains files such as :

* `settings.py` that contains all configurations of the Django project. This is where we will add configurations for the Django rest-framework package and other packages.
    
* `urls.py` that contains all the URLs of the project.
    
* `wsgi` which is useful for running your Django application in development mode and also for deployment.
    

To allow the admin to make CRUD operations on menu objects, we need to add an application that will contain all the logic required to treat a request, serialize or deserialize the data, and finally save it in the database.

We are reusing the MVT architecture ( Model - View - Template ), but we are replacing the layers of views and templates with serializers and viewsets.

So let's start with adding the application.

### Adding the Menu application

In the current working directory, type the following command to create a new Django application.

```bash
django-admin startapp menu
```

Once you have executed this command, ensure to have the same structure of the directory as in the following image :

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1698270235541/2b31f0bd-0339-49c5-890f-1ab42f407f25.png)

After creating the Django application, we need to register the newly created application in the `INSTALLED_APPS` of the `settings.py` file of the Django project. We will also register the `rest_framework` application for the rest-framework package to actually work.

```python
# RestaurantCore/settings.py
...
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",

    #third apps
    "rest_framework",

    # installed apps

    "menu"
]
```

With the application added to the `INSTALLED_APPS` list, we can now start writing the `menu` Django application logic.

Let's start with the `models.py` file. Most of the time, this file contains a model which is a representation of a table in the database. Using the Django ORM, we do not need to write a single line of SQL to create a table and add fields.

```python
# menu/models.py

from django.db import models


class Menu(models.Model):
    name = models.CharField(max_length=255)
    price = models.FloatField()
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
```

The `Menu` model comes with fields such as :

* `name` for the name of the menu
    
* `price` for the price
    
* `created` for the date of creation of the object. With `auto_now_add` set to True, the data is automatically added at the creation.
    
* And finally `updated` for the date of update or modification of the object. With `auto_now` each time the object is saved, the date is **updated**.
    

The next step is to add a serializer. This will help Django convert JSON data to Python native objects seamlessly which can be dealt with more ease.

In the `menu` application, in the serializers.py file, add the following content

```python
from rest_framework import serializers

from menu.models import Menu


class MenuSerializer(serializers.ModelSerializer):

    class Meta:
        model = Menu
        fields = ['name', 'price', 'created', 'updated', 'id']
        read_only_fields = ['created', 'updated', 'id']
```

In the lines above, we are creating a serializer using the `ModelSerializer` class. The `ModelSerializer` class is a shortcut for adding a serializer for a model to deal with querysets and field validations seamlessly, thus no need to add our validation logic and error handling.

In this serializer, we are defining a `Meta` class and we set the models, the fields but also the read-only fields. These fields should not be modified with mutation requests for example.

Great! Now that we have a model serializer, then let's add the viewset to define the interface ( controller ) that will handle the requests. Create in the `menu` application folder, a file called `viewsets` and add the following.

```python
from rest_framework import viewsets
from rest_framework.permissions import AllowAny
from menu.models import Menu

from menu.serializers import MenuSerializer


class MenuViewSet(viewsets.ModelViewSet):
    queryset = Menu.objects.all()
    serializer_class = MenuSerializer
    permission_classes = [AllowAny]
```

In the code above, we are creating a new viewset class with the `ModelViewSet` class. Why use a viewset and not an API view? Well, viewsets already come with all the needed logic for CRUD operations such as listing, retrieving, updating, creating, and deleting. This helps us make the dev process faster and cleaner.

For this endpoint, we want to allow any users to make these CRUD operations. ( We will deal with authentication and permissions in the next article 😁 )

We have the viewset that can help with CRUD operations, we need to register the viewsets and expose the API endpoint to manage menus.

### Adding the Menu Endpoint

In the root directory of the Django project, create a file called `routers.py` . This file will contain all the endpoints of the API, in this case, the `/menu/` endpoint. Then, we will register these endpoints on the `urls.py` file of the Django application.

Let's start by writing the code for the `routers.py` file.

```python
# ./routers.py

from rest_framework import routers

from menu.viewsets import MenuViewSet

router = routers.SimpleRouter()

router.register(r'menu', MenuViewSet, basename="menu")

urlpatterns = router.urls
```

In the code above, here is what's happening:

* The `routers.SimpleRouter()` is used to create a simple default router that automatically generates URLs for a DRF `ViewSet`.
    
* `MenuViewSet` from `menu.viewsets` is registered with the router.
    
* The `basename` parameter in `router.register` is set to `"menu"`. This basename is used to construct the URL names for the `MenuViewSet`.
    
* Finally, `urlpatterns = router.urls` sets the urlpatterns for this part of the application to those generated by the router for the `MenuViewSet`.
    

We can now register the defined `router` URLs in the `urls.py` file of the Django project.

```python
# RestaurantCore/urls.py

from django.contrib import admin
from django.urls import path, include

from routers import router

urlpatterns = [
    path("admin/", admin.site.urls),

    path('api/', include((router.urls, 'core_api'), namespace='core_api')),
]
```

In the code above, we are registering the new `url` in the Django application. The line `path('api/', include((router.urls, 'core_api'), namespace='core_api'))` defines a path that includes all URLs from the `router`, prefixed with `'api/'`. This is nested within a namespace `'core_api'`.

With the URLs and endpoint defined, we should be able to move to the creation of the frontend and start consuming data from the API 😍. But wait, there is still something we need to configure, web developer's biggest **opps : CORS.**

### Important configuration: CORS

Before the API is usable from a frontend POV, we need to configure CORS. But what are CORS? [**Cross-Origin Resource Sharing**](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) (CORS) is a security feature implemented in web browsers to control how web pages in one domain can request resources from another domain. It's an important part of web security because it helps prevent malicious attacks, such as Cross-Site Scripting (XSS) and data theft, by restricting how resources can be shared between different origins.

In our case, making a request from the browser to the API URL will return a frontend error, a very ugly and sometimes frustrating one.

![Cors Errors](https://cdn-images-1.medium.com/max/1000/0*fFXQdFSyTAjJOGJJ.png)

Let's solve this error by configuring CORS on the API we have created. First, let's install the `django-cors-headers` package.

```bash
python -m pip install django-cors-headers
```

Once the installation is done, open the `settings.py` file and ensure to add the following configurations.

```python
INSTALLED_APPS = [
    ...,
    "corsheaders",
    ...,
]

MIDDLEWARE = [
    ...,
    "corsheaders.middleware.CorsMiddleware",
    "django.middleware.common.CommonMiddleware",
    ...,
]

CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",
    "http://127.0.0.1:3000",
]
```

In the code above, the `CORS_ALLOWED_ORIGINS` helps us tell Django which domain origin to accept. As we are planning to use Next.js on the frontend and these apps run by default on the `3000` port, we are adding two addressees from which Django can let requests coming from.

Great! We have successfully built a Django REST API, ready to serve data. With the `api/menu` endpoint, we can list menus, and create a menu and by using the detail endpoint `api/menu/<menu_id>`, we can update a menu or delete one.

In the next section, we will build a Next.js frontend using the App router architecture that will consume data from the Django backend we have created.

## Building the frontend with Next.js

In the precedent section of this article, we have built the backend of our full-stack application using Django. In this section, we will build the front end by using Next.js, a React framework built to make the development and deployment of a React application much easier than using the library directly.

We will build the UI of the frontend application just by using CSS. We will start with the listing, the page for creation, and the page for editing an article. Without further due, let's start by creating the Next.js project.

### Setup the Next.js project

The Next.js team has made the creation of a Next.js project quite easy. Run the following command to create a new project.

```bash
npx create-next-app@latest
```

You will be presented with options to choose for the configuration of the project. Here are the options to follow if you want to configure the project following this article.

```bash
What is your project named? next-js-front
Would you like to use TypeScript? No
Would you like to use ESLint? Yes
Would you like to use Tailwind CSS? No
Would you like to use `src/` directory? Yes
Would you like to use App Router? (recommended) Yes
Would you like to customize the default import alias (@/*)? No
```


Once you are done choosing the options for the creation of the project, a new directory called `next-js-front` containing all the resources needed to develop, start, and build the Next.js project will be created.

```bash
cd next-js-front && npm run dev
```

This will start the project on http://localhost:3000.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1705839838942/ce18767c-8ca2-41b1-856d-0d4cfe4a7eed.png)

With the project installed, we can now move to building the first block of the application.

### Building the listing page for the articles

In this section, we will build a listing page for the articles. On the page, the user should be able to view a list of articles, and a button to add a new menu, and for each item (menu) shown in the list, there should be actions for editing and deletion.

At the end of the article, it should look like in the image below.👇

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1700521923320/da6f3ba3-d468-4c4a-9da5-c988f3bcbd99.png)

Now that we have an idea about how the interface should look like, let's start coding.

In the Next.js project, you will find the `src/app` content of the Next.js project. The `app` folder should contain files such as `page.js`, `layout.js`, and `style.css`. Here is a quick description of each file and its goal.

* `page.js`: Next.js version 13 introduced the AppRouter architectural pattern, marking a shift from the previous versions' approach of structuring files in a `pages` directory. This new pattern enhances routing clarity and simplicity by determining the structure of the frontend page rendering based on the file and directory organization.
    
    The AppRouter brings notable improvements. It's not just faster; it also features server-side rendering as a default, facilitating the use of server components. Additionally, it extends functionality with features like `layout.js`, which I will explain later. It also includes specific files for various functions, such as `error.js` for error handling and `loading.js` for default loading behaviors.
    
    In this framework, creating a route like `menu/supplements` on the client side in a Next.js application involves placing a `page.js` file in the corresponding `menu/supplements` directory. This focus on `page.js` files simplifies the structure, as other files in the directory are not involved in routing. This approach grants developers more flexibility in organizing their application's architecture, allowing for the placement of components used on a specific page adjacent to the page's declaration.
    
* `layout.js`: In Next.js, particularly from version 13 with the AppRouter, the `layout.js` file is integral to defining the application's overall layout and style. It sets up a consistent framework across your app, encompassing elements like headers, footers, and navigation bars. `layout.js` supports hierarchical layouts, meaning different sections of your app can have unique layouts by having their own `layout.js` files. It also allows for dynamic layouts, adapting to different pages or application states.
    
    This file is key for integrating components like global state management and theme providers, ensuring a consistent environment across all pages. Additionally, `layout.js` is beneficial for SEO and performance optimization, as it centralizes metadata management and reduces the need for re-rendering common elements.
    
* `style.css`: Well, it contains the CSS code of the project. We will inject it into the `layout.js` file.
    

Let's start coding now. We will start by adding the necessary `css` class definitions so we can focus on the Next.js code.

```javascript
// src/app/style.css

.menu-container {
    max-width: 70%;
    height: 90vh;
    margin: 0 auto;
    padding: 20px;
    background: #f9f9f9;
    border-radius: 8px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.menu-item {
    border: 1px solid #ddd;
    padding: 10px;
    margin: 10px;
    border-radius: 4px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    display: flex;
    justify-content: space-between;
    align-items: center;
    background-color: #fff;
}

.menu-item-info {
    display: flex;
    flex-direction: column;
}

.menu-item-name {
    font-size: 1.2rem;
    font-weight: 600;
}

.menu-item-price {
    color: #555;
}

.menu-item-actions {
    display: flex;
    gap: 10px;
}

button {
    padding: 5px 10px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}

.edit-button {
    background-color: #ffca28;
    color: #333;
}

.add-button {
    background-color: #008000;
    color: #fff;
    padding: 10px;
    margin: 10px;
}

.delete-button {
    background-color: #f44336;
    color: #fff;
}

form {

    width: 60%;
}

.form-item {
    padding: 10px;
    display: flex;
    flex-direction: column;
}

input {
    height: 22px;
    border-radius: 4px;
    border: solid black 0.5px;
}

.success-message {
    color: #008000;
}

.error-message {
    color: #f44336;
}
```

And then in the `src/app/layout.js`, let's import the CSS code but also modify the container `className` to `menu-container`.

```javascript
// src/app/layout.js

import { Inter } from "next/font/google";

import "./style.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata = {
  title: "Restaurant Menu",
  description: "A simple UI to handle menus",
};

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <main className="menu-container">{children}</main>
      </body>
    </html>
  );
}
```

In the code above, we are importing fonts and CSS files, mostly assets. When we declare the metadata object with the title and the description. Then we define the `RootLayout` component with `menu-containerclassName`, and also importing our font into the `body` tag.

Safe to say that we have a complete `layout.js` now, and we can move to writing the `page.js` code. It will contain the code for listing the menus. So navigating `/` should send you to the listing of all menus.

### Building the Listing Page

In the precedent sections, we have ensured to have the necessary code for the CSS and also defined the `layout.js` file. We can now build the interface for the listing page.

In the `src/app/page.js`, make sure to have the following imports in the file to start.

```javascript
// src/app/page.js

"use client";

import { useEffect, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
```

You might have noticed the `use client` directive. This tells Next.js to render this page on the client side, so the page we are building should contain client-side code.

If you want server-side code on this page, use the directive `use server` instead.

Apart from that, we are importing the `useState` and `useEffect` hooks to respectively manage states and effects in the application. The `useRouter` and `useSearchParams` will also be useful as we have buttons to redirect to the editing page and adding page.

Next, we will declare two functions, one to retrieve the list of menus from the API and the other one to delete a menu.

```javascript
// src/app/page.js

...
/**
 * Fetches a menu item by ID.
 * @param {number} id The ID of the menu item to retrieve.
 */
async function deleteMenu(id) {
  const res = await fetch(`http://127.0.0.1:8000/api/menu/${id}/`, {
    method: "DELETE",
  });
  if (!res.ok) {
    throw new Error("Failed to retrieve menu");
  }
  return Promise.resolve();
}

/**
 * Fetches menu data from the server.
 */
async function getData() {
  const res = await fetch("http://127.0.0.1:8000/api/menu/");
  if (!res.ok) {
    throw new Error("Failed to fetch data");
  }
  return res.json();
}
```

In the code above, we are declaring two functions :

* `deleteMenu`: which takes one parameter, the `id` of the article, and then sends a deletion request. We are using the `fetch` API to send requests.
    
* `getData`: which requests to retrieve all menus from the API. We return the json of the response.
    

We have the methods that we will use in the listing of articles. We need now to write the item component that will be used to display information about a menu in the list of menus.

In the same `page.js` file, add the following `MenuItem` component.

```javascript
...
/**
 * Represents a single menu item.
 */
const MenuItem = ({ id, name, price, onEdit, onDelete }) => {
  return (
    <div className="menu-item" data-id={id}>
      <div className="menu-item-info">
        <div className="menu-item-name">{name}</div>
        <div className="menu-item-price">${price.toFixed(2)}</div>
      </div>
      <div className="menu-item-actions">
        <button className="edit-button" onClick={onEdit}>
          Edit
        </button>
        <button
          className="delete-button"
          onClick={() => {
            deleteMenu(id).then(() => onDelete(id));
          }}
        >
          Delete
        </button>
      </div>
    </div>
  );
};
```

In the code above, we are defining the `MenuItem` component taking props such as the `id` of the `menu`, the `name`, the `price`, the `onEdit` method of how to behave when the Edit button is clicked, and finally the `onDelete` method that is triggered when the delete button is created.  
We can now move on to writing the code for the page and using the `MenuItem` component.

```javascript
...
/**
 * The main page component.
 */
export default function Page() {
  const [menuItems, setMenuItems] = useState(null);
  const router = useRouter();
  const params = useSearchParams();

  // State for displaying a success message
  const [displaySuccessMessage, setDisplaySuccessMessage] = useState({
    show: false,
    type: "", // either 'add' or 'update'
  });

  // Fetch menu items on component mount
  useEffect(() => {
    const fetchData = async () => {
      const data = await getData();
      setMenuItems(data);
    };
    fetchData().catch(console.error);
  }, []);

  // Detect changes in URL parameters for success messages
  useEffect(() => {
    if (!!params.get("action")) {
      setDisplaySuccessMessage({
        type: params.get("action"),
        show: true,
      });
      router.replace("/");
    }
  }, [params, router]);

  // Automatically hide the success message after 3 seconds
  useEffect(() => {
    const timer = setTimeout(() => {
      if (displaySuccessMessage.show) {
        setDisplaySuccessMessage({ show: false, type: "" });
      }
    }, 3000);
    return () => clearTimeout(timer);
  }, [displaySuccessMessage.show]);

  // Handle deletion of a menu item
  const handleDelete = (id) => {
    setMenuItems((items) => items.filter((item) => item.id !== id));
  };

  return (
    <div>
      <button className="add-button" onClick={() => router.push("/add")}>
        Add
      </button>
      {displaySuccessMessage.show && (
        <p className="success-message">
          {displaySuccessMessage.type === "add" ? "Added a" : "Modified a"} menu
          item.
        </p>
      )}
      {menuItems ? (
        menuItems.map((item) => (
          <MenuItem
            key={item.id}
            id={item.id}
            name={item.name}
            price={item.price}
            onEdit={() => router.push(`/update/${item.id}`)}
            onDelete={handleDelete}
          />
        ))
      ) : (
        <p>Loading...</p>
      )}
    </div>
  );
}
```

The block code above is quite long, but let's quickly describe what is going on there.

* `Page` is the name of the component. Next.js will display on the browser the code coming from this component.
    
* Next, we are defining important states such as `menuItems` to stock the response we will get from `getData` and the `displaySuccessMessage` that will be used to show feedback when a creation/deletion is successful. We are also retrieving objects such as the `router` and the `params`. Those will be useful in implementing the deletion logic and the routing for the creation or edition of a menu.
    
    ```javascript
      const [menuItems, setMenuItems] = useState(null);
      const router = useRouter();
      const params = useSearchParams();
    
      // State for displaying a success message
      const [displaySuccessMessage, setDisplaySuccessMessage] = useState({
        show: false,
        type: "", // either 'add' or 'update'
      });
    ```
    
* In the next lines of code, we are defining three `useEffect` hooks, the first one to help us retrieve data from the API by calling the `getData` method, the second one used to handle success message displays when a creation or an update is successful ( we use a param in the URL to check if we need to display the message ) and the last `useEffect` is used to handle the time of display of the message. We use the `setTimeout` method to display the message for 3 seconds only
    
* Next, we are writing the JSX code with the add button. We are also using the `MenuItem` component declared above in the `page.js` file to display menu information.
    

You can find the code for the whole file on the GitHub repository [here](https://github.com/koladev32/django-api-nextjs/blob/master/menu-frontend/src/app/page.js).

We have a working listing page now. We can also delete articles. Let's now write the creation page.

### Building the Creation Page for a menu

In the last section, we have built the page for listing all menus. In this section, we will build the page for the creation of a Menu.

This will just be a form with a name and price fields. Let's get to it.

In the `src/app` directory, create a new directory called `add`. Inside this newly created directory, create a file called `page.js`. With this file, it means that when we navigate to the `/add` route, we will have the code on the `src/app/add/page.js` displayed.

Let's start writing the code.

```javascript
// src/app/add/page.js

"use client";

import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";

/**
 * Sends a POST request to create a new menu item.
 * @param {Object} data The menu item data to be sent.
 */
async function createMenu(data) {
  const res = await fetch("http://127.0.0.1:8000/api/menu/", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(data),
  });

  if (!res.ok) {
    throw new Error("Failed to create data");
  }

  return res.json();
}
```

In the code above, we are importing the required hooks for managing side effects, states, and routing. The next code block is much more interesting, as we are writing the method `createMenu` in charge of sending the POST request for the creation of a menu. This method takes as a parameter `data` which is a JSON object containing the required data to create a menu object.

We can move now to writing the `Page` component logic for this page.

```javascript
// src/app/add/page.js
...
const Page = () => {
  const router = useRouter();
  const [formData, setFormData] = useState({ name: "", price: "" });
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  /**
   * Handles the form submission.
   * @param {Event} event The form submission event.
   */
  const onFinish = (event) => {
    event.preventDefault();
    setIsLoading(true);
    createMenu(formData)
      .then(() => {
        // Navigate to the main page with a query parameter indicating success
        router.replace("/?action=add");
      })
      .catch(() => {
        setError("An error occurred");
        setIsLoading(false);
      });
  };

  // Cleanup effect for resetting loading state
  useEffect(() => {
    return () => setIsLoading(false);
  }, []);

  return (
    <form onSubmit={onFinish}>
      <div className="form-item">
        <label htmlFor="name">Name</label>
        <input
          required
          name="name"
          value={formData.name}
          onChange={(event) =>
            setFormData({ ...formData, name: event.target.value })
          }
        />
      </div>
      <div className="form-item">
        <label htmlFor="price">Price</label>
        <input
          required
          type="number"
          name="price"
          value={formData.price}
          onChange={(event) =>
            setFormData({ ...formData, price: event.target.value })
          }
        />
      </div>
      {error && <p className="error-message">{error}</p>}
      <div>
        <button disabled={isLoading} className="add-button" type="submit">
          Submit
        </button>
      </div>
    </form>
  );
};

export default Page;
```

The code above is also quite long, but let's explore what is done there.

* We are defining three states for managing the form data `formData`, loading when a request is pending `loading`, and also one state to store errors received from the backend and display them on the frontend `error`.
    
* Next, we have the `onFinish` method which is the method executed when the user submits the form. This method will first call the `event.preventDefault();` to prevent the default behavior of the browser when the user clicks on the submit button of a form, which reloads the page.
    
    After that, we set the `loading` state to true, as we are starting a creation request. Because this is an asynchronous request, we handle cases where the request is successful, by redirecting the user to the listing page with the URL param `action=add` which will trigger the display of a success message. In the case of an error, we set the error message that will be displayed on the frontend.
    
* Next, we have a `useEffect` used to clean up effects if the user leaves the page for example, or when the component is unmounted.
    
* Finally, the JSX code where we use conventional HTML tags to build a form, pass required props values to the inputs, and display the error message.
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1700521952002/e29a8f5e-1dd1-488b-aa75-dbcfc9f7fb40.png)

With the creation page written, we can finally move to crafting the edition page. Nothing will change as much from the creation page apart from the fact that we must:

* Retrieve the menu with the `id` on the editing page to fill the form with the existing values so that the user can modify them.
    
* And that's it mostly.
    

Let's create the edition page.

### Creating the Edition page

In the precedent section, we have learned how to create a simple form with Next.js and how to send a request to an API using the `fetch` API. We have now a working creation page for adding a new menu.

In this section, we will build the edition page for modifying the name, and the price of a menu. Nothing different from the creation page apart from the routing and the fact that we need information about the menu we want to modify.

In the `src/app/` directory, create a directory called `update` . In this directory, create a new directory called `[menuId]`. This tells Next.js that this is a dynamic route because the `menuId` can be changed depending on the item selected for editing in the listing of menus.

So for example, the user can be redirected to `/update/1` or `/update/2` with `menuId` being either `1` or `2`. This will also help us retrieve the `menuId` from the URL to request the API to retrieve data about the menu from the backend, and we can fill the form with the values that we have for `price` and `name` in the form.

In the `src/app/update/[menuId]/` directory, create the `page.js` file.

```javascript
// src/app/update/[menuId]/page.js

"use client" 

import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";

/**
 * Fetches a menu item by ID.
 * @param {number} id The ID of the menu item to retrieve.
 */
async function getMenu(id) {
  const res = await fetch(`http://127.0.0.1:8000/api/menu/${id}/`);
  if (!res.ok) {
    throw new Error("Failed to retrieve menu");
  }
  return res.json();
}

/**
 * Updates a menu item by ID.
 * @param {number} id The ID of the menu item to update.
 * @param {Object} data The updated data for the menu item.
 */
async function updateMenu(id, data) {
  const res = await fetch(`http://127.0.0.1:8000/api/menu/${id}/`, {
    method: "PUT",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(data),
  });

  if (!res.ok) {
    throw new Error("Failed to update menu");
  }
  return res.json();
}
```

In the code above, we are importing the required hooks for managing side effects, states, and routing. In the next code block, we are defining two methods :

* `getMenu` to retrieve a specific menu from the API using the detail API endpoint.
    
* `updateMenu` to send a PUT request to update information about a specific menu.
    

Let's move to the code of the `Page` component.

```javascript
// src/app/update/[menuId]/page.js

... 

const Page = ({ params }) => {
  const router = useRouter();
  const [formData, setFormData] = useState({ name: "", price: "" });
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  /**
   * Handles form submission.
   * @param {Event} event The form submission event.
   */
  const onFinish = (event) => {
    event.preventDefault();
    setIsLoading(true);
    updateMenu(params.menuId, formData)
      .then(() => {
        router.replace("/?action=update");
      })
      .catch(() => {
        setError("An error occurred");
        setIsLoading(false);
      });
  };

  // Cleanup effect for resetting loading state
  useEffect(() => {
    return () => setIsLoading(false);
  }, []);

  // Fetch menu item data on component mount
  useEffect(() => {
    const fetchData = async () => {
      try {
        const data = await getMenu(params.menuId);
        setFormData({ name: data.name, price: data.price });
      } catch (error) {
        setError(error.message);
      }
    };
    fetchData();
  }, [params.menuId]);

  return (
    <form onSubmit={onFinish}>
      <div className="form-item">
        <label htmlFor="name">Name</label>
        <input
          required
          name="name"
          value={formData.name}
          onChange={(e) => setFormData({ ...formData, name: e.target.value })}
        />
      </div>
      <div className="form-item">
        <label htmlFor="price">Price</label>
        <input
          required
          type="number"
          name="price"
          value={formData.price}
          onChange={(e) => setFormData({ ...formData, price: e.target.value })}
        />
      </div>
      {error && <p className="error-message">{error}</p>}
      <div>
        <button disabled={isLoading} className="add-button" type="submit">
          Submit
        </button>
      </div>
    </form>
  );
};

export default Page;
```

The code above is nearly identical to the form for the creation of the menu, the main difference being the prop passed to the `Page` component.

In Next.js 13, the `params` object is the prop containing the dynamic segment values (in our case `menuId`). With the value of `menuId`, we can easily trigger the `getMenu` the method by passing the `params.menuId` value but also ensure the update request with the `updateMenu` method by passing the `params.menuId` value and the `data` retrieved from the form.

This is great! We have a fully working Next.js 13 application integrated with Django that can list menus, and display pages for the creation and editing of menu information, but can also handle the deletion. Here's below, a demo of how the application features should look like.

![](https://www.loom.com/share/7c2cf140165146c5be43e33faafd1e40?sid=8e1b1447-e238-49fe-8f30-cfc80734125d)

## Conclusion

And there you have it – a step-by-step guide to building a full-stack application using Django and Next.js. This combination offers a robust and scalable solution for web development, blending Django's secure and efficient backend capabilities with the reactive and modern frontend prowess of Next.js.

By following this guide, you've learned how to set up a Django REST API and create a frontend application in Next.js. This CRUD application for managing a restaurant's menu serves as a practical example of how these two technologies can be seamlessly integrated.

Remember, the journey doesn't end here. Both Django and Next.js are rich with features and possibilities. I encourage you to dive deeper, experiment with more advanced features, and continue honing your skills as a full-stack developer.

[Here is the link to the codebase](https://github.com/koladev32/django-api-nextjs) of the application built in this article.