Core Concepts
On this page
How djust works, and when to use it.
What is djust?
djust brings Phoenix LiveView-style reactive server-side rendering to Django. Instead of writing JavaScript to update the UI, you write Python. The server renders HTML; the client patches the DOM.
Key idea: State lives on the server. Events travel up from the browser; HTML patches travel down. The client is a thin WebSocket layer (~70 KB gz of JS).
The LiveView Lifecycle
Every request goes through the same cycle:
1. HTTP GET → mount() → render → initial HTML sent to browser
2. WebSocket opens → mount() → render (skipped if state is restored)
3. User event → handler() → re-render → diff → patch sent to client
4. Client patches DOM → back to step 3
mount()
Called when the view is first rendered over HTTP, and again when the WebSocket connects (unless state is restored from a snapshot). Initialize all state here, and keep it idempotent — don't perform one-time side effects such as DB writes or notifications:
def mount(self, request, **kwargs):
self.items = []
self.query = ""
self._refresh() # build expensive querysets
request is the standard Django HttpRequest. URL kwargs (from path("<int:id>/", ...)) are passed as **kwargs.
get_context_data()
Called before every render — both the first HTTP render and every WebSocket update. Return the template context:
def get_context_data(self, **kwargs):
# Assign private state to public context variables
self.items = self._items
return {"items": self.items, "query": self.query}
Event handlers
Called when the user interacts with dj-click, dj-input, etc. After a handler returns, djust automatically re-renders and sends the diff:
@event_handler()
def do_search(self, value: str = "", **kwargs):
self.query = value
self._refresh()
# No need to call render — djust does it automatically
State: Public vs Private
djust uses a naming convention to control what gets serialized:
| Prefix | Example | Behavior |
|---|---|---|
| No prefix | self.count | Public — included in template context, serialized for JIT |
_ prefix | self._items | Private — internal state, not serialized |
Use private vars for QuerySets and expensive objects. Assign them to public vars in get_context_data():
def _refresh(self):
self._items = Item.objects.filter(active=True) # private QuerySet
def get_context_data(self, **kwargs):
self.items = self._items # public — JIT-evaluated in Rust
return super().get_context_data(**kwargs)
The Template Engine
djust uses a Rust-powered template engine that is compatible with Django's template syntax — all 57 built-in filters work. Templates render 16-37x faster than Django's Python renderer for typical workloads.
Templates use dj-* attributes for event binding:
| Attribute | Fires when | Handler receives |
|---|---|---|
dj-click="handler" | Button/element clicked | **kwargs |
dj-input="handler" | Input value changes (input event; text fields debounced 300 ms) | value= current value |
dj-change="handler" | Input/select loses focus | value= current value |
dj-submit="handler" | Form submitted | All form fields as kwargs |
VDOM Diffing
On every update, djust:
- Renders the full template in Rust (fast)
- Diffs the new DOM against the previous DOM in Rust (very fast)
- Sends only the changed patches to the client (~bytes, not full HTML)
This means complex re-renders that change one row in a 1000-row table only transmit one row's worth of HTML.
When to Use LiveView vs Standard Django Views
Use LiveView for:
- Forms with real-time validation
- Search/filter interfaces that update as you type — see search-as-you-type and typeahead with @server_function
- Live dashboards, counters, feeds
- Multi-step wizards — see build a wizard
- Collaborative features (presence, cursors) — see real-time comments
- Any UI where you'd otherwise write custom fetch/AJAX code
Use standard Django views for:
- Simple read-only pages (no interactivity)
- REST API endpoints
- File download/redirect responses
- Admin-only interfaces that don't need real-time updates
Both work together: djust views live alongside standard Django views in the same project. Use as_view() for both.
Security Model
djust enforces strict security by default:
- Whitelisted handlers only — only methods decorated with
@event_handler()can be called by clients. Calling any other method name over WebSocket returns an error. - Cross-site WebSocket protection — the handshake's
Originheader is checked againstALLOWED_HOSTS. The HTTP fallback transport uses Django's CSRF token, which the client reads automatically from thecsrftokencookie (or a{% csrf_token %}hidden input if one is present in the page) — no djust template tag is needed to provide it - Auth integration — use
djust.LoginRequiredMixin/djust.PermissionRequiredMixinon the view, anddjust.decorators.permission_requiredon individual event handlers
Next Steps
- Guides — real-time features, navigation, presence, uploads
- State Management — debounce, throttle, loading states, optimistic updates
- API Reference — complete LiveView API