Django and Modern JS Libraries - React
In the previous part, we built a Django backend and GraphQL API that is responsible for communication of Django project and React app. In this part of the tutorial, we will create a single page application with React from scratch. We will bundle our application with webpack and we will not use create-react-app boilerplate. Also, if you like reactive programming, you may be interested in SolidJS and Reactive Primitives.
Create React App from Scratch
Step - 1: Configuring development environment
(Note: if you already installed the node, you can skip this part)
We will use Node backend for the development environment. Therefore, we need to install Node and Node package manager npm. To prevent potential dependency problems, we will create a clean node environment. I will use NVM which is Node version manager, and it allows us to create isolated Node environments.In your terminal, run the code below.
# install node version manager wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash
# check installation
command -v nvm
# should prints nvm, if it doesn"t
# you can restart your terminal
# install node
# node" is an alias for the latest version
nvm install node
# use the installed version
nvm use node
# prints Now using node v..
Now we can create frontend directory in Django project.Go to the root directory of the project. 'backend/'In your terminal copy and paste the code.
# create frontend directory
mkdir FRONTEND
cd FRONTEND
#in backend/FRONTEND create a Node project
npm init
# you may fill the rest
Now we can install Javascript dependecies such as React and API related other libraries.
# add core react library
npm install react react-dom react-router-dom
# add graphql client-side framework of Apollo and parser
npm install apollo-boost @apollo/react-hooks graphql graphql-tag
# -- DEVELOPMENT PACKAGES---
# add babel transpiler
npm install --save-dev @babel/core @babel/preset-env @babel/preset-react
# add webpack bundler
npm install --save-dev webpack webpack-cli webpack-dev-server
# add webpack loaders and plugins
npm install --save-dev babel-loader css-loader style-loader html-webpack-plugin mini-css-extract-plugin postcss-loader postcss-preset-env
If everything goes well, we can create necessary files.
# create source folder for client side code
mkdir src
# our react app's root file
touch index.js
#create webpack config file
touch webpack.config.js
# get into src folder
cd src
# create HTML entrypoint for React development
touch index.html
# our app file and styling
touch App.js
// Screens
touch MovieList.js
touch MoviePage.js
# for styling
touch App.css
All npm packages contain a file which holds metadata about the app. This file is package.json file. You should update *package.json* file.
Edit scripts section, and add Babel presets and postcss configurations.
{
"scripts": {
"start": "webpack-dev-server --open --hot --mode development",
"build": "webpack --mode production"
},
"babel": {
"presets": [
"@babel/preset-env",
"@babel/preset-react"
]
},
"postcss": {
"plugins": { "postcss-preset-env": {} }
},
}
Step 2 - Webpack configuration and index.html file
What is webpack ?
Webpack is a module bundler and a task runner. We will bundle all our JavaScript application including CSS styling into two JavaScript files, if you prefer you can output only one file. Because of the rich plugins, you can also do many things with Webpack like compressing with different algorithms, eliminate unused CSS code, extracting your CSS to different files, uploading your bundle to cloud provider like S3 etc…
I made two different Webpack settings in one file. One is for development environment, and the other one is for production environment. Also note that we do not optimize these configurations.
Copy/Paste the following code into *webpack.config.js* file.
const path = require("path");
const HtmlWebPackPlugin = require("html-webpack-plugin");
// checks if it is production bundling or development bundling
const isEnvProduction = process.argv.includes("production")
// our JS root file
const entrypoint = './index.js'
const productionSettings = {
mode: "production",
entry: entrypoint,
output: {
// output directory will be the root directory of django
path: path.resolve(__dirname, '../'),
// this is the bundled code we wrote
filename: 'static/js/[name].js',
// this is the bundled library code
chunkFilename: 'static/js/[name].chunk.js'
},
optimization: {
minimize: true,
splitChunks: {
chunks: 'all',
name: true,
},
runtimeChunk: false,
},
devServer: {
historyApiFallback: true,
stats: 'normal',
},
module: {
rules: [
{
loader: 'babel-loader',
test: /\.js$|jsx/,
exclude: /node_modules/
},
{
test: /\\.css$/i,
use: [
// IMPORTANT => don't forget `injectType` option
// in some cases some styles can be missing due to
// inline styling.
{ loader: 'style-loader', options: { injectType: 'styleTag' } },
"css-loader"
],
},
]
},
plugins: [
new HtmlWebPackPlugin({
// ENTRYPOINT - this is where webpack read our app for bundling
template: "./src/index.html",
// OUTPUT FILE
// this is emitted bundle html file
// ----------------------------------
// django will use this as template after bundling
// -----------------------------------
filename:"./templates/index.html"
}),
]
};
const devSettings = {
mode: "development",
entry: entrypoint,
output: {
path: path.resolve(__dirname, './build'),
publicPath: "/",
filename: 'static/js/bundle.js',
chunkFilename: 'static/js/[name].chunk.js',
},
devtool: 'inline',
devServer: {
historyApiFallback: true,
contentBase: './dist',
stats: 'minimal',
},
module: {
rules: [
{
loader: 'babel-loader',
test: /\.js$|jsx/,
exclude: /node_modules/
},
{
test: /\.css$/i,
use: [
//{loader: MiniCssExtractPlugin.loader, options: {
// //your styles extracted in a file for production builds.
// //hmr: isEnvDevelopment,
// },
// },
// IMPORTANT => don't forget `injectType` option
{ loader: 'style-loader', options: { injectType: 'styleTag' } },
"postcss-loader"
//"css-loader"
//{ loader: 'sass-loader' },
],
},
]
},
plugins: [
new HtmlWebPackPlugin({
template: "./src/index.html",
})
]
};
module.exports = isEnvProduction ? productionSettings : devSettings;
When we are developing frontend, our React app render all our JavaScript code to this HTML file in the src folder. Also, when we build our code for production (bundling), Webpack will use this HTML as a template.
It is important to say that Django will not use this HTML file. This is the HTML entry point of_ W_ebpack. *Django will use the output of webpack bundle*.
Update your index.html file.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Django-React Integration Tutorial"/>
<title>Django React Integration</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
Step - 3 Create the React app
The index file is the root file of our app, meaning that all our codes will be connected to this root file. The other tutorials or eact boilerplates use this file for only render function of ReactDOM and leave it tiny and clear. Writing this index file as it is is a totally a choice.
What we will do is as follows:
We will create an Init component that will initialize the API framework (Apollo) and routing library (react-router-dom).
We will wrap our App.js file with API framework so that all our components will be in the context of API.
The Apollo Provider expects an Apollo client. The Apollo client has the information of the requested address, which is the address of our Django server.
After then we will wrap our App file again with the router component, namely Browser Router. This makes our app a single-page-application. Thus, we make routing without rendering all the page when the URL of the address bar changes.
At the end of the file, you will see the render function of ReactDOM which accepts our root component, which is Init component in our case, and the DOM element that our app will be rendered in there.
Update your *index.js* file as follows.
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { BrowserRouter } from "react-router-dom"
import ApolloClient from 'apollo-boost';
import { ApolloProvider } from '@apollo/react-hooks';
/*
our frontend api client will make request to
the adress we define in the first part.
*/
const apiclient = new ApolloClient({
uri: 'http://127.0.0.1:8000/graphql',
});
const Init = () => (
<ApolloProvider client={apiclient}>
<BrowserRouter>
<App />
</BrowserRouter>
</ApolloProvider>
)
ReactDOM.render( <Init />, document.getElementById('root'))
Now, we are ready to create our simple movie app.
Our app has two different screens:
- The main page which lists all movies in the database with less information
- The movie page will show specific movie with more information.
Now update your *App.js* file.
import React from "react";
import { Route, Switch, Link } from "react-router-dom"
// UI Screens we will define later
import MoviePage from "./MoviePage.js" //New
import MoviePage from "./MoviePage.js" //New
import "./App.css"
const App = () => {
return (
<div className="App">
<Switch>
<Route exact path="/" component={MainPage} />
{// colon before slug means it is a dynamic value
// that makes slug parameter anything
// like: /movie/the-lighthouse-2019 or /movie/anything
// as long as slug matches with database.
}
<Route exact path="/movie/:slug" component={MoviePage} />
</Switch>
</div>
)
}
export default App
*Let me explain what those code means*
When a user first opens our page, switch component from react-router-dom will look the URL. Then try to match the path of route components with this URL, if any, then the matched component in the route will be rendered.
Step - 4 Create page components and styling
MovieList component will be shown on landing page. Copy/Paste to "MovieList.js" file
import React from "react";
import gql from "graphql-tag";
// our first query will requests all movies
// with only given fields
// note the usage of gql with 'jsvascript string literal'
export const MOVIE_LIST_QUERY = gql`
query movieList{
movieList{
name, posterUrl, slug
}
}
`
const MovieList = (props) => {
const { loading, error, data } = useQuery(MOVIE_LIST_QUERY);
// when query starts, loading will be true until the response come.
// At this time this will be rendered on screen
if (loading) return <div>Loading</div>
// if response fail, this will be rendered
if (error) return <div>Unexpected Error: {error.message}</div>
//if query succeed, data will be available and render the data
return(
<div className="main-page">
{data && data.movieList &&
data.movieList.map(movie => (
<div className="movie-card" key={movie.slug}>
<img
className="movie-card-image"
src={movie.posterUrl}
alt={movie.name + " poster"}
title={movie.name + " poster"}
/>
<p className="movie-card-name">{movie.name}</p>
<Link to={`/movie/${movie.slug}`} className="movie-card-link" />
</div>
))
}
</div>
)
}
export default MovieList
MoviePage component will show more details than the list view but only information of a specific movie.
Copy and paste the code MoviePage.js file.
import React from "react";
import gql from "graphql-tag";
// Note the usage of argument.
// the exclamation mark makes the slug argument as required
// without it , argument will be optional
export const MOVIE_QUERY = gql`
query movie($slug:String!){
movie(slug:$slug){
id, name, year, summary, posterUrl, slug
}
}
`
const MoviePage = (props) => {
// uncomment to see which props are passed from router
//console.log(props)
// due to we make slug parameter dynamic in route component,
// urlParameters will look like this { slug: 'slug-of-the-selected-movie' }
const urlParameters = props.match.params
const { loading, error, data } = useQuery(MOVIE_QUERY, {
variables:{slug:urlParameters.slug}
});
if (loading) return <div>Loading</div>
if (error) return <div>Unexpected Error: {error.message}</div>
return (
<div className="movie-page">
<Link to="/" className="back-button" >Main Page</Link>
{data && data.movie &&
<div className="movie-page-box">
<img
className="movie-page-image"
src={data.movie.posterUrl}
alt={data.movie.name + " poster"}
title={data.movie.name + " poster"}
/>
<div className="movie-page-info">
<h1>{data.movie.name}</h1>
<p>Year: {data.movie.year}</p>
<br />
<p>{data.movie.summary}</p>
</div>
</div>
}
</div>
)
}
export default MoviePage
Add some styling: update *App.css.*
html, body {
width:100vw;
overflow-x: hidden;
height:auto;
min-height: 100vh;
margin:0;
}
.App {
position: absolute;
left:0;
right:0;
display: flex;
min-width: 100%;
min-height: 100vh;
flex-direction: column;
background-color: #181818;
/*font-family: "Open Sans", sans-serif;*/
font-size: 16px;
font-family: sans-serif;
}
/* MAIN PAGE */
.main-page {
position: relative;
display: flex;
flex-wrap: wrap;
min-height: 40vh;
background-color: #3f3e3e;
margin:10vh 5vw;
border-radius: 6px;
}
/* MOVIE CARD */
.movie-card {
position: relative;
width:168px;
height:auto;
background: #f1f1f1;
border-radius: 6px;
margin:16px;
box-shadow: 0 12px 12px -4px rgba(0,0,0, 0.4);
}
.movie-card:hover {
box-shadow: 0 12px 18px 4px rgba(0,0,0, 0.8);
}
.movie-card-image {
width:168px;
height:264px;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
}
.movie-card-name {
text-align: center;
margin: 0;
padding: 8px;
font-weight: bold;
}
.movie-card-link {
position: absolute;
top:0;
left:0;
right: 0;
bottom: 0;
}
/* MOVIE PAGE */
.back-button {
position: absolute;
left:10px;
top:10px;
width:120px;
padding: 8px 16px;
text-align: center;
background: #f1f1f1;
color:black;
font-weight: bold;
cursor:pointer;
}
.movie-page {
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex-wrap: wrap;
min-height: 80vh;
margin:10vh 10vw;
border-radius: 6px;
}
.movie-page-box {
position: relative;
display: flex;
height:352px;
background-color: #f1f1f1;
}
.movie-page-image {
width:280px;
height:352px;
}
.movie-page-info {
position: relative;
display: flex;
flex-direction: column;
height:352px;
width: auto;
max-width: 400px;
padding: 16px 32px;
}
Finally, Start Django-React App
Development Environment
In development environment we will run two different servers. One is Django server for backend, and the other one is Webpack server for frontend development. In production environment, We will only run one Django server as I promise.
Go to the root folder of the Django project. '***backend/'.*
Execute the below command and make Django server ready for frontend requests.
python manage.py runserver
Open another terminal and go to FRONTEND directory. 'backend/FRONTEND'
npm run start
You will see those screens.
Django and React Successfully Integrated . We created a simple single-page-application. Now, the last part of this tutorial will be made this app works seamlessly with our Django project.
Now you can stop the webpack server with the corresponding terminal screen.
The Final Part - Production Build of Django and React
Now, We can build our app for the production environment. Go to the FRONTEND directory and execute the build command.
npm run build
When the build process done, there will be two Javascript file in *backend/static* folder:
- main.js
- vendors~main.chunk.js
Also check *backend/templates* folder and you will see other *index.html* file.
*This is the HTML file that Django will use.*
I made this graphic in order to show webpack bundling process and how our app will use output files.
FINISHED
Top comments (0)