Use React with Django Ninja (including Auth) in 15 mins ⚡️
We'll create a full-stack application using React.js frontend and a Django REST API, using the awesome Django Ninja (Much nicer than Django REST framework), including user authentication.
Here's a video guide of me building this:
I'll also show you 5 things that you'll want to do when adding auth:
- How to add page routing (using React Router) to your React.js frontend
- How to add centralized state management to React using Zustand (another excellent, well-designed package)
- How to add persistent state (e.g., refreshing the page and still being logged in) using simple local storage
- How to register new users
- Secure and simple auth using Django's in-built session authentication
For my parallel guide for Vue.js and Django, see Use Vue with Django (including Auth) in 15 mins 🍒
React and Django Ninja are an excellent combination. Let's start! 🏎
Part 1: Setting Up the Backend (Django Ninja)
1.1 Create a new Django project
First, let's install and create new Django project and app:
pip install django django-ninja django-cors-headersdjango-admin startproject core .python manage.py startapp sim
1.2 Update Django settings
Open core/settings.py
and make the following changes:
INSTALLED_APPS = [# ...'sim','ninja','corsheaders',]MIDDLEWARE = ["corsheaders.middleware.CorsMiddleware", # Add this line to the top# ... other middleware]# Add these settings anywhere in the file.CORS_ALLOW_CREDENTIALS = TrueCORS_ALLOWED_ORIGINS = ["http://localhost:5173"] # React app URLCSRF_TRUSTED_ORIGINS = ['http://localhost:5173']AUTH_USER_MODEL = 'sim.CustomUser'
1.3 Create a custom user model
Following good Django practice, we'll create a custom user model to extend the default user model. This will allow us to use email as the username field.
In sim/models.py
, add the following:
from django.contrib.auth.models import AbstractUserfrom django.db import modelsclass CustomUser(AbstractUser):email = models.EmailField(unique=True)USERNAME_FIELD = 'email'REQUIRED_FIELDS = ['username']def __str__(self):return self.email
1.4 Set up URL routing
In core/urls.py
, add:
from django.contrib import adminfrom django.urls import pathfrom sim.api import apiurlpatterns = [path('admin/', admin.site.urls),path("api/", api.urls),]
1.5 Create API endpoints
Create sim/api.py
and add:
from ninja import NinjaAPIfrom ninja.security import django_authfrom django.contrib.auth import authenticate, login, logoutfrom django.middleware.csrf import get_tokenfrom .models import CustomUser as Userfrom . import schemasapi = NinjaAPI(csrf=True)@api.get("/set-csrf-token")def get_csrf_token(request):return {"csrftoken": get_token(request)}@api.post("/login")def login_view(request, payload: schemas.SignInSchema):user = authenticate(request, username=payload.email, password=payload.password)if user is not None:login(request, user)return {"success": True}return {"success": False, "message": "Invalid credentials"}@api.post("/logout", auth=django_auth)def logout_view(request):logout(request)return {"message": "Logged out"}@api.get("/user", auth=django_auth)def user(request):secret_fact = ("The moment one gives close attention to any thing, even a blade of grass","it becomes a mysterious, awesome, indescribably magnificent world in itself.")return {"username": request.user.username,"email": request.user.email,"secret_fact": secret_fact}@api.post("/register")def register(request, payload: schemas.SignInSchema):try:User.objects.create_user(username=payload.email, email=payload.email, password=payload.password)return {"success": "User registered successfully"}except Exception as e:return {"error": str(e)}
1.6 Create schemas
Create sim/schemas.py
and add:
from pydantic import BaseModelclass SignInSchema(BaseModel):email: strpassword: str
1.7 Apply migrations and test the backend
Run the following commands:
python manage.py makemigrationspython manage.py migratepython manage.py runserver
Visit http://localhost:8000/api/docs
to see the API documentation. Test each endpoint using the interactive documentation to ensure they're working correctly.
Part 2: Setting Up the Frontend (React)
2.1 Create a new React project
In a new terminal, navigate to your project root and run:
npm create vite@latest frontend -- --template reactcd frontendnpm installnpm i zustand react-router-dom
2.2 Set up routing and main component
Replace the content of src/App.jsx
with:
import { useEffect } from 'react'import { Routes, Route } from 'react-router-dom'import Home from './pages/Home.jsx'import Login from './pages/Login.jsx'import Register from './pages/Register.jsx'import { useAuthStore } from './store/authStore'function App() {const setCsrfToken = useAuthStore((state) => state.setCsrfToken)useEffect(() => {void setCsrfToken()}, [setCsrfToken])return (<div className="m-auto flex w-1/2 gap-2 p-10"><Routes><Route path="/" element={<Home />} /><Route path="/login" element={<Login />} /><Route path="/register" element={<Register />} /></Routes></div>)}export default App
2.3 Create authentication store
Create src/store/authStore.js
and add:
import { create } from 'zustand'import { persist, createJSONStorage } from 'zustand/middleware'export const useAuthStore = create(persist((set, get) => ({user: null,isAuthenticated: false,setCsrfToken: async () => {const response = await fetch('http://localhost:8000/api/set-csrf-token',{method: 'GET',credentials: 'include',},)const data = await response.json()return data.csrftoken},login: async (email, password) => {const csrftoken = await get().setCsrfToken()const response = await fetch('http://localhost:8000/api/login', {method: 'POST',headers: {'Content-Type': 'application/json','X-CSRFToken': csrftoken,},body: JSON.stringify({email,password,}),credentials: 'include',})const data = await response.json()if (data.success) {set({isAuthenticated: true,})get().fetchUser()} else {set({user: null,isAuthenticated: false,})}return data.success},logout: async () => {try {const csrftoken = await get().setCsrfToken()const response = await fetch('http://localhost:8000/api/logout', {method: 'POST',headers: {'X-CSRFToken': csrftoken,},credentials: 'include',})if (response.ok) {set({user: null,isAuthenticated: false,})}} catch (error) {console.error('Logout failed', error)throw error}},fetchUser: async () => {try {const csrftoken = await get().setCsrfToken()const response = await fetch('http://localhost:8000/api/user', {credentials: 'include',headers: {'Content-Type': 'application/json','X-CSRFToken': csrftoken,},})if (response.ok) {const data = await response.json()set({user: data,isAuthenticated: true,})} else {set({user: null,isAuthenticated: false,})}} catch (error) {console.error('Failed to fetch user', error)set({user: null,isAuthenticated: false,})}},}),{name: 'auth-storage',storage: createJSONStorage(() => localStorage),},),)export const getCSRFToken = () => {/*We get the csrftoken from the cookeis in the user's browser.You can use an package here if you want nicer code, or just use the code below.*/const name = 'csrftoken'let cookieValue = nullif (document.cookie && document.cookie !== '') {const cookies = document.cookie.split(';')for (let i = 0; i < cookies.length; i++) {const cookie = cookies[i].trim()if (cookie.substring(0, name.length + 1) === name + '=') {cookieValue = decodeURIComponent(cookie.substring(name.length + 1))break}}}if (cookieValue === null) {throw new Error('Missing CSRF cookie.')}return cookieValue}
2.4 Create React components
Create the following files in the src/pages
directory:
- Create your home page at
src/pages/Home.jsx
:
import { useEffect } from 'react'import { Link, useNavigate } from 'react-router-dom'import { useAuthStore } from '../store/authStore'function Home() {const { isAuthenticated, user, logout, fetchUser } = useAuthStore()const navigate = useNavigate()useEffect(() => {void fetchUser()}, [fetchUser])const handleLogout = async () => {try {await logout()navigate('/login')} catch (error) {console.error(error)}}return (<div className="2"><h1>Welcome to the home page</h1>{isAuthenticated ? (<div><p>Hi there {user?.username}!</p><p>You are logged in.</p><p>{user?.secret_fact}</p><button onClick={handleLogout}>Logout</button></div>) : (<p className="flex flex-col">You are not logged in.<Link to="/login">Login</Link><Link to="/register">Register</Link></p>)}</div>)}export default Home
- Create your login page
src/pages/Login.jsx
:
import React, { useState } from 'react'import { useNavigate } from 'react-router-dom'import { useAuthStore } from '../store/authStore'function Login() {const [email, setEmail] = useState('')const [password, setPassword] = useState('')const [error, setError] = useState('')const login = useAuthStore((state) => state.login)const navigate = useNavigate()const handleSubmit = async (e) => {e.preventDefault()setError('')const success = await login(email, password)if (success) {navigate('/')} else {setError('Login failed. Please check your credentials.')}}return (<div className="login flex flex-col gap-5"><h1>Login</h1><form onSubmit={handleSubmit}><div><label htmlFor="email">Email:</label><inputid="email"type="text"value={email}onChange={(e) => setEmail(e.target.value)}required/></div><div><label htmlFor="password">Password:</label><inputid="password"type="password"value={password}onChange={(e) => setPassword(e.target.value)}required/></div><button type="submit">Login</button></form>{error && <p className="error">{error}</p>}</div>)}export default Login
- Create your register page at
src/pages/Register.jsx
:
import { useState } from 'react'import { useNavigate } from 'react-router-dom'import { getCSRFToken } from '../store/authStore'function Register() {const [email, setEmail] = useState('')const [password, setPassword] = useState('')const [error, setError] = useState('')const [success, setSuccess] = useState('')const navigate = useNavigate()const handleSubmit = async (e) => {e.preventDefault()try {const response = await fetch('http://localhost:8000/api/register', {method: 'POST',headers: {'Content-Type': 'application/json','X-CSRFToken': getCSRFToken(),},body: JSON.stringify({ email, password }),credentials: 'include',})const data = await response.json()if (response.ok) {setSuccess('Registration successful! Please log in.')setTimeout(() => {navigate('/login')}, 1000)} else {setError(data.error || 'Registration failed')}} catch (err) {setError('An error occurred during registration: ' + err)}}return (<div><h1>Register</h1><form onSubmit={handleSubmit}><div><label htmlFor="email">Email:</label><inputid="email"type="email"value={email}onChange={(e) => setEmail(e.target.value)}required/></div><div><label htmlFor="password">Password:</label><inputid="password"type="password"value={password}onChange={(e) => setPassword(e.target.value)}required/></div><button type="submit">Register</button></form>{error && <p>{error}</p>}{success && <p>{success}</p>}</div>)}export default Register
2.5 Update main.jsx
We want to use the React router to navigate between our different React components, which represent pages.
- So, replace the content of
src/main.jsx
with:
import React from 'react'import ReactDOM from 'react-dom/client'import './index.css'import App from './App'import { BrowserRouter as Router } from 'react-router-dom'const root = ReactDOM.createRoot(document.getElementById('root'))root.render(<React.StrictMode><Router><App /></Router></React.StrictMode>,)
2.6 Add Tailwind CSS (Optional)
To add Tailwind CSS for styling:
- Install Tailwind and its dependencies:
npm install -D tailwindcss postcss autoprefixernpx tailwindcss init -p
- Update
tailwind.config.js
:
/** @type {import('tailwindcss').Config} */export default {content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],theme: {extend: {},},plugins: [],}
- Replace the content of
src/index.css
with:
@tailwind base;@tailwind components;@tailwind utilities;
Part 3: Testing and Verification
Now that we have set up both the backend and frontend, let's check ensure everything is working correctly.
3.1 Backend Testing
- Start your Django server:
python manage.py runserver
-
Visit
http://localhost:8000/api/docs
to see the Django Ninja API documentation. -
Test each endpoint using the interactive documentation:
- Try to register a new user
- Attempt to login with the created user
- Fetch the user information
- Test the logout functionality
If all these operations work without errors, your backend is set up correctly.
3.2 Frontend Testing
- Start your React development server:
cd frontendnpm run dev
-
Visit
http://localhost:5173
(or the URL provided in the console). -
Test the following user flows:
3.3 Cross-Origin Resource Sharing (CORS) Testing
- Ensure both your Django and React servers are running.
- In the React app, try to register, login, and fetch user data.
- Open the browser's developer tools and check the Console and Network tabs for any CORS-related errors.
If you encounter CORS issues, double-check your Django settings to ensure the CORS middleware and settings are correctly configured.
Congrats 🎉
You've set up a full-stack application with Django Ninja as the backend API and React as the frontend, implementing user authentication. This setup provides a solid foundation for building more complex applications.
Next Steps
Here are some ideas of next things to build.
- Implement password reset functionality
- Add email verification for new user registrations
- Create a user profile page where users can update their information