Go to file
James Greenwood ce67d84865
All checks were successful
build / build (push) Successful in 27s
drop debounce to 100ms
2025-11-17 14:37:55 +00:00
.gitea/workflows fix 2025-11-17 09:12:35 +00:00
assets Initial commit: Phoenix LiveView demo for interactive data tables with filtering, sorting, pagination, URL state, and progressive enhancement 2025-11-16 10:24:06 +00:00
config fix 2025-11-17 09:12:35 +00:00
lib drop debounce to 100ms 2025-11-17 14:37:55 +00:00
priv Initial commit: Phoenix LiveView demo for interactive data tables with filtering, sorting, pagination, URL state, and progressive enhancement 2025-11-16 10:24:06 +00:00
test drop to 15 records per page 2025-11-17 14:37:33 +00:00
.formatter.exs Initial commit: Phoenix LiveView demo for interactive data tables with filtering, sorting, pagination, URL state, and progressive enhancement 2025-11-16 10:24:06 +00:00
.gitignore Initial commit: Phoenix LiveView demo for interactive data tables with filtering, sorting, pagination, URL state, and progressive enhancement 2025-11-16 10:24:06 +00:00
AGENTS.md Initial commit: Phoenix LiveView demo for interactive data tables with filtering, sorting, pagination, URL state, and progressive enhancement 2025-11-16 10:24:06 +00:00
Dockerfile Dockerfile for building 2025-11-17 08:38:37 +00:00
mix.exs fix 2025-11-17 09:18:49 +00:00
mix.lock Initial commit: Phoenix LiveView demo for interactive data tables with filtering, sorting, pagination, URL state, and progressive enhancement 2025-11-16 10:24:06 +00:00
README.md drop to 15 records per page 2025-11-17 14:37:33 +00:00

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

# 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

# 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 in your browser.

Seeding Data

The database is automatically seeded with 1,000,000 sample records on first run. To reset:

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

# 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

License

This demonstration project is provided as-is for educational purposes.