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.
              
    
Top comments (0)