Django, InertiaJs and React - A guide to get you started - Pt. 1
Tags: django, inertiajs, react, webdev
Published: 2023-01-15 02:14:42

:warning: This guide is for versions of __Inertiajs__ before v 1.0. I will be including the upgrade to __InertiaJs V 1.0__ in Part 2.

__Motivations:__ I prepared this guide to solve a problem I was having with one of the projects that I have in production. I won't bore you with the details but, what I can say about it is that it's a Django project that makes use of ReactJs using Webpack but in a kind of thrown together way that made development a pain. With that in mind, I decided to search for a better way. Hopefully this will be of help to someone.

I'll be splitting this guide into two parts just to make it easier to consume.

## Prerequisites
* Some experience with the Django framework and React
* Have an environment that is ready for Django app development (the usual requirements like Pip / Pipenv and so on)
* Python 3.7+
* NodeJS 16.x+
* React Dev Tools

## Tested platforms

* :white_check_mark: `Ubuntu 22.04 LTS` 
* :white_check_mark: `MacOS` 
* :white_check_mark: `Windows` ** Thanks to [@TappMax](https://twitter.com/TappMax) for testing

## What we'll be doing in Part. 1
* Setting up our Django project
* Setting up GIT (optional, but usually a good idea)
* Installing the required Django dependencies and configuring them
* Setting up our Inertia App
* Testing Django with our InertiaJS App

### Initial Setup of up our Django Project

For this guide, we will be building a simple contact list manager. Since the main focus is to walk through getting Django, InertiaJS, React with Vite to play nicely, we will be skipping things like Authentication and styling (for the most part).

Let's begin getting our Django project started

* Let's create a folder for our project and navigate to it

```shell
mkdir contact-app
cd contact-app
```


![New project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ykw7rk9kesqxhbp5qa6x.png)

* Create your virtual environment using the following command (or your preferred way to create a virtual environment):

```shell
python3 -m venv .venv
```

* activate your newly created environment using: 

```shell
source .venv/bin/activate
```

![Activating virtual environment](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2iv17w5ddwzrysv2vh2y.png)

* install Django 4.1 LTS using the command and it would be a good idea to upgrade pip in our virtual environment as well

```shell
pip install Django==4.1
pip install --upgrade pip
```

![Installing Django](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cjzsi03w4zogtrs060ok.png)

:information_source: `Your terminal output might be different but as long as all the packages were installed properly, you can proceed to the next step`

:warning: A note about the settings file in the django project that was just created: the `SECRET_KEY` is something that should be kept safe. There are a few things that we can do to secure our settings file but we'll save that for another time.

* Create our django project in our current directory

```shell
django-admin startproject contact_manager .
```

:information_source: `If the command ran correctly, you will not see anything returned. You can always checking using the ls command` 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qmmjfousy1vqsnbxb9l7.png)

* Let's make sure our application is working correctly

```shell
./manage.py runserver
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2bbrdg2z5hlxgla69f9h.png)

:information_source: Your terminal should show something similar. Don't worry about unapplied migrations warning. We'll fix those soon.

* At this point, you should be able to navigate to the development server address.

* We should be up and running at this point. The console will tell us that we have unapplied migrations...that's ok for now

![Up and running](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/br1glvwc9664ieqs9wg8.png)

:information_source: We should see the start page when we navigate to http://127.0.0.1:8000 as directed in the terminal output

```shell
# All the commands
python3 -m venv .venv
source .venv/bin/activate
pip install Django==4.1
pip install --upgrade pip
./manage.py runserver
```

```shell
# Windows Notes: Provided by Max

# All the commands on windows
python -m venv .venv
.venv\Scripts\activate
pip install Django==4.1
pip install --upgrade pip
manage.py runserver
```

### Setting up git (optional, but recommended)

This step is optional but recommended. If you have the development server run, go ahead and stop it.

:warning: if you intend to follow this step, the assumption here is that you have experience setting up GIT. That said, if you don't, I gotchu. This link will help: https://www.atlassian.com/git/tutorials/install-git 

* Let's initialize our git repo for this project

```shell
git init
```

* You should see something that looks like

```shell
Initialized empty Git repository in PATH/TO/YOUR/PROJECT
```

* Let's get a git ignore file in place from now. Depending on your IDE or Editor, you may be able to generate a `.gitignore` file. 
* Create a file named `.gitignore` in the root of the project and copy and paste the following (this file is probably overkill for our purposes)

```shell
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# PyInstaller
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Minimal Django:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Minimal Virtualenv
.venv

# Jetbrains
.idea

# Minimal NodeJS
dist
node_modules

```

* With this in place, we can add all our files and then commit them

```shell
git add .
git commit -m 'initial commit'
```



### Installing Django Dependencies

In this section, we'll be installing __inertia/django__ and __django-vite__ and configuring our settings to make things work.

* If you're using version control, let's go ahead and make a new branch from main

```shell
git checkout -b install-dependencies # or whatever you want to call it
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/726oo59lb3xt70vzo7n9.png)

#### Installing Inertia/Django

For more information about the Inertia.js Django Adapter: https://pypi.org/project/inertia-django/

```shell
pip install inertia-django
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rpdtwxvop3exw0991q5f.png)

:information_source: Your output should look something like what's shown above

Now would be a good time to open up the project in your IDE or editor. I'll be using Pycharm Professional but VSCode is fine too. And let's have a look at the project structure so far.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fgc7hkfayj7oauq0mz1h.png)

* Next, we will update our ```INSTALLED_APPS``` in our ```settings.py``` file to inertia `inertia`

```python
INSTALLED_APPS = [
  # django inertia apps,
  'inertia',
  # our project's apps,
]
```

* and then update our `MIDDLEWARE` in `settings.py` to include the inertia middleware `inertia.middleware.InertiaMiddleware` 

```python
MIDDLEWARE = [
  # django middleware,
  'inertia.middleware.InertiaMiddleware',
  # your project's middleware,
]
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w4mrjptoow213reewwmy.png)

:information_source: Your `settings.py` should look something like the image above

Next, create a template file `templates/base.html` having the initial structure

```html
<!DOCTYPE *html*>
<html *lang*="en">
  <head>
    <meta *charset*="UTF-8">
    <title>Title</title>
  </head>
  <body>
    {% block *inertia* %}{% endblock %}
  </body>
</html>
```

* To settings, add `INERTIA_LAYOUT = 'base.html'` to the end of the settings file

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/llse50r21i2jq8bdbdur.png)

* Update `DIRS` in `TEMPLATES` to include `BASE_DIR / 'templates'`

```python
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'], # add this line
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rkc2wx0jw1einegj8bes.png)

:information_source: The `TEMPLATES` section of your `settings.py` should look like this

#### Installing Django-Vite

```shell
pip install django-vite
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vr4dqjp7uiz4672l4fki.png)

* Once installed, add `django-vite` to our installed apps

```py
INSTALLED_APPS = [
  # django apps,
  'django_vite',
  'inertia',
  # your project's apps,
]
```

* In the root of the project, we will need to create a `package.json` file by using `npm`

```shell
npm init -y
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/knaify3ajoban6d9tjhv.png)

* Now we can install `Vite` :rocket:

```shell
npm i -D vite
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ytl2mpp8df1l0o67ipq3.png)

* For vite to work, we need to create a `vite.config.js` file in the root of our project with the following content

```javascript
import { resolve }  from 'path';
 
module.exports = {
 plugins: [],
 root: resolve('./react-app'),
 base: '/static/',
 server: {
   host: 'localhost',
   port: 3000,
   open: false,
   watch: {
     usePolling: true,
     disableGlobbing: false,
   },
 },
 resolve: {
   extensions: ['.js', '.json'],
 },
 build: {
   outDir: resolve('./react-app/dist'),
   assetsDir: '',
   manifest: true,
   emptyOutDir: true,
   target: 'es2015',
   rollupOptions: {
     input: {
       main: resolve('./react-app/src/main.js'),
     },
     output: {
       chunkFileNames: undefined,
     },
   },
 },
};
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yw0f400ac6i95zyxney2.png)

* Update the file `templates/base.html` with the following content

```html
{% load django_vite %}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <!-- include vite hmr -->
     {% vite_hmr_client %}
     {% vite_asset 'src/main.js' %}
     <title>Title</title>
  </head>
  <body>
     {% block inertia %}{% endblock %}
  </body>
</html>
```

:information_source: we are referencing `js/main.js`, we will create this file eventually.

* Update `settings.py` to include the necessary settings for Django Vite

```python
# InertiaJS Related settings
INERTIA_LAYOUT = 'base.html'

# We need this for django form posting
CSRF_HEADER_NAME = 'HTTP_X_XSRF_TOKEN'
CSRF_COOKIE_NAME = 'XSRF-TOKEN'

# Where ViteJS assets are built.
DJANGO_VITE_ASSETS_PATH = BASE_DIR / "react-app" / "dist"

# If we should use HMR or not.
DJANGO_VITE_DEV_MODE = DEBUG

# we need this to get around cors issues
DJANGO_VITE_DEV_SERVER_HOST = '127.0.0.1'

# this is the default, but I'm leaving this here, so you know what to change if you want to run on a different port
DJANGO_VITE_PORT = 3000

# Name of our static files' folder (after called python manage.py collectstatic)
STATIC_ROOT = BASE_DIR / "static"

# Include DJANGO_VITE_ASSETS_PATH into STATICFILES_DIRS to be copied inside
# when run command python manage.py collectstatic
STATICFILES_DIRS = [DJANGO_VITE_ASSETS_PATH]
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qy0h85s1zrifl61jf0z9.png)

* Let's create our `react-app/dist` folder which we will use eventually (Django's dev server will complain about it)
* Let's create our entry point for what will become our __React/InertiaJS__ app `react-app/src/main.js`

```javascript
alert('Django Vite says hello world!');
```

* Update `package.json` to include `dev` and `build` scripts so that we can run vite

```json
{
  "scripts": {
    "build": "vite build",
    "dev": "vite",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
}
```


* At this point, we should be able to run `vite` and we shouldn't see any errors in the terminal. That said, we still can't do anything useful yet

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z0j0vt8wuqx1gz96l208.png)

In our browser, we should able to access our js file

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2vug5s4e1f4s5rfzk24d.png)

* Let's add a file for our views and we'll call it... `contact_manager/views.py` :smirk:

```python
from django.shortcuts import render
 
 
def index(request):
   return render(request, "base.html")
```

* Let's update our urls `urls.py`

```python
from django.contrib import admin
from django.urls import path
 
# this was added
from contact_manager import views
# from contact_manager.views import index # this is a valid import as well
 
urlpatterns = [
   path('', views.index), # this was added
   path('admin/', admin.site.urls),
]
```

* Open a new terminal and run the django dev server
* Navigate to `http://127.0.0.1:8000` and we should see our JavaScript alert.


![Django Vite says Hello World!](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/te0hf9kh38wrd8wogwas.png)

:information_source: We have HMR working. Of course you can check that it works by changing the `main.js` file and observing what happens. :thumbsup:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bto1joj441lpdh3h7nqz.png)

* Since we're at a decent point, let's commit our changes before we move forward (if you're using version control)

```shell
git add .
git commit -m "Install dependencies and did initial setup for vite to serve main.js file via vite"
```



#### Now for the interesting part :raised_hands:

Update the previously created `contact_manager/views.py` fileto include an import from `inertia` 

```python
from inertia import inertia
 
 
@inertia('Home/Index')
def index(request):
   return {
       'contacts': ['Acid Burn', 'Crash Override', 'Lord Nikon'],
   }
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wtukhonwpivpastv7ltv.png)

:information_source: The object we return from our `index` function becomes the props of the `React` component we will eventually render

If everything worked correctly, when we inspect our index page, we should see a `div` that has `id="app"` along with a `data-page` attribute that contains the `contacts` as defined in our python view file. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g1024qfh6pch4dtokp72.png)

The last thing we'll do for Part 1 of this tutorial is to setup our InertiaJS application

* Let's install the dependencies we need via NPM

```shell
npm i -D react react-dom @vitejs/plugin-react
```

Edit: For our InertiaJs dependencies **@inertiajs/inertia-react @inertiajs/progress**, we will need specific versions as shown in the `package.json` snippet below.

```json
"devDependencies": {
    "@inertiajs/inertia-react": "^0.8.1",
    "@inertiajs/progress": "^0.2.7",
    "@types/react": "^18.0.26",
    "@types/react-dom": "^18.0.10",
    "@vitejs/plugin-react": "^3.0.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "ts-loader": "^9.4.2",
    "typescript": "^4.9.4",
    "vite": "^4.0.4"
  }
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o5naq860v6878z3jb7y8.png)

* Stop vite if you have not yet done so

* Rename our `main.js` to `main.jsx` and replace the content with what is shown below

```jsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import { createInertiaApp } from '@inertiajs/inertia-react';
import { InertiaProgress } from '@inertiajs/progress';
import Layout from './components/Layout.jsx';
 
const pages = import.meta.glob('./pages/**/*.jsx');
 
document.addEventListener('DOMContentLoaded', () => {
 
 InertiaProgress.init();
 
 createInertiaApp({
   resolve: async (name) => {
     const page = (await pages[`./pages/${name}.jsx`]()).default;
     page.layout = page.layout || Layout;
 
     return page;
   },
   setup({ el, App, props }) {
     createRoot(el).render(<App {...props} />);
   },
 }).then();
});
```

* Let's update our `vite.config.js` file to work with our changes

```javascript
import { resolve }  from 'path';
import react from '@vitejs/plugin-react';
 
module.exports = {
 plugins: [
   react({
     include: '**/*.disabled',
   }),
 ],
 root: resolve('./react-app'),
 base: '/static/',
 server: {
   host: 'localhost',
   port: 3000,
   open: false,
   watch: {
     usePolling: true,
     disableGlobbing: false,
   },
 },
 resolve: {
   extensions: ['.js', '.json'],
 },
 build: {
   outDir: resolve('./react-app/dist'),
   assetsDir: '',
   manifest: true,
   emptyOutDir: true,
   target: 'es2015',
   rollupOptions: {
     input: {
       main: resolve('./react-app/src/main.jsx'),
     },
     output: {
       chunkFileNames: undefined,
     },
   },
 },
};

```

* Let's create a layout component `src/components/Layout.jsx` which we will use to wrap our `pages` 

```jsx
const Layout = ({ children }) => {
 return (
   <main>
     <div>{children}</div>
   </main>
 );
};
 
export default (page) => <Layout>{page}</Layout>;
```

* Update `templates/base.html` to point to our `main.jsx` file
* Create our Index page component `src/pages/Home/Index.jsx` and paste the following content

```jsx
export default function Home ({contacts}) {
 return (
   <div>
     <h1>Contact List</h1>
     {Array.isArray(contacts) && contacts.length ? <ul>
       {contacts.map(contact => <li key={contact}>{contact}</li>)}
     </ul> : <p>No contacts yet...</p>}
   </div>
 );
}

```

:information_source: Now, if everything worked, we should see something that looks like the image below :thumbsup: 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/slnq4hjrlbzxgpguhuho.png)

* Go ahead and commit our changes

```shell
git add .
git commit -m "update implementation to render inertia js app with props"
```

If you've made it this far and everything works then, nice! As you can see, InertiaJs is pretty cool and being able to use it with Django makes it way cooler (imo).

__Coming up in part 2, we'll take a look at the following:__

* Upgrade InertiaJs to v1.0
* Integrating TypeScript
* Installing Material UI (you can use any component library you want)
* Implementing the contact app
  * Backend 
    * models, views
  * Frontend
    * Pages and components

Hope you found this guide useful. Let me know if you have questions or suggestions.

## References:

The following links were used in the making of this guide

* __inertia-django__ https://pypi.org/project/inertia-django/
* __django-vite__ https://github.com/MrBin99/django-vite
* `vite.config.js` original source: https://raw.githubusercontent.com/MrBin99/django-vite-example/master/vite.config.js
* InertiaJs + Django Setup video (inspiration for this guide) https://www.youtube.com/watch?v=85-i4fr5MeU