Businesses work with dozens of vendors, suppliers, and partners every day.
But most companies still depend on emails, spreadsheets, and WhatsApp groups for vendor communication. This leads to delays, miscommunication, & zero transparency.
That’s why the need for a secure & real-time vendor collaboration portal is growing rapidly.
A modern collaboration system allows companies to communicate instantly, share documents safely, assign tasks, track updates, & keep every vendor aligned in one place.
When it comes to building such a system, Django + React is the perfect combination. Django provides powerful backend security, while React offers a fast and dynamic frontend for real-time collaboration.
In this blog, you’ll learn exactly how to build a secure and real-time vendor portal using Django + React, including the system architecture & backend setup.
By the end, you’ll have a fully working Django React vendor portal that you can customize for your own business or clients.
What Is a Vendor Collaboration Portal? (And Why Build It Yourself?)
A vendor collaboration portal (or partner communication platform) is a central system where companies and vendors can communicate, share files, track updates, and manage daily operations in real time.
Instead of scattered conversations, everything runs through one secure hub. It solves these points:
- Slow email-based communication.
- Missing or outdated vendor documents.
- No proper tracking of tasks or project updates.
- Miscommunication between teams, suppliers, and partners.
- Zero real-time visibility in logistics and procurement workflows.
That’s why building your own custom vendor portal is often the smarter choice; you get control, flexibility, and full ownership of the system.
A Django React vendor portal fits perfectly here because it gives you:
- A scalable backend
- A real-time frontend
- Full customization options
- Secure data handling
- API-first architecture
Why Django + React Is the Perfect Tech Stack for Real-Time Collaboration?
If you’re building a modern real-time vendor portal, Django + React is one of the strongest tech stacks you can choose.
1. Advantages of Django REST Framework
- Fast API development.
- Built-in authentication & permissions.
- Easy vendor models, document management, and task workflows.
- Secure and scalable.
2. Why React Is Ideal for Dynamic UIs?
- React helps create a smooth, modern, app-like interface that users love:
- Real-time updates without page reloads.
- Component-based UI for vendor dashboards, tasks, and chat.
- Works perfectly with REST APIs and WebSockets.
3. How Django Channels + WebSockets Enable Real-Time Features?
- To enable instant notifications, live chat, and live updates, your portal needs real-time communication.
- Django Channels + WebSockets make this possible. They allow:
- Real-time chat between vendors and teams.
- Live task updates.
- Document status changes.
- Broadcast notifications.
4. Strong Security Features
- A vendor portal must be secure, and Django provides:
- JWT authentication.
- Role-based access control (admin, vendor, manager, etc.).
- HTTPS.
- Session protection.
- Safe file handling and permission checks.
This makes Django + React ideal for building a secure vendor collaboration system.
What Will Be the System Architecture for the Vendor Collaboration Portal?
Your Django React vendor portal system architecture will have multiple layers working together:
High-Level Architecture:
- Django REST API: handles authentication, vendor data, tasks, & documents.
- Django Channels: Powers WebSockets for real-time chat & live notifications.
- React Frontend: User-friendly dashboard with instant updates.
- PostgreSQL Database: Secure, scalable storage for all vendor data.
- Redis: Message broker for real-time events.
- Nginx (optional):Production-ready server setup.
Vendors ↔ Portal ↔ Real-Time Messaging
- Messages, notifications, task updates, and documents flow instantly between teams and vendors.
- No page refresh, no delays, real-time collaboration at its best.
What Are Core Features We’ll Build in This Real-Time Vendor Portal?
In this Django React vendor portal tutorial, we will build all the essential features required for a modern real-time collaboration system.
- Vendor registration & login with JWT authentication.
- Secure role-based access, so only authorized users can view data.
- Real-time chat using WebSockets.
- Vendor document upload, sharing, and approval.
- Task assignments with live status updates.
- Instant notifications for every important event.
- Activity log to track communication history.
- API-first architecture so you can later build mobile apps as well.
By the end, your real-time vendor portal Django app will be fully functional, scalable, and ready to deploy.
Setting Up the Backend: Django REST + Django Channels
Goal: Build a secure, real-time backend for the vendor portal using Django REST Framework (DRF) + djangorestframework-simplejwt for JWT authentication + channels + Redis for WebSockets.
Install packages
# Create venv & activate before this
pip install django djangorestframework djangorestframework-simplejwt channels channels-redis psycopg2-binary Pillow
Project setup (commands)
django-admin startproject vendor_portal
cd vendor_portal
python manage.py startapp core
python manage.py migrate
settings.py (essential parts)
# settings.py (abridged)
INSTALLED_APPS = [
"django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes",
"django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles",
"rest_framework", "rest_framework.authtoken", "core", "channels"
]
# Channels / Redis
ASGI_APPLICATION = "vendor_portal.asgi.application"
REDIS_URL = "redis://127.0.0.1:6379"
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": { "hosts": [REDIS_URL] },
}
}
# DRF + JWT
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework_simplejwt.authentication.JWTAuthentication",
),
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
}
# Media / static (for docs)
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"
ASGI entrypoint (asgi.py)
# asgi.py
import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
import core.routing
from core.middleware import JwtAuthMiddleware # custom middleware
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "vendor_portal.settings")
django_asgi_app = get_asgi_application()
application = ProtocolTypeRouter({
"http": django_asgi_app,
"websocket": JwtAuthMiddleware( # authenticates JWT for websocket
URLRouter(core.routing.websocket_urlpatterns)
),
})
JWT Auth middleware for Channels (core/middleware.py)
# core/middleware.py
import jwt
from django.conf import settings
from channels.db import database_sync_to_async
from django.contrib.auth import get_user_model
from channels.middleware import BaseMiddleware
from urllib.parse import parse_qs
User = get_user_model()
@database_sync_to_async
def get_user(user_id):
try:
return User.objects.get(id=user_id)
except User.DoesNotExist:
return None
class JwtAuthMiddleware(BaseMiddleware):
async def __call__(self, scope, receive, send):
query_string = scope.get("query_string", b"").decode()
qs = parse_qs(query_string)
token = qs.get("token")
scope["user"] = AnonymousUser = None
if token:
token = token[0]
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
user = await get_user(payload.get("user_id"))
scope["user"] = user
except Exception as e:
scope["user"] = None
return await super().__call__(scope, receive, send)
Models (core/models.py)
from django.db import models
from django.contrib.auth import get_user_model
User = get_user_model()
class VendorProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
company_name = models.CharField(max_length=255)
contact_number = models.CharField(max_length=30, blank=True, null=True)
def __str__(self):
return self.company_name or self.user.username
class Task(models.Model):
title = models.CharField(max_length=255)
description = models.TextField(blank=True)
created_by = models.ForeignKey(User, related_name="tasks_created", on_delete=models.CASCADE)
assignees = models.ManyToManyField(User, related_name="tasks_assigned", blank=True)
status = models.CharField(max_length=30, default="open") # open, in-progress, done
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Message(models.Model):
sender = models.ForeignKey(User, related_name="sent_messages", on_delete=models.CASCADE)
room = models.CharField(max_length=255) # simple room name per vendor chat
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
class Document(models.Model):
owner = models.ForeignKey(User, related_name="documents", on_delete=models.CASCADE)
file = models.FileField(upload_to="vendor_docs/")
title = models.CharField(max_length=255)
uploaded_at = models.DateTimeField(auto_now_add=True)
is_public = models.BooleanField(default=False)
Serializers (core/serializers.py)
from rest_framework import serializers
from django.contrib.auth import get_user_model
from .models import VendorProfile, Task, Message, Document
User = get_user_model()
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ("id", "username", "email", "first_name", "last_name")
class VendorProfileSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = VendorProfile
fields = "__all__"
class TaskSerializer(serializers.ModelSerializer):
created_by = UserSerializer(read_only=True)
class Meta:
model = Task
fields = "__all__"
class MessageSerializer(serializers.ModelSerializer):
sender = UserSerializer(read_only=True)
class Meta:
model = Message
fields = "__all__"
class DocumentSerializer(serializers.ModelSerializer):
owner = UserSerializer(read_only=True)
class Meta:
model = Document
fields = "__all__"
Views (DRF viewsets & JWT) (core/views.py)
from rest_framework import viewsets, permissions
from .models import VendorProfile, Task, Message, Document
from .serializers import VendorProfileSerializer, TaskSerializer, MessageSerializer, DocumentSerializer
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser, FormParser
class VendorProfileViewSet(viewsets.ModelViewSet):
queryset = VendorProfile.objects.select_related("user").all()
serializer_class = VendorProfileSerializer
permission_classes = [permissions.IsAuthenticated]
class TaskViewSet(viewsets.ModelViewSet):
queryset = Task.objects.all().order_by("-created_at")
serializer_class = TaskSerializer
permission_classes = [permissions.IsAuthenticated]
def perform_create(self, serializer):
serializer.save(created_by=self.request.user)
class MessageViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Message.objects.all().order_by("created_at")
serializer_class = MessageSerializer
permission_classes = [permissions.IsAuthenticated]
class DocumentViewSet(viewsets.ModelViewSet):
queryset = Document.objects.all().order_by("-uploaded_at")
serializer_class = DocumentSerializer
permission_classes = [permissions.IsAuthenticated]
parser_classes = (MultiPartParser, FormParser)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
How to Build the Frontend Using React?
Goal: React frontend that calls DRF APIs and connects to Channels WebSocket for real-time chat, notifications, tasks, and secure file sharing.
Create React project
npx create-react-app vendor-portal-frontend
cd vendor-portal-frontend
npm install axios jwt-decode react-router-dom
# optional: npm install reconnecting-websocket
Folder structure
src/
api/
axios.js
components/
Auth/
Login.jsx
PrivateRoute.jsx
Dashboard/
Dashboard.jsx
VendorList.jsx
Chat/
ChatRoom.jsx
Tasks/
Tasks.jsx
Documents/
DocumentUpload.jsx
Notifications/
Notifications.jsx
App.jsx
index.jsx
Axios + Auth helper (src/api/axios.js)
import axios from "axios";
const API_BASE = process.env.REACT_APP_API_BASE || "http://127.0.0.1:8000";
const instance = axios.create({
baseURL: `${API_BASE}/api/`,
timeout: 10000,
});
export function setAuthToken(token) {
if (token) instance.defaults.headers.common["Authorization"] = `Bearer ${token}`;
else delete instance.defaults.headers.common["Authorization"];
}
export default instance;
Login page (get JWT) src/components/Auth/Login.jsx
import React, { useState } from "react";
import axios from "axios";
import { setAuthToken } from "../../api/axios";
const Login = ({ onLogin }) => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const handleSubmit = async e => {
e.preventDefault();
try {
const res = await axios.post("http://127.0.0.1:8000/api/token/", { username, password });
const { access } = res.data;
localStorage.setItem("access_token", access);
setAuthToken(access);
onLogin();
} catch (err) {
alert("Login failed");
}
};
return (
<form onSubmit={handleSubmit}>
<h2>Login</h2>
<input value={username} onChange={e => setUsername(e.target.value)} placeholder="username" />
<input type="password" value={password} onChange={e => setPassword(e.target.value)} placeholder="password" />
<button type="submit">Login</button>
</form>
);
};
export default Login;
Protected route helper PrivateRoute.jsx
import React from "react";
import { Navigate } from "react-router-dom";
export default function PrivateRoute({ children }) {
const token = localStorage.getItem("access_token");
return token ? children : <Navigate to="/login" />;
}
Chat component (WebSocket, real-time) src/components/Chat/ChatRoom.jsx
import React, { useEffect, useState, useRef } from "react";
import axios from "../../api/axios";
const ChatRoom = ({ roomName }) => {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState("");
const wsRef = useRef(null);
useEffect(() => {
const token = localStorage.getItem("access_token");
// connect with token in query param
const wsProtocol = window.location.protocol === "https:" ? "wss" : "ws";
const wsUrl = `${wsProtocol}://127.0.0.1:8000/ws/chat/${roomName}/?token=${token}`;
wsRef.current = new WebSocket(wsUrl);
wsRef.current.onopen = () => console.log("WebSocket connected");
wsRef.current.onmessage = (e) => {
const data = JSON.parse(e.data);
setMessages(prev => [...prev, data]);
};
wsRef.current.onclose = () => console.log("WebSocket disconnected");
// load recent messages from REST API
(async () => {
const res = await axios.get("messages/");
setMessages(res.data.filter(m => m.room === roomName));
})();
return () => {
wsRef.current.close();
};
}, [roomName]);
const send = () => {
if (!input.trim()) return;
wsRef.current.send(JSON.stringify({ message: input }));
setInput("");
};
return (
<div>
<h3>Chat: {roomName}</h3>
<div style={{height: 300, overflowY: "scroll", border: "1px solid #ddd"}}>
{messages.map((m, idx) => (
<div key={idx}><strong>{m.username || "User"}:</strong> {m.message || m.content}</div>
))}
</div>
<input value={input} onChange={e => setInput(e.target.value)} placeholder="Type a message" />
<button onClick={send}>Send</button>
</div>
);
};
export default ChatRoom;
Tasks module (basic) src/components/Tasks/Tasks.jsx
import React, { useEffect, useState } from "react";
import axios from "../../api/axios";
const Tasks = () => {
const [tasks, setTasks] = useState([]);
const [title, setTitle] = useState("");
useEffect(() => {
(async () => {
const res = await axios.get("tasks/");
setTasks(res.data);
})();
}, []);
const createTask = async () => {
if (!title) return;
const res = await axios.post("tasks/", { title });
setTasks(prev => [res.data, ...prev]);
setTitle("");
};
return (
<div>
<h3>Tasks</h3>
<input value={title} onChange={e => setTitle(e.target.value)} placeholder="New task title" />
<button onClick={createTask}>Create</button>
<ul>
{tasks.map(t => <li key={t.id}>{t.title} — {t.status}</li>)}
</ul>
</div>
);
};
export default Tasks;
Secure file-sharing (upload) src/components/Documents/DocumentUpload.jsx
import React, { useState, useEffect } from "react";
import axios from "../../api/axios";
const DocumentUpload = () => {
const [file, setFile] = useState(null);
const [docs, setDocs] = useState([]);
useEffect(() => {
(async () => {
const res = await axios.get("documents/");
setDocs(res.data);
})();
}, []);
const upload = async () => {
if (!file) return;
const form = new FormData();
form.append("file", file);
form.append("title", file.name);
const res = await axios.post("documents/", form, {
headers: {'Content-Type': 'multipart/form-data'}
});
setDocs(prev => [res.data, ...prev]);
setFile(null);
};
return (
<div>
<h3>Documents</h3>
<input type="file" onChange={e => setFile(e.target.files[0])} />
<button onClick={upload}>Upload</button>
<ul>
{docs.map(d => (
<li key={d.id}><a href={`http://127.0.0.1:8000${d.file}`}>{d.title}</a> by {d.owner.username}</li>
))}
</ul>
</div>
);
};
export default DocumentUpload;
How to Integrate Django REST API + React Frontend?
This section shows how React calls the Django REST API (Axios examples), how authentication is handled, protected routes, and how React connects to Django Channels for real-time messaging.
API calls (Axios examples)
Get tasks
import axios from "./api/axios";
const res = await axios.get("tasks/");
console.log(res.data);
Create task
await axios.post("tasks/", { title: "New task" });
Upload document
const form = new FormData();
form.append("file", file);
form.append("title", file.name);
await axios.post("documents/", form, { headers: {'Content-Type': 'multipart/form-data'}});
Get user profile
const res = await axios.get("profiles/");
All these endpoints use JWT auth (Bearer token header set by setAuthToken() earlier).
Here’s the Complete GitHub Code to Build a Vendor Collaboration Portal in Django + React.
How Do We Build Secure & Real-Time Vendor Collaboration Platforms?
- We build secure portals using JWT, HTTPS, encrypted APIs, and strict permission control for safe vendor interactions.
- We integrate WebSockets with Django Channels for instant chat, live task updates, and real-time vendor notifications.
- Our API-first architecture ensures fast performance, strong security, and easy integration with mobile or third-party tools.
Want a Real-Time Django or React Solution? Contact Us Now!
Create Your Own Secure, Real-Time Vendor Portal Today
A secure and real-time vendor collaboration portal can transform how your business communicates with suppliers and partners.
With Django + React, you get reliability, speed, scalability, and full customization, all the ingredients needed to build a powerful collaboration system.
Start implementing it step-by-step, and create a modern vendor portal that truly stands out.
FAQs
- You can build it by combining Django REST Framework for APIs, Django Channels for real-time updates, and React for the frontend UI.
- Yes. With Django Channels and WebSockets, Django supports real-time communication for chat, notifications, and live updates.
- Use JWT authentication, role-based permissions, input validation, secure file storage, HTTPS, and WebSocket token authentication.
- Yes, React consumes Django REST APIs via Axios and connects to Django Channels via WebSockets for real-time events.