Building a Simple Counter App with Elixir and Phoenix LiveView
This guide will walk you through creating a simple counter application that demonstrates the basics of Phoenix LiveView. Step 1: Setting up a new Phoenix project First, make sure you have Elixir and Phoenix installed. Then, create a new Phoenix project: # Install Phoenix (if you haven't already) mix archive.install hex phx_new # Create a new Phoenix project with LiveView mix phx.new counter_app --live # Move into the project directory cd counter_app # Set up the database (we won't use it much, but it's required) mix ecto.setup Step 2: Create a simple counter LiveView Create a new file at lib/counter_app_web/live/counter_live.ex: defmodule CounterAppWeb.CounterLive do # Use the LiveView functionality from the web module use CounterAppWeb, :live_view # mount/3 is called when the LiveView is first rendered # It sets up the initial state of the LiveView def mount(_params, _session, socket) do # Initialize the socket with a count of 0 # assign/2 is used to set values in the socket's state {:ok, assign(socket, count: 0)} end # handle_event/3 processes events sent from the client # This function handles the "increment" event def handle_event("increment", _params, socket) do # Update the count by adding 1 # update/3 modifies an existing assign value {:noreply, update(socket, :count, &(&1 + 1))} end # This function handles the "decrement" event def handle_event("decrement", _params, socket) do # Update the count by subtracting 1 {:noreply, update(socket, :count, &(&1 - 1))} end # This function handles the "reset" event def handle_event("reset", _params, socket) do # Reset the count to 0 {:noreply, assign(socket, count: 0)} end # render/1 defines what HTML to display # It receives the socket's assigns as its argument def render(assigns) do ~H""" Simple Counter App Count: - Reset + """ end end Step 3: Add the route for our LiveView Edit lib/counter_app_web/router.ex to add a route for our counter: defmodule CounterAppWeb.Router do use CounterAppWeb, :router # Pipeline definitions (already included when you create a new project) pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_live_flash plug :put_root_layout, html: {CounterAppWeb.Layouts, :root} plug :protect_from_forgery plug :put_secure_browser_headers end pipeline :api do plug :accepts, ["json"] end # Browser routes scope "/", CounterAppWeb do pipe_through :browser # Add this line to direct the root path to our CounterLive module live "/", CounterLive end # Other route groups would go here end Step 4: Add some basic CSS Edit assets/css/app.css to add some styling for our counter: /* Add this to the bottom of the file */ /* Container for the counter application */ .counter-container { max-width: 500px; margin: 0 auto; padding: 20px; text-align: center; font-family: system-ui, sans-serif; } /* Style for the buttons */ .buttons { display: flex; justify-content: center; gap: 10px; margin-top: 20px; } .button { padding: 10px 20px; font-size: 18px; border: none; border-radius: 4px; background-color: #4a6da7; color: white; cursor: pointer; transition: background-color 0.3s; } .button:hover { background-color: #2c4a7c; } Step 5: Run the application Start the Phoenix server: mix phx.server Visit http://localhost:4000 in your browser, and you should see your counter application running! Understanding the LiveView Flow When a user visits the page: The mount/3 function is called, initializing the state with count: 0 The render/1 function generates the initial HTML When a user clicks a button: The phx-click attribute tells LiveView which event to trigger LiveView sends this event to the server through the WebSocket connection The corresponding handle_event/3 function is called on the server The state is updated accordingly The render/1 function is called again to update the HTML LiveView sends only the differences (patches) to the browser to update the DOM This is all handled automatically by LiveView, giving you real-time updates without writing any JavaScript! Next Steps To expand this application, you could: Add more complex state management Create LiveComponents for reusable UI elements Implement form validation Add persistence with Ecto and a database Congratulations! You've created your first Phoenix LiveView application.

This guide will walk you through creating a simple counter application that demonstrates the basics of Phoenix LiveView.
Step 1: Setting up a new Phoenix project
First, make sure you have Elixir and Phoenix installed. Then, create a new Phoenix project:
# Install Phoenix (if you haven't already)
mix archive.install hex phx_new
# Create a new Phoenix project with LiveView
mix phx.new counter_app --live
# Move into the project directory
cd counter_app
# Set up the database (we won't use it much, but it's required)
mix ecto.setup
Step 2: Create a simple counter LiveView
Create a new file at lib/counter_app_web/live/counter_live.ex
:
defmodule CounterAppWeb.CounterLive do
# Use the LiveView functionality from the web module
use CounterAppWeb, :live_view
# mount/3 is called when the LiveView is first rendered
# It sets up the initial state of the LiveView
def mount(_params, _session, socket) do
# Initialize the socket with a count of 0
# assign/2 is used to set values in the socket's state
{:ok, assign(socket, count: 0)}
end
# handle_event/3 processes events sent from the client
# This function handles the "increment" event
def handle_event("increment", _params, socket) do
# Update the count by adding 1
# update/3 modifies an existing assign value
{:noreply, update(socket, :count, &(&1 + 1))}
end
# This function handles the "decrement" event
def handle_event("decrement", _params, socket) do
# Update the count by subtracting 1
{:noreply, update(socket, :count, &(&1 - 1))}
end
# This function handles the "reset" event
def handle_event("reset", _params, socket) do
# Reset the count to 0
{:noreply, assign(socket, count: 0)}
end
# render/1 defines what HTML to display
# It receives the socket's assigns as its argument
def render(assigns) do
~H"""
counter-container">
Simple Counter App
Count: <%= @count %>
buttons">
" class="button">-
" class="button">Reset
" class="button">+
"""
end
end
Step 3: Add the route for our LiveView
Edit lib/counter_app_web/router.ex
to add a route for our counter:
defmodule CounterAppWeb.Router do
use CounterAppWeb, :router
# Pipeline definitions (already included when you create a new project)
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {CounterAppWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
# Browser routes
scope "/", CounterAppWeb do
pipe_through :browser
# Add this line to direct the root path to our CounterLive module
live "/", CounterLive
end
# Other route groups would go here
end
Step 4: Add some basic CSS
Edit assets/css/app.css
to add some styling for our counter:
/* Add this to the bottom of the file */
/* Container for the counter application */
.counter-container {
max-width: 500px;
margin: 0 auto;
padding: 20px;
text-align: center;
font-family: system-ui, sans-serif;
}
/* Style for the buttons */
.buttons {
display: flex;
justify-content: center;
gap: 10px;
margin-top: 20px;
}
.button {
padding: 10px 20px;
font-size: 18px;
border: none;
border-radius: 4px;
background-color: #4a6da7;
color: white;
cursor: pointer;
transition: background-color 0.3s;
}
.button:hover {
background-color: #2c4a7c;
}
Step 5: Run the application
Start the Phoenix server:
mix phx.server
Visit http://localhost:4000
in your browser, and you should see your counter application running!
Understanding the LiveView Flow
-
When a user visits the page:
- The
mount/3
function is called, initializing the state withcount: 0
- The
render/1
function generates the initial HTML
- The
-
When a user clicks a button:
- The
phx-click
attribute tells LiveView which event to trigger - LiveView sends this event to the server through the WebSocket connection
- The corresponding
handle_event/3
function is called on the server - The state is updated accordingly
- The
render/1
function is called again to update the HTML - LiveView sends only the differences (patches) to the browser to update the DOM
- The
This is all handled automatically by LiveView, giving you real-time updates without writing any JavaScript!
Next Steps
To expand this application, you could:
- Add more complex state management
- Create LiveComponents for reusable UI elements
- Implement form validation
- Add persistence with Ecto and a database
Congratulations! You've created your first Phoenix LiveView application.