Integrating LLM Using API to a Django and React App
This LLM-API consumer, that I am presenting here, is built upon a Django application using React as static files. With the API consumer, I implemented a language corrector and translator application, which is a kind of playground to try creating something great.
Django
ReactJS
PostgeSQL
Django REST framework
You can find here the GitHub repository of the template, which is the foundation or starting point of the application.
Setting up the environment
Download the .zip
from GitHub
Move the files to a new directory. Let’s name it llmlang
.
Open CLI and change the directory using the $ cd llmlang
command
Create a virtual environment for the project
python -m venv venv
Activate the enviroment
venv\scripts\activate
Install the dependencies
pip install -r requirements.txt
Create an .env file and update with the next environment variables
Generate new key for the SECRET_KEY
python -c 'import secrets;print(secrets.token_hex(32))'
# .env
SECRET_KEY=<generated-secret-key>
Create new database
Installing Postgres on your device is necessarry.
Open CLI
psql -U postgres
Create a database, let’s name it langapp.
CREATE DATABASE langapp WITH OWNER postgres;
Update .env
# .env
DATABASE_URL=postgresql://postgres:<password>@localhost:5432/langapp
SSL_REQUIRE=False
Migrate data
python manage.py makemigrations accounts
python manage.py migrate
Starting the language app development
Create model
# langapp/models.py
from django.db import models
from django.utils import timezone
class Language(models.Model):
timestamp = models.DateTimeField(default=timezone.now)
# These fields can contain up to 1000 characters to accommodate lengthy user inputs.
prompt = models.CharField(max_length=1000)
correction = models.CharField(max_length=1000)
translation = models.CharField(max_length=1000)
def __str__(self):
return self.prompt
Migrate the data
python manage.py makemigrations langapp
python manage.py migrate lang
Install Django REST framework and update config/settings.py
pip install djangorestframework
Add ‘rest-framework’ to the INSTALLED_APPS
. And the next initial setup to the end of the file:
# config/settings.py
# REST Framework settings
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
],
}
langapp/serializers.py
Create the file then update with this:
# langapp/serializers.py
from rest_framework import serializers
from .models import Language
class LanguageSerializer(serializers.ModelSerializer):
class Meta:
model = Language
fields = '__all__'
langapp/llm-api.py
You need an API key from OpenAI or another provider.
pip install openai
If you choose a provider other than OpenAI, always read its documentation to how to apply the API.
Let’s integrate and costumize the LLM-API in a new llm_api.py
file.
# langapp/llm_api.py
import openai
from environs import Env
from rest_framework.response import Response
from .serializers import LanguageSerializer
env = Env()
env.read_env()
class Assistant:
def __init__(self, request):
self.request = request
self.prompt = request.data['prompt']
self.model = 'gpt-4o-mini'
self.role = """Your response should be the correction of the given prompt.
If the prompt is already correct, respond with 'Your english is correct'."""
self.key = env.str("OPENAI_API_KEY")
def llm_response(self):
if self.key:
openai.api_key = self.key
response = openai.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": self.role},
{"role": "user", "content": self.prompt}
],
temperature=0.6, # 0-1
max_tokens=512,
)
return response.choices[0].message.content
else:
return f"@Thank you for the prompt! Possible problem with API key. We are working on it."
def get_data(self):
correction = self.llm_response()
self.role="""Anyanyelvi szintű magyarul beszélő vagy és az a feladatod, hogy a szövegeket, amiket a felhasználó ad,
magyarra fordíts! Ha magyarul kapod a feladatot, akkor ugyanazt a szöveget válaszold vissza!"""
translation = self.llm_response()
data = { 'prompt': self.prompt, 'correction': correction, 'translation': translation }
# run on server
serializer = LanguageSerializer(data=data, context={'request': self.request})
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data)
langapp/views.py
# langapp/views.py
from django.views.generic import TemplateView
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Language
from .serializers import LanguageSerializer
from .llm_api import Assistant
# React home page
class React(TemplateView):
template_name = 'index.html'
class LangAI(APIView):
serializer_class = LanguageSerializer
def get(self, request):
detail = Language.objects.all()
serializer = LanguageSerializer(detail, many=True)
return Response(serializer.data)
def post(self, request):
return Assistant(request).get_data()
def delete(self, request):
try:
detail = Language.objects.all()
detail.delete()
return Response({'message': 'Item deleted successfully.'})
except Language.DoesNotExist:
return Response({'error': 'Item not found.'})
langapp/urls.py
Create this file and update it with the pattern below.
# langapp/urls.py
from django.urls import path
from .views import LangAI
urlpatterns = [
path('api/chat/', LangAI.as_view(), name='chat'),
]
config/urls.py
Update the config.urls pattern list with langapp.urls.
# config/urls.py
from django.contrib import admin
from django.urls import path, include
from django.urls import re_path
from langapp.views import React
urlpatterns = [
path('admin/', admin.site.urls),
# App
path("", include("langapp.urls")),
# React
re_path(r'^.*', React.as_view(), name='frontend'),
]
CORS
pip install django-cors-headers
config/settings.py
Add ‘corsheaders’ to the INSTALLED_APPS
.
Add ‘corsheaders.middleware.CorsMiddleware’ to the MIDDLEWARE
.
CorsMiddleware should be placed as high as possible, especially before any middleware that can generate responses such as Django’s CommonMiddleware or Whitenoise’s WhiteNoiseMiddleware. If it is not before, it will not be able to add the CORS headers to these responses. docs
# config/settings.py
# CORS
CORS_ALLOWED_ORIGINS = [
'http://localhost:3000',
'http://localhost:5173',
'http://localhost:8000',
'https://<example>up.railway.app',
'https://<example>.com',
]
Setting up frontend
cd frontend
Install axios
npm install axios
Create frontend/.env
# frontend/.env
export VITE_APP_URL=http://localhost:8000
Create src/axios.js and add the code:
// src/axios.jsx
import axios from 'axios';
// Export base url from env variable
const baseURL = import.meta.env.VITE_APP_URL;
// Get csrf token
const getCSRFToken = () => {
const csrfToken = document.cookie.match(/csrftoken=([\w-]+)/);
return csrfToken ? csrfToken[1] : null;
};
// Axios instance
const axiosInstance = axios.create({
baseURL: baseURL,
timeout: 5000,
headers: {
'X-CSRFToken': getCSRFToken(),
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
export default axiosInstance;
Update App.jsx
// src/App.jsx
import React, {useEffect, useState} from 'react';
import axiosInstance from './axios';
import './App.css';
function App() {
const [response, setResponse] = useState([]);
const [formData, setFormData] = useState({ prompt: '', });
const [isLoading, setIsLoading] = useState(false);
const [fadeIn, setFadeIn] = useState(false);
// path
const path = import.meta.env.VITE_APP_URL + '/api/chat/';
const pathname = window.location.pathname.endsWith('/') ? window.location.pathname.slice(0, -1) : window.location.pathname;
const postPrompt = (e) => {
setIsLoading(true); // spinner on
e.preventDefault();
axiosInstance.post(path, {
prompt: formData.prompt,
})
.then((res) => {
// console.log(res);
setFormData({ prompt: '', });
setResponse((response) => [...response, res.data]);
})
.catch((error) => {
console.log(error);
});
};
const deleteItem = (id) => {
axiosInstance.delete(path)
.then((res) => {
// get answer
axiosInstance.get(path)
.then((res) => {
setResponse(res.data);
})
.catch((error) => {
console.log(error);
});
})
.catch((error) => {
console.log(error);
});
};
// useEffect for getting data
useEffect(() => {
// get answer
console.log(path); // test
axiosInstance.get(path)
.then((res) => {
setResponse(res.data);
})
.catch((error) => {
console.log(error);
});
}, [path]);
// useEffect for spinner
useEffect(() => {
setIsLoading(false); // spinner off when goes to the bottom of the response list
}, [response]);
useEffect(() => {
// Trigger the fade-in effect when the component mounts
setFadeIn(true);
}, []);
// handle input
const handleInput = (event) => {
setFormData({
...formData,
[event.target.name]: event.target.value,
});
};
return (
<div className="App">
<h1>Corrector</h1>
<h1> * </h1>
<div className="response">
{window.location.port === '5173' ? (
<>
<div className="prompt">This is an english language corrector.</div>
<div className='correction'>Your English is correct.</div>
<div className='translation'>Ez egy angol nyelvhelyesség-javító.</div>
</>
) : null}
{response.map((item) => (
<div key={item.id} className={`item ${fadeIn ? 'fade-in' : ''}`}>
<div className="prompt">{item.prompt}</div>
<div className='correction'>{item.correction}</div>
<div className='translation'>{item.translation}</div>
</div>
))}
{isLoading && <div className="loading">Loading...</div>}
</div>
<form onSubmit={postPrompt}>
<h1> * </h1>
<textarea
type="text"
name="prompt"
value={formData.prompt}
onChange={handleInput}
placeholder="Enter your text"
required
/>
<button type="submit">Submit</button>
</form>
<button className="delete" onClick={deleteItem}>Delete</button>
</div>
);
}
export default App;
Update App.css
/* src/App.css */
body {
background: #324065;
}
.App {
text-align: center;
font-family: 'Courier New', Courier, monospace;
color: #0c1823;
}
h1 {
color: #d1a266;
}
.loading {
color: #d1a266;
}
textarea[type="text"] {
width: 600px;
height: 150px;
font-size: 16px;
text-align: left;
vertical-align: top;
border-radius: 8px;
}
button {
display: block;
margin: 0 auto;
margin-top: 10px;
width: 605px;
background-color: #fdd95d;
color: black;
padding: 10px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
.delete {
background-color: #cd4a33;
}
.delete:hover {
background-color: #d21f0a;
}
.prompt,
.correction,
.translation {
justify-content: center;
display: flex;
width: 590px;
padding: 8px 12px;
margin: 8px auto;
border-radius: 8px;
}
.prompt {
background-color: #add4e5;
}
.correction {
background-color: #fff7b3;
}
.translation {
background-color: #add4e5;
margin-bottom: 32px;
}
Update main.jsx
// src/main.jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)
npm run build
Run the Application
cd ..
python manage.py runserver