216 lines
6.9 KiB
Markdown
216 lines
6.9 KiB
Markdown
|
|
# Phoenix LiveView Action Requests Demo
|
||
|
|
|
||
|
|
A demonstration of how Phoenix LiveView can replace complex multi-framework stacks (Django + React, Rails + React, etc.) with a single, elegant solution for building interactive, real-time data tables.
|
||
|
|
|
||
|
|
## What This Demo Shows
|
||
|
|
|
||
|
|
This project implements a fully-featured data table with **filtering, sorting, and pagination** in a fraction of the code required by traditional stack combinations. What typically requires:
|
||
|
|
|
||
|
|
- Backend API (Django/Rails)
|
||
|
|
- Frontend framework (React/Vue)
|
||
|
|
- State management (Redux/Zustand)
|
||
|
|
- API client libraries (Axios/Fetch)
|
||
|
|
- TypeScript type definitions
|
||
|
|
- Multiple build tools and configs
|
||
|
|
|
||
|
|
...is accomplished here in **one Phoenix LiveView module** with progressive enhancement, real-time updates, and URL-based state management built in.
|
||
|
|
|
||
|
|
## Why This Matters
|
||
|
|
|
||
|
|
### Compared to Django + React (or similar stacks):
|
||
|
|
|
||
|
|
| Aspect | Django + React | Phoenix LiveView |
|
||
|
|
|--------|---------------|------------------|
|
||
|
|
| **Codebase** | Split across backend/frontend | Single unified codebase |
|
||
|
|
| **Languages** | Python + JavaScript/TypeScript | Elixir |
|
||
|
|
| **Files needed** | Views, serializers, API endpoints, React components, state management, type definitions | 1 LiveView module + 1 context |
|
||
|
|
| **State sync** | Manual API calls, polling, or WebSocket setup | Automatic via LiveView |
|
||
|
|
| **Real-time** | Requires channels/WebSockets setup | Built-in |
|
||
|
|
| **Progressive enhancement** | Requires separate server-side rendering setup | Native |
|
||
|
|
| **URL state** | Manual query param handling on both ends | Declarative with `handle_params/3` |
|
||
|
|
| **Deployment** | Two separate services | Single Elixir application |
|
||
|
|
| **Build complexity** | Webpack/Vite + Python packaging | Mix (built-in) |
|
||
|
|
|
||
|
|
### Performance Benefits
|
||
|
|
|
||
|
|
- **Reduced latency**: No API round-trips, server renders diffs only
|
||
|
|
- **Minimal bandwidth**: LiveView sends HTML diffs, not full JSON payloads
|
||
|
|
- **Efficient updates**: Only changed DOM elements are updated
|
||
|
|
- **No client-side state bugs**: Source of truth lives on the server
|
||
|
|
|
||
|
|
### Maintainability Benefits
|
||
|
|
|
||
|
|
- **Single mental model**: No context switching between languages/frameworks
|
||
|
|
- **Fewer dependencies**: No npm packages, no frontend framework updates
|
||
|
|
- **Type safety**: Elixir's pattern matching catches errors at compile time
|
||
|
|
- **Simpler testing**: Test the LiveView, not API + frontend + integration
|
||
|
|
|
||
|
|
## Features Implemented
|
||
|
|
|
||
|
|
### Core Functionality
|
||
|
|
- ✅ **Search/Filter**: Fuzzy text search on patient names
|
||
|
|
- ✅ **Status Filter**: Dropdown (All, Resolved, Unresolved)
|
||
|
|
- ✅ **Assignment Filter**: Dropdown (All, Mine, Assigned, Unassigned)
|
||
|
|
- ✅ **Sorting**: All columns with ascending/descending indicators
|
||
|
|
- ✅ **Pagination**: 15 records per page with full navigation
|
||
|
|
- ✅ **URL State**: All filters/sorting/pagination in URL (bookmarkable/shareable)
|
||
|
|
- ✅ **Progressive Enhancement**: Works with and without JavaScript
|
||
|
|
|
||
|
|
### Technical Highlights
|
||
|
|
- **Single LiveView module** handles all user interactions
|
||
|
|
- **Flop library** for efficient query building
|
||
|
|
- **SQLite database** with proper indexes
|
||
|
|
- **Real-time updates** without polling or manual WebSocket setup
|
||
|
|
- **Responsive design** with Tailwind CSS + DaisyUI
|
||
|
|
- **Theme support** with light/dark modes
|
||
|
|
|
||
|
|
## Database Schema
|
||
|
|
|
||
|
|
```elixir
|
||
|
|
# action_requests table
|
||
|
|
id :binary_id, primary_key: true
|
||
|
|
patient_name :string
|
||
|
|
status :string
|
||
|
|
assigned_user_id :integer
|
||
|
|
delivery_scheduled_at :naive_datetime
|
||
|
|
inserted_at :utc_datetime
|
||
|
|
updated_at :utc_datetime
|
||
|
|
|
||
|
|
# Indexes
|
||
|
|
- patient_name
|
||
|
|
- status
|
||
|
|
- assigned_user_id
|
||
|
|
- inserted_at
|
||
|
|
```
|
||
|
|
|
||
|
|
## Tech Stack
|
||
|
|
|
||
|
|
- **Phoenix 1.8+** - Web framework
|
||
|
|
- **LiveView** - Real-time server-rendered interactions
|
||
|
|
- **Flop** - Filtering, ordering, and pagination
|
||
|
|
- **Ecto** - Database wrapper and query builder
|
||
|
|
- **SQLite** - Embedded database (zero-config)
|
||
|
|
- **Tailwind CSS + DaisyUI** - Styling
|
||
|
|
- **Elixir** - Language
|
||
|
|
|
||
|
|
## Getting Started
|
||
|
|
|
||
|
|
### Prerequisites
|
||
|
|
- Elixir 1.15+
|
||
|
|
- Erlang/OTP 26+
|
||
|
|
|
||
|
|
### Setup
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Install dependencies and setup database
|
||
|
|
mix setup
|
||
|
|
|
||
|
|
# Start the Phoenix server
|
||
|
|
mix phx.server
|
||
|
|
|
||
|
|
# Or start inside IEx
|
||
|
|
iex -S mix phx.server
|
||
|
|
```
|
||
|
|
|
||
|
|
Visit [`localhost:4000`](http://localhost:4000) in your browser.
|
||
|
|
|
||
|
|
### Seeding Data
|
||
|
|
|
||
|
|
The database is automatically seeded with 1,000,000 sample records on first run. To reset:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
mix ecto.reset
|
||
|
|
```
|
||
|
|
|
||
|
|
## Code Structure
|
||
|
|
|
||
|
|
```
|
||
|
|
lib/
|
||
|
|
├── action_requests_demo/
|
||
|
|
│ ├── action_requests/
|
||
|
|
│ │ └── action_request.ex # Schema definition
|
||
|
|
│ └── action_requests.ex # Context with query logic
|
||
|
|
└── action_requests_demo_web/
|
||
|
|
├── live/
|
||
|
|
│ └── action_requests_live.ex # Main LiveView (filtering, sorting, pagination)
|
||
|
|
└── router.ex # Single route
|
||
|
|
```
|
||
|
|
|
||
|
|
## Progressive Enhancement
|
||
|
|
|
||
|
|
This app works perfectly **without JavaScript**:
|
||
|
|
|
||
|
|
1. Forms use `method="get"` and `action="/"`
|
||
|
|
2. Filter changes trigger full page reloads via form submission
|
||
|
|
3. All state persists in URL query parameters
|
||
|
|
|
||
|
|
When JavaScript is enabled:
|
||
|
|
|
||
|
|
1. Forms trigger LiveView events instead of page reloads
|
||
|
|
2. LiveView patches the URL and re-renders
|
||
|
|
3. Updates happen in real-time with minimal data transfer
|
||
|
|
|
||
|
|
**The same code handles both modes** via `handle_params/3` - no duplicate logic required.
|
||
|
|
|
||
|
|
## Testing
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Run all tests
|
||
|
|
mix test
|
||
|
|
|
||
|
|
# Run with coverage
|
||
|
|
mix test --cover
|
||
|
|
```
|
||
|
|
|
||
|
|
31 comprehensive tests covering all filtering, sorting, and pagination scenarios.
|
||
|
|
|
||
|
|
## Key Implementation Details
|
||
|
|
|
||
|
|
### Special Filter Logic
|
||
|
|
- **"Mine"**: `assigned_user_id = current_user_id` (mocked as 1)
|
||
|
|
- **"Assigned"**: `assigned_user_id IS NOT NULL`
|
||
|
|
- **"Unassigned"**: `assigned_user_id IS NULL`
|
||
|
|
|
||
|
|
### URL Format
|
||
|
|
All state is in the URL for bookmarking/sharing:
|
||
|
|
```
|
||
|
|
/?status=resolved&assignment=mine&patient_name=john&page=2&order_by=patient_name&order_directions=asc
|
||
|
|
```
|
||
|
|
|
||
|
|
### Default Behavior
|
||
|
|
- Sort: `inserted_at DESC` (newest first)
|
||
|
|
- Per page: 15 records
|
||
|
|
- Current user: ID 1 (for demo purposes)
|
||
|
|
|
||
|
|
## Why Elixir/Phoenix?
|
||
|
|
|
||
|
|
This demo showcases Elixir's strengths:
|
||
|
|
|
||
|
|
1. **Concurrency**: BEAM VM handles thousands of concurrent connections effortlessly
|
||
|
|
2. **Fault tolerance**: Process isolation means one user's error won't crash others
|
||
|
|
3. **Low latency**: LiveView's stateful connections eliminate API overhead
|
||
|
|
4. **Developer productivity**: Write less code, ship faster, maintain easier
|
||
|
|
5. **Scalability**: Vertical and horizontal scaling built into the platform
|
||
|
|
|
||
|
|
## Production Considerations
|
||
|
|
|
||
|
|
This is a **demo application**. For production use, consider:
|
||
|
|
|
||
|
|
- Authentication/authorization (currently mocked)
|
||
|
|
- Rate limiting
|
||
|
|
- Database connection pooling configuration
|
||
|
|
- CDN for static assets
|
||
|
|
- Load balancing for multiple nodes
|
||
|
|
- Monitoring and observability
|
||
|
|
|
||
|
|
## Learn More
|
||
|
|
|
||
|
|
- **Phoenix Framework**: https://www.phoenixframework.org/
|
||
|
|
- **Phoenix LiveView**: https://hexdocs.pm/phoenix_live_view
|
||
|
|
- **Flop**: https://hexdocs.pm/flop
|
||
|
|
- **Elixir**: https://elixir-lang.org/
|
||
|
|
|
||
|
|
## License
|
||
|
|
|
||
|
|
This demonstration project is provided as-is for educational purposes.
|