GraphQL lets clients ask for exactly what they need.
Phoenix LiveView turns server state into live HTML over WebSockets.
Pair them and you get:
- 🎯 Precise backend queries (GraphQL)
- ⚡ Reactive, server‑rendered views (LiveView)
- 🧩 All in Elixir — no Apollo, no Redux, no SPA bloat
1 ▸ Add Absinthe to Your Phoenix App
# mix.exs
defp deps do
[
{:absinthe, "~> 1.7"},
{:absinthe_plug, "~> 1.7"},
{:absinthe_phoenix, "~> 2.0"} # for subscriptions
]
end
Create a schema:
# lib/my_app_web/schema.ex
object :user do
field :id, non_null(:id)
field :name, :string
field :email, :string
field :orders, list_of(:order)
end
Expose it:
# router.ex
forward "/api/graphql", Absinthe.Plug, schema: MyAppWeb.Schema
2 ▸ Query GraphQL inside LiveView
def handle_event("load_user", %{"id" => id}, socket) do
{:ok, %{data: data}} =
Absinthe.run(
~S(
query ($id: ID!) {
user(id: $id) {
name
email
orders { total }
}
}
),
MyAppWeb.Schema,
variables: %{"id" => id}
)
{:noreply, assign(socket, user: data["user"])}
end
- No Apollo client
- No JS fetch
- Pure Elixir, pure LiveView.
3 ▸ Render the Result
<button phx-click="load_user" phx-value-id="42" class="btn">Load User</button>
<%= if @user do %>
<div class="mt-4 p-4 border rounded">
<h2 class="font-bold"><%= @user["name"] %></h2>
<p class="text-sm text-gray-600"><%= @user["email"] %></p>
<h3 class="mt-3 font-semibold">Orders</h3>
<ul class="list-disc ml-5">
<%= for o <- @user["orders"] do %>
<li>$<%= o["total"] %></li>
<% end %>
</ul>
</div>
<% end %>
Tailwind keeps the UI crisp; LiveView rerenders instantly.
4 ▸ Mutations & Instant Feedback
def handle_event("update_email", %{"email" => email}, socket) do
{:ok, %{data: %{"updateEmail" => user}}} =
Absinthe.run("""
mutation ($id: ID!, $email: String!) {
updateEmail(id: $id, email: $email) { id name email }
}
""", MyAppWeb.Schema, variables: %{"id" => socket.assigns.user_id, "email" => email})
{:noreply, assign(socket, user: user)}
end
No client-side state management — LiveView assigns are the state.
5 ▸ Real‑Time Updates with GraphQL Subscriptions
# schema.ex
subscription do
field :order_created, :order do
config fn _, _ ->
{:ok, topic: "*"}
end
end
end
In LiveView (thanks to absinthe_phoenix
):
def mount(_, _, socket) do
{:ok, socket} =
Absinthe.Phoenix.Subscription.subscribe(socket, "order_created")
end
def handle_info(%{subscription_data: %{result: %{data: %{"orderCreated" => order}}}}, socket) do
{:noreply, stream_insert(socket, :orders, order)}
end
Every new order shows up live without polling.
6 ▸ Pagination, Filters, & Arguments
GraphQL handles vars; LiveView handles events:
<select phx-change="change_page" name="page">
<%= for p <- 1..@total_pages do %>
<option value={p}><%= p %></option>
<% end %>
</select>
def handle_event("change_page", %{"page" => p}, socket) do
run_page_query(String.to_integer(p))
end
7 ▸ Why This Stack Rocks
Layer | Role |
---|---|
GraphQL | Declarative data contract, fine‑grained ops |
LiveView | Real‑time HTML/assigns over WebSockets |
Absinthe | Elixir‑native execution & subscriptions |
Tailwind | Consistent, utility‑first styling |
- All Elixir → one codebase, one skillset
- No SPA → faster cold start, SEO‑friendly
- Real‑time over channels without extra plumbing
Dive Deeper
📘 Phoenix LiveView: The Pro’s Guide to Scalable Interfaces and UI Patterns
- Server‑side GraphQL querying strategies
- Subscriptions & Presence patterns
- Real‑time dashboards without JS frameworks
- Production tips for caching, auth, and error handling
Build data‑rich, real‑time interfaces without frontend bloat.