🚀 Keep Your Django Code
Use your existing models, permissions, and business logic. No REST APIs needed – your Django ORM works in the frontend.
Build decoupled JavaScript SPAs with Django-Template like simplicity. Turn your Django backend into a live, reactive data source.
You've built a solid Django backend. Now you want to add a modern, decoupled JavaScript SPA without rewriting everything.
The old way: Build REST APIs, manage state, handle WebSockets, debug sync issues for weeks.
StateZero way: Your Django models work directly in JavaScript SPAs. Build Vue.js frontends or vanilla JS applications with Django-like simplicity but delivering full modern SPA experiences.
Check out our live demo to see Django models working in real-time with optimistic updates, live querysets, and file uploads.
You have working Django models and want to build a modern decoupled SPA. You want the frontend to deploy separately, scale independently, and use modern JavaScript frameworks. But implementing real-time, reactive SPAs means:
# Your Django models work perfectly
class Task(models.Model):
title = models.CharField(max_length=200)
completed = models.BooleanField(default=False)
user = models.ForeignKey(User, on_delete=models.CASCADE)
def clean(self):
if not self.title.strip():
raise ValidationError("Title cannot be empty")
// But your frontend becomes a nightmare of boilerplate
const [tasks, setTasks] = useState([]);
const [loading, setLoading] = useState(true);
// Fetch data
useEffect(() => {
fetch('/api/tasks/')
.then(res => res.json())
.then(data => {
setTasks(data);
setLoading(false);
});
}, []);
// Optimistic updates (so UI feels snappy)
const toggleTask = async (taskId) => {
// Update UI immediately
setTasks(prev => prev.map(task =>
task.id === taskId ? {...task, completed: !task.completed} : task
));
try {
await fetch(`/api/tasks/${taskId}/`, {
method: 'PATCH',
body: JSON.stringify({completed: !task.completed})
});
} catch (error) {
// Revert on failure
setTasks(prev => prev.map(task =>
task.id === taskId ? {...task, completed: !task.completed} : task
));
}
};
// Real-time updates (so everyone stays in sync)
useEffect(() => {
const ws = new WebSocket('ws://localhost:8000/tasks/');
ws.onmessage = (event) => {
const {type, task} = JSON.parse(event.data);
if (type === 'task_updated') {
setTasks(prev => prev.map(t => t.id === task.id ? task : t));
}
};
return () => ws.close();
}, []);
// Plus error handling, conflict resolution, connection state...
You wrote 50+ lines of complex code and haven't built a single feature yet.
# Your Django models stay exactly the same
class Task(models.Model):
title = models.CharField(max_length=200)
completed = models.BooleanField(default=False)
user = models.ForeignKey(User, on_delete=models.CASCADE)
def clean(self):
if not self.title.strip():
raise ValidationError("Title cannot be empty")
// Your decoupled Vue.js/vanilla JS SPA becomes this simple
import { useQueryset } from '@statezero/core/vue';
import { Task } from './models';
const tasks = useQueryset(() => Task.objects.filter({ user: currentUser.id }));
const toggleTask = (task) => {
task.completed = !task.completed;
task.save(); // Validates in Django, updates everywhere instantly
};
// That's it. You get:
// ✅ Optimistic updates - UI feels instant
// ✅ Real-time sync - Changes appear automatically
// ✅ Django validation - Your clean() method runs
// ✅ Rich permissions - Field-level and object-level access control
// ✅ Decoupled deployment - Frontend and backend deploy independently
// ✅ Auto-optimized queries - StateZero handles select_related/prefetch_related
// 5 lines instead of 50+. Build your SPA features.
StateZero doesn't replace Django – it supercharges it. Your existing Django app becomes a live, reactive data source:
# Keep your existing Django models (zero changes)
class Order(models.Model):
product_name = models.CharField(max_length=100)
quantity = models.PositiveIntegerField()
status = models.CharField(max_length=20, choices=STATUS_CHOICES)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
def clean(self):
if self.quantity <= 0:
raise ValidationError("Quantity must be positive")
# Keep your existing permissions
class OrderPermission(AbstractPermission):
def filter_queryset(self, request, queryset):
if request.user.is_staff:
return queryset
return queryset.filter(created_by=request.user)
def allowed_actions(self, request, model):
return {ActionType.READ, ActionType.CREATE}
def visible_fields(self, request, model):
return {"id", "product_name", "quantity", "status", "created_by"}
def editable_fields(self, request, model):
return {"product_name", "quantity"}
// Use them directly in your frontend - full Django ORM power
const myOrders = useQueryset(() => Order.objects.filter({
created_by: currentUser.id, // snake_case field names (like Django)
status: 'pending'
}));
const createOrder = (productName, quantity) => {
const order = Order.objects.create({
product_name: productName, // snake_case fields, camelCase methods
quantity: quantity,
created_by: currentUser.id
});
// Django validation runs automatically
// Permission classes control access (field-level + object-level)
// Other users see the change instantly
// Queries are automatically optimized
};
// Your sophisticated permission system just works
What users expect: Google Docs-style collaboration where changes from other users appear instantly and your actions feel immediate.
What Django developers get: Enterprise-grade real-time features without writing a single line of WebSocket code.
<!-- Multiple users editing the same Django model -->
<template>
<div v-for="task in tasks" :key="task.id" class="task-item">
<input
v-model="task.title"
@input="task.save()"
:class="{ 'being-edited': task.isBeingEditedByOthers }"
/>
<button @click="task.completed = !task.completed; task.save()">
{{ task.completed ? '✓' : '○' }}
</button>
<span v-if="task.lastEditedBy && task.lastEditedBy.id !== currentUser.id">
Last edited by {{ task.lastEditedBy.username }}
</span>
</div>
</template>
<script setup>
import { useQueryset } from '@statezero/core/vue';
import { Task } from './models';
// This component automatically:
// - Shows changes from other users in real-time
// - Handles optimistic updates for snappy UX
// - Resolves conflicts when users edit simultaneously
// - Respects your Django permissions
const tasks = useQueryset(() => Task.objects.filter({
project: currentProject.id
}));
</script>
No WebSocket management. No conflict resolution code. No manual synchronization. Real-time collaboration just works.
StateZero automatically optimizes your database queries behind the scenes. When you write simple JavaScript queries, StateZero intelligently applies select_related()
and prefetch_related()
to minimize database hits and maximize performance.
// You write this simple code
const orders = useQueryset(() => Order.objects
.filter({ status: 'pending' }).fetch({fields: ['customer', 'customer__profile']})
);
// StateZero automatically optimizes it to:
// Order.objects.filter(status='pending')
// .select_related('customer', 'customer__profile')
// .prefetch_related('items')
// .only('id', 'status', 'customer__name', 'customer__profile__email')
// Zero manual optimization needed!
Building decoupled SPAs with AI tools? Bolt.dev, v0, and Cursor create beautiful Vue.js/React frontends, but connecting them to Django usually means weeks of API development.
StateZero eliminates all of that. Your AI-generated SPA components work immediately with your Django models.
StateZero provides an expressive permission system that looks like DRF permissions but auto-applies to every query:
# Write permission classes like you know from DRF
class TaskPermission(AbstractPermission):
def filter_queryset(self, request, queryset):
# Users only see their own tasks
return queryset.filter(created_by=request.user)
def allowed_actions(self, request, model):
# Control which operations users can perform
return {ActionType.READ, ActionType.CREATE, ActionType.UPDATE}
def visible_fields(self, request, model):
# Control which fields users can see
if request.user.is_staff:
return "__all__"
return {"id", "title", "completed", "created_by"}
def editable_fields(self, request, model):
# Control which fields users can modify
return {"title", "completed"}
def allowed_object_actions(self, request, obj, model):
# Object-level permissions
if obj.created_by == request.user:
return {ActionType.READ, ActionType.UPDATE, ActionType.DELETE}
return {ActionType.READ}
# Register with your model
registry.register(Task, ModelConfig(
model=Task,
permissions=[TaskPermission]
))
// Permissions automatically apply to all frontend queries
const tasks = useQueryset(() => Task.objects.all());
// ✅ Automatically filtered to user's tasks
// ✅ Only visible fields included
// ✅ Actions validated before execution
Need custom backend logic in your SPA? StateZero's actions system lets you write Python functions and auto-generates type-safe JavaScript functions:
# Write backend functions in Python (in your Django app's actions.py)
from statezero.core.actions import action
from rest_framework import serializers
class SendNotificationSerializer(serializers.Serializer):
user_id = serializers.IntegerField()
message = serializers.CharField(max_length=500)
class NotificationResponseSerializer(serializers.Serializer):
success = serializers.BooleanField()
notification_id = serializers.CharField()
@action(
serializer=SendNotificationSerializer,
response_serializer=NotificationResponseSerializer,
permissions=[IsAuthenticatedPermission]
)
def send_notification(user_id, message, request):
# Your custom business logic here
notification = Notification.objects.create(
recipient_id=user_id,
message=message,
sender=request.user
)
# Send via email/SMS/push notification
notification.send()
return {
'success': True,
'notification_id': str(notification.id)
}
// Auto-generated type-safe JavaScript function
import { sendNotification } from './actions/general/send-notification';
// Call your backend function directly from the SPA
const handleNotify = async () => {
const result = await sendNotification({
user_id: selectedUser.id,
message: "Your order is ready!"
});
if (result.success) {
console.log(`Notification sent: ${result.notification_id}`);
}
};
// ✅ Full type safety with auto-generated TypeScript types
// ✅ Input validation with Zod schemas
// ✅ Permission checking before execution
// ✅ Error handling with StateZero exceptions
// AI tool generates this React SPA component
const ProjectDashboard = () => {
const projects = useQueryset(() => Project.objects.filter({
status: 'active',
team_members: currentUser.id
}));
return (
<div className="dashboard">
{projects.map(project => (
<ProjectCard key={project.id} project={project} />
))}
</div>
);
};
// Your decoupled SPA immediately works with Django:
// ✅ Your Django permissions automatically apply
// ✅ Real-time updates from other team members
// ✅ Optimistic updates for instant feedback
// ✅ No API endpoints needed
// ✅ Deploy SPA and Django backend independently
Got a Django app that's been running for years? StateZero gives you a safe path to modern decoupled SPAs:
Build your decoupled SPA with Vue.js or vanilla JavaScript:
StateZero doesn't blur boundaries or create tight coupling. It creates a clean declarative data layer between your Django backend and decoupled SPA:
Deploy them independently. Scale them separately. Your Django architecture stays proper.
// Complete Django ORM API faithfully ported to JavaScript
const users = useQueryset(() => User.objects
.filter({ is_active: true }) // snake_case field names
.exclude({ role: 'guest' })
.orderBy('-date_joined') // camelCase method names
);
// All Django field lookups work
const recentOrders = useQueryset(() => Order.objects.filter({
created_at__gte: lastWeek, // Django-style field lookups
total__gt: 100,
customer__region: 'US'
}));
// Q objects for complex queries (just like Django!)
import { Q } from '@statezero/core';
const complexQuery = useQueryset(() => User.objects.filter({
Q: [
Q.AND([
{ is_active: true },
Q.OR([
{ role: 'admin' },
{ role: 'staff' }
])
])
]
}));
// Aggregations, counts, exists(), first(), last()
const totalRevenue = useQueryset(() => Order.objects
.filter({ status: 'completed' })
.aggregate('total__sum')
);
// Get single instances with full error handling
const user = await User.objects.get({ username: 'john' });
// Throws DoesNotExist or MultipleObjectsReturned just like Django
// getOrCreate, updateOrCreate - the full Django ORM
const [order, created] = await Order.objects.getOrCreate(
{ customer: customerId, product: productId },
{ quantity: 1, status: 'pending' }
);
Try our live demo to experience:
npx statezero sync
)Ready to supercharge your Django app with modern SPAs?
→ Transform your first Django model in 15 minutes
Stop spending weeks building APIs and managing state. Start building the SPA features your users actually want.
Your Django backend is solid. Your models are battle-tested. Now make them work seamlessly in decoupled JavaScript SPAs.
StateZero: Your Django backend, supercharged for modern SPAs.