Elixir Interview Questions with Answers
Most Asked Elixir Interview Questions for Software Engineer Roles
Introduction
Elixir is a dynamic, functional programming language built on the Erlang VM (BEAM). Designed for building scalable, maintainable, and fault-tolerant applications, Elixir leverages the battle-tested OTP framework to create resilient systems with lightweight concurrency and message passing. Its Phoenix web framework delivers real-time capabilities through Channels and LiveView, making it a top choice for modern web and distributed systems. This comprehensive guide collects the most frequently asked Elixir interview questions, ranging from foundational concepts like pattern matching, recursion, and the pipe operator to advanced topics such as OTP behaviours, Ecto, Phoenix LiveView, distributed Elixir, and performance optimization. Whether you are starting your Elixir journey or preparing for a senior role, these questions will help you solidify your knowledge and ace your next technical interview.
Why Elixir?
- Strong static typing – catches errors at compile time, reducing runtime bugs
- Excellent tooling and IDE support – autocompletion, navigation, and refactoring
- Superset of JavaScript – works seamlessly with all existing JavaScript libraries
- Used by major frameworks like React, Angular, and Vue – essential for large-scale apps
- Enables scalable and maintainable enterprise-grade applications
- Growing community, continuous improvements, and high demand in the job market
Most Asked Elixir Interview Questions
Elixir is a functional, concurrent programming language built on the Erlang VM (BEAM). It is designed for building scalable and maintainable applications with high fault tolerance.
- Functional Programming: Immutable data and pure functions
- Concurrency: Lightweight processes and message passing
- Fault Tolerance: Supervisor trees and "let it crash" philosophy
- Hot Code Upgrades: Update code without stopping the system
- Pattern Matching: Powerful matching and destructuring
# Hello World in Elixir
IO.puts("Hello, World!")Elixir provides a rich set of immutable data types including integers, floats, booleans, strings, atoms, lists, tuples, and maps.
- Integers: Arbitrary precision integers
- Floats: IEEE 754 double-precision
- Booleans:
trueandfalse - Strings: UTF-8 encoded binaries
- Atoms: Constants whose name is their value
- Lists: Linked lists with head/tail
- Tuples: Fixed-size collections
- Maps: Key-value pairs
# Data Types in Elixir
age = 25
salary = 50000.50
pi = 3.14159265358979
grade = ?A
is_active = true
name = "Alice"
price = 99.99
IO.puts("Age: #{age}")
IO.puts("Salary: #{salary}")
IO.puts("Pi: #{pi}")
IO.puts("Grade: #{grade}")
IO.puts("Active: #{is_active}")
IO.puts("Name: #{name}")
IO.puts("Price: #{price}")Elixir uses immutable variables that cannot be reassigned. Constants can be defined using module attributes with the @ syntax. Variables are bound through pattern matching and cannot be mutated.
- Variables: Immutable, can be rebound but not mutated
- Module Attributes:
@constantfor compile-time constants - Pattern Matching: Bind variables through matching
- Pinning Operator:
^to use existing value in pattern matching - Rebinding: Variables can be rebound to new values
# Variables and Constants in Elixir
x = 10
pi = 3.14159
val = 3.14
str = "Hello"
IO.puts("x = #{x}")
IO.puts("pi = #{pi}")
IO.puts("val = #{val}")
IO.puts("str = #{str}")Pattern Matching allows matching values against patterns and binding variables. It's a fundamental feature used throughout Elixir for destructuring data structures and controlling program flow.
- Match Operator:
=for pattern matching - List Matching:
[head | tail]pattern - Tuple Matching:
{a, b}pattern - Map Matching:
%{key: value}pattern - Pinning:
^to match existing value
# Pattern Matching in Elixir
{x, y} = {3, 4}
IO.puts("x = #{x}, y = #{y}")
[head | tail] = [1, 2, 3, 4]
IO.puts("Head: #{head}, Tail: #{inspect(tail)}")
%{name: name, age: age} = %{name: "Alice", age: 25}
IO.puts("Name: #{name}, Age: #{age}")
# Pinning operator
x = 10
^x = 10 # Matches
# ^x = 20 # Will not match, raises error
# Pattern matching in function clauses
defmodule Math do
def add({a, b}), do: a + b
def add([a, b]), do: a + b
def add(%{a: a, b: b}), do: a + b
end
IO.puts(Math.add({3, 4}))
IO.puts(Math.add([3, 4]))
IO.puts(Math.add(%{a: 3, b: 4}))Modules are used to group related functions. Functions are defined using def and can have multiple clauses for pattern matching. Private functions are defined with defp.
defmodule— Defines a moduledef— Defines a public functiondefp— Defines a private function- Multiple Clauses: Different implementations for different patterns
- Guard Clauses:
whenfor additional conditions
# Modules and Functions in Elixir
defmodule Math do
# Public function
def add(a, b) do
a + b
end
# Private function
defp subtract(a, b) do
a - b
end
# One-liner function
def multiply(a, b), do: a * b
# Multiple clauses
def divide(a, 0), do: {:error, "Division by zero"}
def divide(a, b), do: {:ok, a / b}
# Guard clauses
def factorial(0), do: 1
def factorial(n) when n > 0 do
n * factorial(n - 1)
end
# Default arguments
def greet(name, greeting \ "Hello") do
"#{greeting}, #{name}!"
end
end
IO.puts(Math.add(10, 20))
IO.puts(Math.multiply(5, 4))
IO.puts(Math.factorial(5))
IO.puts(Math.greet("Alice"))
IO.puts(Math.greet("Bob", "Hi"))Recursion is a technique where a function calls itself. Elixir prefers recursion over loops due to immutability and the absence of traditional loops. Tail call optimization ensures efficiency.
- Base Case: Stopping condition
- Recursive Case: Self-call with smaller input
- Tail Call Optimization: Efficient recursion without stack overflow
- Recursive Functions: Factorial, Fibonacci, list processing
- Accumulator Pattern: Efficient recursion with accumulators
# Recursion in Elixir
defmodule Recursion do
# Factorial
def factorial(0), do: 1
def factorial(n) when n > 0 do
n * factorial(n - 1)
end
# Fibonacci
def fibonacci(0), do: 0
def fibonacci(1), do: 1
def fibonacci(n) when n > 1 do
fibonacci(n - 1) + fibonacci(n - 2)
end
# List processing
def sum([]), do: 0
def sum([head | tail]) do
head + sum(tail)
end
def length([]), do: 0
def length([_ | tail]) do
1 + length(tail)
end
# Tail recursion
def reverse(list), do: reverse(list, [])
defp reverse([], acc), do: acc
defp reverse([head | tail], acc) do
reverse(tail, [head | acc])
end
# Map function with recursion
def map([], _func), do: []
def map([head | tail], func) do
[func.(head) | map(tail, func)]
end
# Filter function with recursion
def filter([], _predicate), do: []
def filter([head | tail], predicate) do
if predicate.(head) do
[head | filter(tail, predicate)]
else
filter(tail, predicate)
end
end
end
IO.puts("Factorial 5: #{Recursion.factorial(5)}")
IO.puts("Fibonacci 8: #{Recursion.fibonacci(8)}")
IO.puts("Sum [1,2,3,4,5]: #{Recursion.sum([1,2,3,4,5])}")
IO.puts("Length [1,2,3,4,5]: #{Recursion.length([1,2,3,4,5])}")
IO.puts("Reverse [1,2,3]: #{inspect(Recursion.reverse([1,2,3]))}")
IO.puts("Map [1,2,3] *2: #{inspect(Recursion.map([1,2,3], fn x -> x * 2 end))}")
IO.puts("Filter [1,2,3,4,5] even: #{inspect(Recursion.filter([1,2,3,4,5], fn x -> rem(x, 2) == 0 end))}")The Enum module provides a rich set of functions to work with collections like lists, maps, and streams. It offers functional programming operations for data transformation.
Enum.map— Transform elementsEnum.filter— Filter elementsEnum.reduce— Aggregate valuesEnum.sort— Sort elementsEnum.each— Iterate for side effects
# Enum Module in Elixir
list = [5, 1, 8, 3, 9, 2, 7]
# Map - transform elements
doubled = Enum.map(list, fn x -> x * 2 end)
IO.puts("Doubled: #{inspect(doubled)}")
# Filter - select elements
evens = Enum.filter(list, fn x -> rem(x, 2) == 0 end)
IO.puts("Evens: #{inspect(evens)}")
# Reduce - accumulate values
sum = Enum.reduce(list, 0, fn x, acc -> x + acc end)
IO.puts("Sum: #{sum}")
# Sort - order elements
sorted = Enum.sort(list)
IO.puts("Sorted: #{inspect(sorted)}")
# Map with capture syntax
squares = Enum.map(list, &(&1 * &1))
IO.puts("Squares: #{inspect(squares)}")
# Filter with capture
greater_than_5 = Enum.filter(list, &(&1 > 5))
IO.puts("> 5: #{inspect(greater_than_5)}")
# Each - side effects
Enum.each(list, fn x -> IO.puts("Item: #{x}") end)
# Find - find first matching
first_even = Enum.find(list, fn x -> rem(x, 2) == 0 end)
IO.puts("First even: #{first_even}")
# Any? - check if any matches
has_even = Enum.any?(list, fn x -> rem(x, 2) == 0 end)
IO.puts("Has even: #{has_even}")
# All? - check if all match
all_even = Enum.all?(list, fn x -> rem(x, 2) == 0 end)
IO.puts("All even: #{all_even}")
# Join - convert to string
joined = Enum.join(list, ", ")
IO.puts("Joined: #{joined}")
# With_index - add index
with_index = Enum.with_index(list)
IO.puts("With index: #{inspect(with_index)}")The Pipe Operator (|>) passes the result of one function as the first argument to the next function, making code more readable and expressive by creating a data pipeline.
- Function Chaining: Chain multiple operations
- Readability: Read left-to-right flow
- Composition: Easily compose functions
- Pipeline Pattern: Common in Elixir codebases
- Multiple Arguments: Works with functions taking multiple arguments
# Pipe Operator in Elixir
list = [5, 1, 8, 3, 9, 2, 7]
# Without pipe - nested function calls
result = Enum.sum(Enum.filter(Enum.map(list, fn x -> x * 2 end), fn x -> x > 10 end))
IO.puts("Without pipe: #{result}")
# With pipe - readable flow
result = list
|> Enum.map(&(&1 * 2))
|> Enum.filter(&(&1 > 10))
|> Enum.sum()
IO.puts("With pipe: #{result}")
# Chaining multiple operations
list
|> Enum.map(&(&1 * 2))
|> Enum.filter(&(&1 > 10))
|> Enum.sort()
|> IO.inspect(label: "Result")
# Pipe with multiple arguments
defmodule Math do
def double(x), do: x * 2
def add(x, y), do: x + y
def square(x), do: x * x
end
result = 5
|> Math.double()
|> Math.add(10)
|> Math.square()
IO.puts("Result: #{result}")
# Pipe with functions that take multiple arguments
result = 10
|> Kernel.+(5)
|> Kernel.*(2)
IO.puts("Result: #{result}")GenServer is a behaviour module for implementing server processes. OTP (Open Telecom Platform) provides a set of libraries and design principles for building fault-tolerant systems with supervision trees.
- GenServer: Generic server implementation
- Client/Server API:
callandcast - OTP Behaviours: GenServer, Supervisor, Application
- Fault Tolerance: Supervisors and restart strategies
- State Management: Maintain state between calls
# GenServer Example in Elixir
defmodule Counter do
use GenServer
# Client API
def start_link(initial_value) do
GenServer.start_link(__MODULE__, initial_value, name: __MODULE__)
end
def increment do
GenServer.call(__MODULE__, :increment)
end
def decrement do
GenServer.call(__MODULE__, :decrement)
end
def get_count do
GenServer.call(__MODULE__, :get_count)
end
def reset do
GenServer.call(__MODULE__, :reset)
end
# Server Callbacks
def init(initial_value) do
{:ok, initial_value}
end
def handle_call(:increment, _from, state) do
{:reply, state + 1, state + 1}
end
def handle_call(:decrement, _from, state) do
{:reply, state - 1, state - 1}
end
def handle_call(:get_count, _from, state) do
{:reply, state, state}
end
def handle_call(:reset, _from, _state) do
{:reply, 0, 0}
end
# Handle cast (fire and forget)
def handle_cast({:set, new_value}, _state) do
{:noreply, new_value}
end
# Handle info (from external)
def handle_info(:tick, state) do
IO.puts("Tick: #{state}")
{:noreply, state + 1}
end
end
# Usage
{:ok, pid} = Counter.start_link(0)
Counter.increment()
Counter.increment()
Counter.decrement()
IO.puts("Count: #{Counter.get_count()}")
Counter.reset()
IO.puts("After reset: #{Counter.get_count()}")
# Cast
GenServer.cast(Counter, {:set, 100})
IO.puts("After cast: #{Counter.get_count()}")
# Send info
send(Counter, :tick)Supervisors are processes that monitor other processes and restart them if they crash, providing fault tolerance and self-healing capabilities through supervision trees.
- Supervision Tree: Hierarchical process structure
- Restart Strategies:
:one_for_one,:one_for_all,:rest_for_one - Child Specifications: Define how to start children
- Dynamic Supervision:
DynamicSupervisorfor dynamic children - Supervisor Flags:
:permanent,:transient,:temporary
# Supervisor Example in Elixir
defmodule MyApp.Application do
use Application
def start(_type, _args) do
children = [
# Start the counter GenServer
{Counter, 0},
# Start a task
{Task, fn -> IO.puts("Task started") end},
# Start a registry
{Registry, keys: :unique, name: MyRegistry},
# Start dynamic supervisor
{DynamicSupervisor, strategy: :one_for_one, name: MyDynamicSupervisor}
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
# Start the supervision tree
{:ok, pid} = MyApp.Application.start(:normal, [])
IO.puts("Supervisor started")
# Get supervisor children
children = Supervisor.which_children(MyApp.Supervisor)
IO.puts("Children: #{inspect(children)}")Tasks are used for asynchronous computations and parallel processing. They provide a simple way to run code concurrently with built-in timeout support and supervision.
- Async/Await:
Task.asyncandTask.await - Parallel Processing: Run multiple tasks concurrently
- Task Supervision:
Task.Supervisorfor supervised tasks - Timeouts: Handle slow tasks with timeouts
- Task.async_stream: Process streams concurrently
# Task Module in Elixir
# Async task
task = Task.async(fn ->
Process.sleep(1000)
"Task completed"
end)
IO.puts("Task started...")
result = Task.await(task)
IO.puts("Result: #{result}")
# Task with timeout
task = Task.async(fn ->
Process.sleep(5000)
"Slow task"
end)
try do
Task.await(task, 2000)
rescue
e in Task.TimeoutError -> IO.puts("Task timed out!")
end
# Async stream with multiple tasks
tasks = 1..5
|> Enum.map(fn i ->
Task.async(fn ->
Process.sleep(i * 100)
i * i
end)
end)
results = Enum.map(tasks, &Task.await/1)
IO.puts("Results: #{inspect(results)}")
# Task with supervisor
defmodule MyTask do
def start_link do
Task.start_link(fn ->
Process.sleep(1000)
IO.puts("Task completed")
end)
end
end
# Task started
{:ok, _} = MyTask.start_link()
# Task as function
Task.async(fn ->
IO.puts("Running task")
end)
|> Task.await()ETS is an in-memory storage system for fast lookups. It provides efficient key-value storage with various table types and is ideal for caching and temporary data storage.
- Table Types:
:set,:ordered_set,:bag - Operations: Insert, lookup, delete, update
- Performance: Very fast read/write operations
- Use Cases: Caching, lookups, temporary storage
- Match Specifications: Complex query patterns
# ETS (Erlang Term Storage) in Elixir
# Create an ETS table
table = :ets.new(:my_table, [:set, :public, :named_table])
# Insert data
:ets.insert(:my_table, {:user1, "Alice", 25})
:ets.insert(:my_table, {:user2, "Bob", 30})
:ets.insert(:my_table, {:user3, "Carol", 22})
# Lookup data
case :ets.lookup(:my_table, :user1) do
[{:user1, name, age}] ->
IO.puts("User: #{name}, Age: #{age}")
[] ->
IO.puts("User not found")
end
# Update data
:ets.insert(:my_table, {:user1, "Alice Johnson", 26})
# Delete data
:ets.delete(:my_table, :user2)
# Get all data
all_data = :ets.tab2list(:my_table)
IO.puts("All data: #{inspect(all_data)}")
# Match data
matches = :ets.match(:my_table, {:user1, :"$1", :"$2"})
IO.puts("Matches: #{inspect(matches)}")
# Match with conditions
matches = :ets.match(:my_table, {:"$1", :"$2", 25})
IO.puts("Users with age 25: #{inspect(matches)}")
# Delete all
:ets.delete_all_objects(:my_table)
# Clean up
:ets.delete(:my_table)Ecto is a database wrapper and query generator for Elixir applications. It provides a type-safe, composable query API with support for multiple databases and migrations.
- Repo: Database repository module
- Schemas: Define data models
- Changesets: Data validation and transformation
- Queries:
fromsyntax for composable queries - Migrations: Database schema management
# Ecto Query Examples
defmodule MyApp.Repo do
use Ecto.Repo,
otp_app: :my_app,
adapter: Ecto.Adapters.Postgres
end
defmodule MyApp.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :name, :string
field :email, :string
field :age, :integer
timestamps()
end
def changeset(user, attrs) do
user
|> cast(attrs, [:name, :email, :age])
|> validate_required([:name, :email])
|> validate_format(:email, ~r/@/)
|> validate_number(:age, greater_than: 0)
end
end
defmodule MyApp.Post do
use Ecto.Schema
import Ecto.Changeset
schema "posts" do
field :title, :string
field :content, :string
belongs_to :user, MyApp.User
timestamps()
end
end
# Basic Queries
alias MyApp.{Repo, User, Post}
# Insert
user = User.changeset(%User{}, %{name: "Alice", email: "alice@email.com", age: 25})
case Repo.insert(user) do
{:ok, user} -> IO.puts("User created: #{user.name}")
{:error, changeset} -> IO.puts("Error: #{inspect(changeset.errors)}")
end
# Query
query = from u in User, where: u.age > 18, select: u
users = Repo.all(query)
IO.puts("Users over 18: #{inspect(users)}")
# Query with order
query = from u in User, order_by: [desc: u.age], select: u
users = Repo.all(query)
# Update
user = Repo.get(User, 1)
updated = User.changeset(user, %{age: 26})
Repo.update(updated)
# Delete
Repo.delete(user)
# Join query
query = from u in User,
join: p in assoc(u, :posts),
where: p.title == "Hello",
select: {u.name, p.title}
results = Repo.all(query)
# Preload
user = Repo.get(User, 1)
user = Repo.preload(user, :posts)
IO.puts("User posts: #{inspect(user.posts)}")Phoenix is a web framework for Elixir built on OTP and Plug. It provides real-time features with channels and LiveView, with high performance and low latency.
- MVC Pattern: Controllers, views, templates
- Channels: Real-time communication via WebSockets
- LiveView: Interactive, server-rendered UIs
- Performance: High concurrency and low latency
- Ecto Integration: Built-in database support
# Phoenix Framework Example
defmodule MyAppWeb.Router do
use Phoenix.Router
import Phoenix.LiveView.Router
scope "/", MyAppWeb do
pipe_through :browser
get "/", PageController, :index
get "/hello/:name", PageController, :hello
resources "/users", UserController
# LiveView routes
live "/counter", CounterLive
live "/dashboard", DashboardLive
end
scope "/api", MyAppWeb do
pipe_through :api
resources "/users", UserController, only: [:index, :create, :show, :update, :delete]
end
end
defmodule MyAppWeb.PageController do
use MyAppWeb, :controller
def index(conn, _params) do
render(conn, "index.html", message: "Hello, World!")
end
def hello(conn, %{"name" => name}) do
render(conn, "hello.html", name: name)
end
end
defmodule MyAppWeb.UserController do
use MyAppWeb, :controller
def index(conn, _params) do
users = Repo.all(User)
render(conn, "index.json", users: users)
end
def create(conn, %{"user" => user_params}) do
changeset = User.changeset(%User{}, user_params)
case Repo.insert(changeset) do
{:ok, user} -> render(conn, "show.json", user: user)
{:error, changeset} -> conn |> put_status(400) |> render("error.json", changeset: changeset)
end
end
def show(conn, %{"id" => id}) do
user = Repo.get(User, id)
render(conn, "show.json", user: user)
end
def update(conn, %{"id" => id, "user" => user_params}) do
user = Repo.get(User, id)
changeset = User.changeset(user, user_params)
case Repo.update(changeset) do
{:ok, user} -> render(conn, "show.json", user: user)
{:error, changeset} -> conn |> put_status(400) |> render("error.json", changeset: changeset)
end
end
def delete(conn, %{"id" => id}) do
user = Repo.get(User, id)
Repo.delete(user)
send_resp(conn, 204, "")
end
endPhoenix LiveView enables building rich, real-time user interfaces with server-rendered HTML. It syncs state between server and client automatically over WebSockets.
- Server-side Rendering: HTML rendered on server
- Real-time Updates: State changes pushed to client
- WebSocket Connection: Persistent connection for updates
- Event Handling:
phx-clickand other bindings - Performance: Minimal client-side JavaScript
# Phoenix LiveView Example
defmodule MyAppWeb.CounterLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, count: 0, history: [])}
end
def render(assigns) do
~H"""
<div class="counter-container">
<h1>Count: <%= @count %></h1>
<div class="buttons">
<button phx-click="increment" phx-value-amount="1">+1</button>
<button phx-click="increment" phx-value-amount="5">+5</button>
<button phx-click="decrement">-1</button>
<button phx-click="reset">Reset</button>
</div>
<div class="history">
<h3>History</h3>
<ul>
<%= for entry <- @history do %>
<li><%= entry %></li>
<% end %>
</ul>
</div>
</div>
"""
end
def handle_event("increment", %{"amount" => amount}, socket) do
count = socket.assigns.count + String.to_integer(amount)
history = ["Incremented by #{amount}" | socket.assigns.history]
{:noreply, assign(socket, count: count, history: history)}
end
def handle_event("decrement", _params, socket) do
count = socket.assigns.count - 1
history = ["Decremented by 1" | socket.assigns.history]
{:noreply, assign(socket, count: count, history: history)}
end
def handle_event("reset", _params, socket) do
history = ["Reset" | socket.assigns.history]
{:noreply, assign(socket, count: 0, history: history)}
end
end
# PubSub for real-time updates
defmodule MyAppWeb.ChatLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
if connected?(socket) do
MyAppWeb.Endpoint.subscribe("chat:global")
end
{:ok, assign(socket, messages: [])}
end
def render(assigns) do
~H"""
<div>
<h2>Chat</h2>
<div id="messages">
<%= for message <- @messages do %>
<div><%= message %></div>
<% end %>
</div>
<form phx-submit="send_message">
<input type="text" name="message" placeholder="Type a message..." />
<button type="submit">Send</button>
</form>
</div>
"""
end
def handle_event("send_message", %{"message" => message}, socket) do
MyAppWeb.Endpoint.broadcast("chat:global", "new_message", message)
{:noreply, socket}
end
def handle_info(%{event: "new_message", payload: message}, socket) do
{:noreply, update(socket, :messages, fn msgs -> [message | msgs] end)}
end
endPlug is a specification and toolkit for building composable web modules. It is the foundation for Phoenix and other web frameworks, providing a simple HTTP abstraction.
- Plug Functions: Define request/response handlers
- Plug Pipeline: Chain multiple plugs together
- Conn Struct: Represents request/response
- Router: Route matching and dispatching
- Adapters: Cowboy, Bandit, and others
# Plug Example
defmodule MyApp.PlugExample do
import Plug.Conn
def init(options), do: options
def call(conn, _opts) do
conn
|> put_resp_content_type("text/plain")
|> send_resp(200, "Hello, World!")
end
end
defmodule MyApp.Router do
use Plug.Router
plug(:match)
plug(:dispatch)
get "/" do
send_resp(conn, 200, "Welcome to Plug!")
end
get "/hello/:name" do
send_resp(conn, 200, "Hello, #{name}!")
end
post "/api/users" do
{:ok, body, conn} = read_body(conn)
case Jason.decode(body) do
{:ok, params} ->
send_resp(conn, 201, Jason.encode!(%{message: "User created", user: params}))
{:error, _} ->
send_resp(conn, 400, Jason.encode!(%{error: "Invalid JSON"}))
end
end
get "/api/users" do
users = [%{id: 1, name: "Alice"}, %{id: 2, name: "Bob"}]
send_resp(conn, 200, Jason.encode!(users))
end
match _ do
send_resp(conn, 404, Jason.encode!(%{error: "Not found"}))
end
end
# Custom Plug
defmodule MyApp.AuthPlug do
import Plug.Conn
def init(options), do: options
def call(conn, _opts) do
case get_req_header(conn, "authorization") do
["Bearer " <> token] ->
case validate_token(token) do
{:ok, user} -> assign(conn, :current_user, user)
{:error, _} -> conn |> send_resp(401, "Unauthorized") |> halt()
end
_ ->
conn |> send_resp(401, "Unauthorized") |> halt()
end
end
defp validate_token(token) do
# Validate JWT or other token
{:ok, %{id: 1, name: "Alice"}}
end
end
# Run the router
{:ok, _} = Plug.Cowboy.http(MyApp.Router, [])Protocols provide polymorphism for different data types. They allow you to define behavior that can be implemented for various types, similar to interfaces in other languages.
- Protocol Definition:
defprotocol - Protocol Implementation:
defimplfor specific types - Data Types: Any data type can implement protocols
- Use Cases: Custom serialization, formatting, inspection
- Fallback:
@fallback_to_anyfor default implementation
# Protocol Example in Elixir
defprotocol Greeter do
def greet(person)
end
# Implement protocol for various types
defimpl Greeter, for: String do
def greet(name) do
"Hello, #{name}!"
end
end
defimpl Greeter, for: Map do
def greet(person) do
"Hello, #{person[:name]}! You are #{person[:age]} years old."
end
end
defimpl Greeter, for: List do
def greet([name, age]) do
"Hello, #{name}! You are #{age} years old."
end
end
defimpl Greeter, for: Integer do
def greet(id) do
"User with ID: #{id}"
end
end
defimpl Greeter, for: Tuple do
def greet({name, age, city}) do
"Hello, #{name} from #{city}! You are #{age} years old."
end
end
# Fallback to any
defimpl Greeter, for: Any do
def greet(_), do: "Greetings!"
end
# Usage
IO.puts(Greeter.greet("Alice"))
IO.puts(Greeter.greet(%{name: "Bob", age: 30}))
IO.puts(Greeter.greet(["Carol", 22]))
IO.puts(Greeter.greet(123))
IO.puts(Greeter.greet({"Dave", 35, "NYC"}))
# Protocol inheritance
defprotocol Serializer do
def to_json(data)
end
defimpl Serializer, for: Map do
def to_json(data) do
Jason.encode!(data)
end
end
defimpl Serializer, for: List do
def to_json(data) do
Jason.encode!(data)
end
end
defimpl Serializer, for: Atom do
def to_json(data) do
Jason.encode!(Atom.to_string(data))
end
end
data = %{name: "Alice", age: 25}
IO.puts("JSON: #{Serializer.to_json(data)}")Macros allow metaprogramming by generating code at compile time. They enable powerful abstractions and DSL creation by manipulating the abstract syntax tree (AST).
- Quote/Unquote:
quoteandunquotefor code generation - Compile-time Execution: Macros run during compilation
- Custom Constructs: Create custom syntax and constructs
- DSL Creation: Build domain-specific languages
- Hygiene: Macros are hygienic by default
# Macros in Elixir
defmodule MyMacros do
defmacro unless(condition, do: expression) do
quote do
if !unquote(condition) do
unquote(expression)
end
end
end
defmacro debug(expression) do
quote do
IO.puts("Expression: #{unquote(expression)}")
result = unquote(expression)
IO.puts("Result: #{result}")
result
end
end
defmacro log(message) do
quote do
IO.puts("[LOG] #{unquote(message)} at #{DateTime.utc_now()}")
end
end
defmacro assert(expression, message \ "Assertion failed") do
quote do
if !unquote(expression) do
raise unquote(message)
end
end
end
defmacro chain(expressions) do
quote do
unquote(expressions)
|> Enum.reduce(fn expr, acc -> expr + acc end)
end
end
defmacro unless(condition, do: do_block, else: else_block) do
quote do
if unquote(condition) do
unquote(else_block)
else
unquote(do_block)
end
end
end
end
defmodule MyApp do
require MyMacros
def test do
MyMacros.unless true do
IO.puts("This won't print")
end
MyMacros.unless false do
IO.puts("This will print")
end
x = 10
MyMacros.debug(x * 2)
MyMacros.log("Application started")
MyMacros.assert(x > 5, "x must be greater than 5")
result = MyMacros.chain([1, 2, 3, 4, 5])
IO.puts("Chain result: #{result}")
# Custom unless with else
MyMacros.unless true do
IO.puts("This won't print")
else
IO.puts("This will print")
end
end
end
MyApp.test()Distributed Elixir allows multiple Elixir nodes to communicate over a network, enabling distributed systems and fault tolerance with built-in Erlang distribution.
- Node Communication: Connect nodes via
Node.connect - Remote Spawning:
Node.spawnfor remote process creation - Distributed Erlang: Built on Erlang's distribution capabilities
- Network Topology: Fully connected mesh network
- Distributed Transactions: Across multiple nodes
# Distributed Elixir Example
# Node 1
# Start node with name
# iex --name node1@127.0.0.1 --cookie secret
# Node 2
# iex --name node2@127.0.0.1 --cookie secret
defmodule Distributed do
def ping(node) do
Node.ping(node)
end
def list_nodes do
Node.list()
end
def send_message(node, message) do
Node.spawn(node, fn ->
IO.puts("Received message: #{message}")
end)
end
def remote_call(node, module, function, args) do
case Node.spawn(node, fn ->
apply(module, function, args)
end) do
pid when is_pid(pid) -> {:ok, pid}
error -> {:error, error}
end
end
def remote_call_with_result(node, module, function, args) do
result = :rpc.call(node, module, function, args)
case result do
{:badrpc, _} -> {:error, "RPC failed"}
_ -> {:ok, result}
end
end
def register_node(name) do
Node.set_cookie(:secret)
Node.start(name)
end
end
# Usage on node1
Distributed.ping(:node2@127.0.0.1)
Distributed.list_nodes()
Distributed.send_message(:node2@127.0.0.1, "Hello from node1")
# Execute function on remote node
Distributed.remote_call(:node2@127.0.0.1, IO, :puts, ["Hello from remote"])
# Remote call with result
Distributed.remote_call_with_result(:node2@127.0.0.1, :erlang, :now, [])Streams enable lazy, composable operations on collections. They process data on-demand, making them memory efficient and ideal for handling large datasets.
- Lazy Evaluation: Process only when needed
- Memory Efficiency: Handle large datasets
- Composability: Chain operations together
- Infinite Streams: Create infinite sequences
- Chunking: Process data in chunks
# Stream Module in Elixir
# Create a stream
stream = Stream.cycle([1, 2, 3])
|> Enum.take(10)
IO.puts("Cyclic stream: #{inspect(stream)}")
# Generate stream
stream = Stream.iterate(0, &(&1 + 2))
|> Enum.take(5)
IO.puts("Even numbers: #{inspect(stream)}")
# File stream
File.stream!("data.txt")
|> Stream.map(&String.trim/1)
|> Stream.filter(&(&1 != ""))
|> Enum.each(&IO.puts/1)
# Lazy processing
data = 1..1000
|> Stream.map(&(&1 * 2))
|> Stream.filter(&rem(&1, 3) == 0)
|> Enum.take(10)
IO.puts("First 10 numbers divisible by 3 after doubling: #{inspect(data)}")
# Streaming with chunk
stream = 1..100
|> Stream.chunk_every(10)
|> Stream.map(fn chunk -> Enum.sum(chunk) end)
|> Enum.take(5)
IO.puts("Sums of chunks: #{inspect(stream)}")
# Infinite stream with recursion
defmodule RandomStream do
def stream do
Stream.repeatedly(fn -> :rand.uniform(100) end)
end
def take(n) do
stream()
|> Enum.take(n)
end
end
random_numbers = RandomStream.take(10)
IO.puts("Random numbers: #{inspect(random_numbers)}")
# Stream with chunk_by
stream = 1..20
|> Stream.chunk_by(fn x -> rem(x, 2) == 0 end)
|> Enum.take(5)
IO.puts("Chunked: #{inspect(stream)}")OTP Applications provide a standardized structure for organizing code, dependencies, and starting the supervision tree. They are the building blocks of Elixir systems.
- Application Callback:
start/2andstop/1 - Supervision Tree: Root supervisor for all processes
- Configuration:
config/config.exsfor app settings - Dependencies: Managed via
mix.exs - Environment:
Mix.env()for runtime environment
# OTP Application Structure
defmodule MyApp do
use Application
def start(_type, _args) do
# Start the supervision tree
children = [
# Start the HTTP server
{Plug.Cowboy, scheme: :http, plug: MyApp.Router, options: [port: 4000]},
# Start the database
MyApp.Repo,
# Start the cache
{Cache, []},
# Start the scheduler
{Scheduler, []},
# Start the registry
{Registry, keys: :unique, name: MyApp.Registry}
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
def stop(_state) do
IO.puts("Application stopping...")
:ok
end
end
# Configuration
defmodule MyApp.Config do
def get(key) do
Application.get_env(:my_app, key)
end
def set(key, value) do
Application.put_env(:my_app, key, value)
end
def get_all do
Application.get_all_env(:my_app)
end
end
# Usage
MyApp.Config.set(:database_url, "postgres://localhost/myapp")
MyApp.Config.set(:pool_size, 10)
IO.puts("Database URL: #{MyApp.Config.get(:database_url)}")
IO.puts("Pool size: #{MyApp.Config.get(:pool_size)}")Behaviours define a set of callbacks that modules must implement. They provide a contract for module interaction and are used extensively in OTP.
- Behaviour Definition:
@callbackand@macrocallback - Implementation:
@behaviourmodule attribute - Common Behaviours: GenServer, Supervisor, Application
- Custom Behaviours: Create your own behaviours
- Callback Validation: Compile-time validation
# Custom Behaviour Example
defmodule Worker do
@callback start_link(any()) :: {:ok, pid()} | {:error, any()}
@callback perform(any()) :: {:ok, any()} | {:error, any()}
@callback stop(pid()) :: :ok
@callback get_status(pid()) :: {:running | :stopped, any()}
defmacro __using__(_opts) do
quote do
@behaviour Worker
def start_link(args) do
GenServer.start_link(__MODULE__, args, name: __MODULE__)
end
def stop(pid) do
GenServer.stop(pid)
end
def get_status(pid) do
GenServer.call(pid, :get_status)
end
defoverridable Worker
end
end
end
defmodule TaskWorker do
use Worker
def init(args) do
{:ok, %{task: args[:task], status: :running}}
end
def perform(task) do
IO.puts("Performing task: #{task}")
Process.sleep(1000)
{:ok, "Task completed: #{task}"}
end
def handle_call(:get_status, _from, state) do
{:reply, {:running, state}, state}
end
def handle_call({:perform, task}, _from, state) do
result = perform(task)
{:reply, result, state}
end
def handle_cast({:stop, reason}, state) do
{:stop, reason, state}
end
end
defmodule EmailWorker do
use Worker
def init(args) do
{:ok, %{email: args[:email], sent: 0}}
end
def perform(email) do
IO.puts("Sending email to: #{email}")
Process.sleep(500)
{:ok, "Email sent to #{email}"}
end
def handle_call(:get_status, _from, state) do
{:reply, {:running, state}, state}
end
def handle_call({:perform, email}, _from, state) do
result = perform(email)
updated_state = %{state | sent: state.sent + 1}
{:reply, result, updated_state}
end
end
# Usage
{:ok, pid1} = TaskWorker.start_link(%{task: "Process data"})
{:ok, result1} = GenServer.call(pid1, {:perform, "Analyze log"})
IO.puts("Result 1: #{result1}")
{:ok, pid2} = EmailWorker.start_link(%{email: "user@example.com"})
{:ok, result2} = GenServer.call(pid2, {:perform, "user@example.com"})
IO.puts("Result 2: #{result2}")Agents provide a simple way to manage state in a process. They are lightweight and easy to use for basic state management with get and update operations.
- State Management:
Agent.start_linkwith initial state - Get/Update:
Agent.get,Agent.update - Thread Safety: State changes are sequential
- Use Cases: Simple counters, caches, configuration
- Short-lived: For simple state management
# Agent Example in Elixir
defmodule UserAgent do
def start_link(initial_state) do
Agent.start_link(fn -> initial_state end, name: __MODULE__)
end
def add_user(user) do
Agent.update(__MODULE__, fn state -> [user | state] end)
end
def get_users do
Agent.get(__MODULE__, fn state -> state end)
end
def get_user_by_id(id) do
Agent.get(__MODULE__, fn state ->
Enum.find(state, fn user -> user.id == id end)
end)
end
def update_user(id, new_user_data) do
Agent.update(__MODULE__, fn state ->
Enum.map(state, fn user ->
if user.id == id do
Map.merge(user, new_user_data)
else
user
end
end)
end)
end
def delete_user(id) do
Agent.update(__MODULE__, fn state ->
Enum.filter(state, fn user -> user.id != id end)
end)
end
def clear_all do
Agent.update(__MODULE__, fn _ -> [] end)
end
def count_users do
Agent.get(__MODULE__, fn state -> length(state) end)
end
end
# Usage
{:ok, _pid} = UserAgent.start_link([])
# Add users
UserAgent.add_user(%{id: 1, name: "Alice", age: 25})
UserAgent.add_user(%{id: 2, name: "Bob", age: 30})
UserAgent.add_user(%{id: 3, name: "Carol", age: 22})
# Get all users
all_users = UserAgent.get_users()
IO.puts("All users: #{inspect(all_users)}")
# Find user by ID
user = UserAgent.get_user_by_id(2)
IO.puts("User 2: #{inspect(user)}")
# Update user
UserAgent.update_user(1, %{age: 26, name: "Alice Johnson"})
# Delete user
UserAgent.delete_user(3)
# Get updated list
IO.puts("Updated users: #{inspect(UserAgent.get_users())}")
IO.puts("User count: #{UserAgent.count_users()}")Registry provides a way to register processes by name for easy lookup. It supports unique and duplicate keys with built-in conflict resolution.
- Registration:
Registry.registerfor process registration - Lookup:
Registry.lookupto find processes - Unique Keys: Each key maps to one process
- Duplicate Keys: Multiple processes per key
- Registry Monitoring: Track process lifecycle
# Registry Example in Elixir
defmodule MyRegistry do
use Registry
def start_link do
Registry.start_link(keys: :unique, name: __MODULE__)
end
def register(name, pid) do
Registry.register(__MODULE__, name, pid)
end
def unregister(name) do
Registry.unregister(__MODULE__, name)
end
def lookup(name) do
Registry.lookup(__MODULE__, name)
end
def whereis(name) do
case Registry.lookup(__MODULE__, name) do
[{pid, _}] -> pid
[] -> nil
end
end
def dispatch(name, message) do
case whereis(name) do
nil -> {:error, "Process not found"}
pid -> send(pid, message)
end
end
def register_with_value(name, pid, value) do
Registry.register(__MODULE__, name, value)
end
def get_value(name) do
case Registry.lookup(__MODULE__, name) do
[{_, value}] -> {:ok, value}
[] -> {:error, "Not found"}
end
end
end
# Sample process
defmodule SampleProcess do
def start_link(name) do
pid = spawn(fn -> loop() end)
MyRegistry.register(name, pid)
pid
end
def start_link_with_value(name, value) do
pid = spawn(fn -> loop() end)
MyRegistry.register_with_value(name, pid, value)
pid
end
def loop do
receive do
message ->
IO.puts("Received: #{message}")
loop()
end
end
end
# Usage
{:ok, _} = MyRegistry.start_link()
# Start processes
SampleProcess.start_link(:worker1)
SampleProcess.start_link_with_value(:worker2, "Important Worker")
# Lookup
pid = MyRegistry.whereis(:worker1)
IO.puts("Worker1 PID: #{inspect(pid)}")
# Get value
{:ok, value} = MyRegistry.get_value(:worker2)
IO.puts("Worker2 value: #{value}")
# Send message
MyRegistry.dispatch(:worker1, "Hello Worker1!")
MyRegistry.dispatch(:worker2, "Hello Worker2!")
# Unregister
MyRegistry.unregister(:worker2)
IO.puts("After unregister: #{MyRegistry.whereis(:worker2)}")DynamicSupervisor allows starting and stopping child processes dynamically at runtime, unlike static supervisors. It's ideal for worker pools and temporary processes.
- Dynamic Children: Start children on demand
- Strategies:
:one_for_oneonly - Use Cases: Worker pools, temporary processes
- Management:
start_child,terminate_child - Count Children:
count_childrenfor monitoring
# DynamicSupervisor Example
defmodule MySupervisor do
use DynamicSupervisor
def start_link(init_arg) do
DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
end
def start_child(module, args) do
DynamicSupervisor.start_child(__MODULE__, {module, args})
end
def terminate_child(pid) do
DynamicSupervisor.terminate_child(__MODULE__, pid)
end
def count_children do
DynamicSupervisor.count_children(__MODULE__)
end
def which_children do
DynamicSupervisor.which_children(__MODULE__)
end
def init(_init_arg) do
DynamicSupervisor.init(strategy: :one_for_one)
end
end
# Worker process
defmodule DynamicWorker do
use GenServer
def start_link(name) do
GenServer.start_link(__MODULE__, name, name: via_tuple(name))
end
def via_tuple(name) do
{:via, Registry, {MyRegistry, name}}
end
def init(name) do
IO.puts("Worker #{name} started")
{:ok, %{name: name, tasks: 0}}
end
def work(pid, task) do
GenServer.call(pid, {:work, task})
end
def handle_call({:work, task}, _from, state) do
IO.puts("#{state.name}: Working on #{task}")
Process.sleep(1000)
new_state = %{state | tasks: state.tasks + 1}
{:reply, {:ok, "Completed #{task}"}, new_state}
end
def handle_info(:stop, state) do
IO.puts("#{state.name}: Stopping")
{:stop, :normal, state}
end
end
# Usage
{:ok, _} = MySupervisor.start_link([])
# Start dynamic children
{:ok, pid1} = MySupervisor.start_child(DynamicWorker, ["Worker1"])
{:ok, pid2} = MySupervisor.start_child(DynamicWorker, ["Worker2"])
DynamicWorker.work(pid1, "Task A")
DynamicWorker.work(pid2, "Task B")
IO.puts("Children count: #{MySupervisor.count_children()}")
# Terminate a child
MySupervisor.terminate_child(pid1)
# Add new child
{:ok, pid3} = MySupervisor.start_child(DynamicWorker, ["Worker3"])
DynamicWorker.work(pid3, "Task C")Task Supervisor provides a supervisor for managing tasks, making it easy to run supervised asynchronous operations with automatic error handling.
- Supervised Tasks: Tasks are automatically supervised
- Async/Await: Same API as regular tasks
- Fault Tolerance: Restart failed tasks
- Use Cases: Background jobs, parallel processing
- Task.Supervisor: Built-in task supervisor
# Task with Supervisor Example
defmodule TaskSupervisor do
use Supervisor
def start_link do
Supervisor.start_link(__MODULE__, [], name: __MODULE__)
end
def start_task(function) do
Supervisor.start_child(__MODULE__, {Task, function})
end
def start_task_with_timeout(function, timeout \ 5000) do
Supervisor.start_child(__MODULE__, {Task, fn ->
try do
Task.await(Task.async(function), timeout)
rescue
e in Task.TimeoutError -> {:error, "Task timed out"}
end
end})
end
def init(_) do
children = [
{Task.Supervisor, name: MyTaskSupervisor}
]
Supervisor.init(children, strategy: :one_for_one)
end
end
defmodule Worker do
def perform_heavy_task(data) do
Task.Supervisor.start_child(MyTaskSupervisor, fn ->
Process.sleep(2000)
IO.puts("Completed: #{data}")
{:ok, data}
end)
end
def perform_with_result(data) do
Task.Supervisor.start_child(MyTaskSupervisor, fn ->
result = do_work(data)
{:ok, result}
end)
end
defp do_work(data) do
Process.sleep(1000)
"Processed: #{data}"
end
end
# Usage
TaskSupervisor.start_link()
# Start multiple tasks
1..5
|> Enum.each(fn i ->
Worker.perform_heavy_task("Task #{i}")
end)
# Tasks with results
tasks = 1..3
|> Enum.map(fn i ->
Worker.perform_with_result("Data #{i}")
end)
Enum.each(tasks, fn task ->
case Task.await(task) do
{:ok, result} -> IO.puts("Result: #{result}")
_ -> IO.puts("Task failed")
end
end)
IO.puts("All tasks started")Phoenix PubSub provides a publish-subscribe mechanism for real-time messaging between processes and nodes. It's built on PG2 for distributed messaging.
- Topics: Subscribe to specific topics
- Broadcasting: Send messages to all subscribers
- Distributed: Works across nodes
- Use Cases: Real-time notifications, chat, updates
- PG2 Integration: Distributed process groups
# Phoenix PubSub Example
defmodule MyApp.PubSub do
use Phoenix.PubSub
def start_link do
Phoenix.PubSub.start_link(__MODULE__, [name: __MODULE__])
end
def broadcast(topic, message) do
Phoenix.PubSub.broadcast(__MODULE__, topic, {:message, message})
end
def broadcast_from(sender, topic, message) do
Phoenix.PubSub.broadcast_from(__MODULE__, sender, topic, {:message, message})
end
def subscribe(topic) do
Phoenix.PubSub.subscribe(__MODULE__, topic)
end
def unsubscribe(topic) do
Phoenix.PubSub.unsubscribe(__MODULE__, topic)
end
def subscribe_to_many(topics) do
Enum.each(topics, fn topic ->
Phoenix.PubSub.subscribe(__MODULE__, topic)
end)
end
def count_subscribers(topic) do
Phoenix.PubSub.count_subscribers(__MODULE__, topic)
end
end
# Listener process
defmodule Listener do
def start_link(topic) do
pid = spawn(fn -> loop(topic) end)
{:ok, pid}
end
def loop(topic) do
MyApp.PubSub.subscribe(topic)
receive do
{:message, message} ->
IO.puts("[#{topic}] Received: #{message}")
loop(topic)
end
end
end
# Message processor
defmodule MessageProcessor do
def start_link do
pid = spawn(fn -> loop() end)
{:ok, pid}
end
def loop do
receive do
{:broadcast, topic, message} ->
MyApp.PubSub.broadcast(topic, message)
loop()
{:broadcast_from, sender, topic, message} ->
MyApp.PubSub.broadcast_from(sender, topic, message)
loop()
end
end
end
# Usage
MyApp.PubSub.start_link()
# Create listeners
{:ok, _} = Listener.start_link("news")
{:ok, _} = Listener.start_link("updates")
{:ok, _} = Listener.start_link("alerts")
# Subscribe to multiple topics
Listener.start_link("user_channel")
# Publish messages
MyApp.PubSub.broadcast("news", "New article published!")
MyApp.PubSub.broadcast("updates", "System update available!")
MyApp.PubSub.broadcast("news", "Breaking news!")
MyApp.PubSub.broadcast("alerts", "Alert: System maintenance!")
# Check subscribers
IO.puts("News subscribers: #{MyApp.PubSub.count_subscribers("news")}")Streams enable efficient data processing with lazy evaluation, making them suitable for large datasets and real-time processing with memory efficiency.
- Lazy Evaluation: Process on demand
- Memory Efficiency: No need to load entire dataset
- Composition: Chain operations together
- Use Cases: File processing, data pipelines
- Infinite Streams: Process infinite sequences
# Streaming Example in Elixir
defmodule DataStream do
def generate_random_numbers(count) do
Stream.repeatedly(fn -> :rand.uniform(100) end)
|> Enum.take(count)
end
def process_data(stream) do
stream
|> Stream.map(&(&1 * 2))
|> Stream.filter(&(&1 > 50))
|> Stream.map(&IO.inspect/1)
|> Enum.to_list()
end
def read_file_chunks(file_path, chunk_size \ 10) do
File.stream!(file_path)
|> Stream.map(&String.trim/1)
|> Stream.filter(&(&1 != ""))
|> Stream.chunk_every(chunk_size)
|> Stream.map(fn chunk -> Enum.join(chunk, ",") end)
end
def web_request_stream(url) do
# Simulate web request streaming
Stream.iterate(1, &(&1 + 1))
|> Stream.map(fn page ->
# Simulate paginated API
%{page: page, data: Enum.to_list(1..10)}
end)
|> Stream.filter(&(&1.page <= 5))
end
def process_large_file(file_path) do
File.stream!(file_path)
|> Stream.map(&String.trim/1)
|> Stream.map(&String.split/1)
|> Stream.flat_map(& &1)
|> Stream.map(&String.downcase/1)
|> Stream.filter(&(&1 != ""))
|> Enum.reduce(%{}, fn word, acc ->
Map.update(acc, word, 1, &(&1 + 1))
end)
end
def stream_with_state(data) do
Stream.iterate(data, fn state ->
new_state = state + 1
IO.puts("State: #{new_state}")
new_state
end)
|> Enum.take(5)
end
def slow_stream do
Stream.iterate(0, &(&1 + 1))
|> Stream.take(10)
|> Stream.map(fn x ->
Process.sleep(100)
x * 2
end)
end
end
# Usage
# Generate random numbers
random_numbers = DataStream.generate_random_numbers(20)
IO.puts("Random numbers: #{inspect(random_numbers)}")
# Process data
processed = DataStream.process_data(1..100)
IO.puts("Processed: #{inspect(processed)}")
# Web request stream
DataStream.web_request_stream()
|> Enum.each(fn response ->
IO.puts("Page #{response.page}: #{inspect(response.data)}")
end)
# Process large file
# result = DataStream.process_large_file("data.txt")
# IO.puts("Word count: #{inspect(result)}")Mnesia is a distributed database management system built into Erlang/Elixir, providing ACID transactions and fault tolerance with built-in replication.
- ACID Transactions: Atomic, consistent, isolated, durable
- Distributed: Works across multiple nodes
- Table Types: Set, ordered_set, bag
- Use Cases: Caching, configuration, session storage
- Replication: Built-in data replication
# Mnesia Database Example
defmodule MyMnesia do
def start do
:mnesia.start()
:mnesia.create_table(:users, [
attributes: [:id, :name, :email, :age],
record_name: :user,
type: :set
])
end
def add_user(id, name, email, age) do
user = {:user, id, name, email, age}
:mnesia.transaction(fn ->
:mnesia.write(user)
end)
end
def get_user(id) do
:mnesia.transaction(fn ->
case :mnesia.read({:user, id}) do
[user] -> {:ok, user}
[] -> {:error, :not_found}
end
end)
end
def get_all_users do
:mnesia.transaction(fn ->
:mnesia.match_object({:user, :_, :_, :_, :_})
end)
end
def update_user(id, field, value) do
:mnesia.transaction(fn ->
case :mnesia.read({:user, id}) do
[{:user, id, name, email, age}] ->
updated_user = case field do
:name -> {:user, id, value, email, age}
:email -> {:user, id, name, value, age}
:age -> {:user, id, name, email, value}
end
:mnesia.write(updated_user)
{:ok, updated_user}
[] ->
{:error, :not_found}
end
end)
end
def delete_user(id) do
:mnesia.transaction(fn ->
:mnesia.delete({:user, id})
end)
end
def find_by_name(name) do
:mnesia.transaction(fn ->
:mnesia.match_object({:user, :_, name, :_, :_})
end)
end
def find_by_age_range(min_age, max_age) do
:mnesia.transaction(fn ->
:mnesia.match_object({:user, :_, :_, :_, :_})
|> Enum.filter(fn {:user, _, _, _, age} -> age >= min_age and age <= max_age end)
end)
end
end
# Usage
MyMnesia.start()
# Add users
MyMnesia.add_user(1, "Alice", "alice@email.com", 25)
MyMnesia.add_user(2, "Bob", "bob@email.com", 30)
MyMnesia.add_user(3, "Carol", "carol@email.com", 22)
# Get user
case MyMnesia.get_user(1) do
{:ok, user} -> IO.puts("User found: #{inspect(user)}")
{:error, _} -> IO.puts("User not found")
end
# Get all users
all_users = MyMnesia.get_all_users()
IO.puts("All users: #{inspect(all_users)}")
# Update user
MyMnesia.update_user(1, :age, 26)
# Delete user
MyMnesia.delete_user(3)
# Find by name
users = MyMnesia.find_by_name("Alice")
IO.puts("Found by name: #{inspect(users)}")
# Find by age range
users = MyMnesia.find_by_age_range(20, 28)
IO.puts("Found by age range: #{inspect(users)}")GenStage is a behaviour for building event processing pipelines with back-pressure and flow control. It enables scalable data processing systems.
- Producers: Generate events
- Consumers: Process events
- Producer-Consumers: Both produce and consume
- Back-pressure: Flow control via demand
- Use Cases: Data pipelines, ETL, streaming
# GenStage Example
defmodule Producer do
use GenStage
def start_link(initial) do
GenStage.start_link(__MODULE__, initial, name: __MODULE__)
end
def init(initial) do
{:producer, initial}
end
def handle_demand(demand, state) when demand > 0 do
events = Enum.to_list(state..state + demand - 1)
{:noreply, events, state + demand}
end
end
defmodule Consumer do
use GenStage
def start_link do
GenStage.start_link(__MODULE__, :ok, name: __MODULE__)
end
def init(:ok) do
{:consumer, :ok}
end
def handle_events(events, _from, state) do
Enum.each(events, fn event ->
IO.puts("Consumed: #{event}")
end)
{:noreply, [], state}
end
end
defmodule ProducerConsumer do
use GenStage
def start_link do
GenStage.start_link(__MODULE__, :ok, name: __MODULE__)
end
def init(:ok) do
{:producer_consumer, :ok}
end
def handle_events(events, _from, state) do
events = Enum.map(events, &(&1 * 2))
{:noreply, events, state}
end
end
defmodule Pipeline do
def start do
{:ok, producer} = Producer.start_link(1)
{:ok, producer_consumer} = ProducerConsumer.start_link()
{:ok, consumer} = Consumer.start_link()
# Create pipeline
GenStage.sync_subscribe(producer_consumer, to: producer)
GenStage.sync_subscribe(consumer, to: producer_consumer)
{:ok, producer}
end
def request_data(count) do
GenStage.demand(__MODULE__, count)
end
end
# Usage
{:ok, producer} = Pipeline.start()
# Request data
GenStage.demand(producer, 5)Flow is a library built on GenStage for parallel data processing with built-in back-pressure and composition. It provides high-performance data processing.
- Parallel Processing: Process data in parallel
- Back-pressure: Automatic flow control
- Composition: Chain operations together
- Use Cases: ETL, data pipelines, batch processing
- MapReduce: Built-in MapReduce support
# Flow Example (Parallel Processing)
defmodule FlowExample do
def process_data(data) do
data
|> Flow.from_enumerable()
|> Flow.map(&(&1 * 2))
|> Flow.filter(&(&1 > 10))
|> Flow.map(&IO.inspect/1)
|> Enum.to_list()
end
def process_file(file_path) do
File.stream!(file_path)
|> Flow.from_enumerable()
|> Flow.map(&String.trim/1)
|> Flow.filter(&(&1 != ""))
|> Flow.chunk_every(100)
|> Flow.map(fn chunk ->
Enum.join(chunk, ",")
end)
|> Enum.to_list()
end
def parallel_map(data, function) do
data
|> Flow.from_enumerable(max_demand: 10)
|> Flow.map(function)
|> Enum.to_list()
end
def parallel_aggregate(data) do
data
|> Flow.from_enumerable(max_demand: 10)
|> Flow.reduce(fn -> 0 end, fn x, acc -> x + acc end)
|> Enum.to_list()
end
def parallel_group_by(data, key_function) do
data
|> Flow.from_enumerable()
|> Flow.group_by(key_function)
|> Enum.to_list()
end
def parallel_join(left, right, join_key) do
Flow.from_enumerables([left, right])
|> Flow.map(fn item -> {join_key.(item), item} end)
|> Flow.group_by(fn {key, _} -> key end)
|> Flow.map(fn {key, items} ->
{key, Enum.map(items, fn {_, item} -> item end)}
end)
|> Enum.to_list()
end
end
# Usage
data = 1..100
# Process data in parallel
result = FlowExample.process_data(data)
IO.puts("Processed: #{inspect(result)}")
# Parallel map
squares = FlowExample.parallel_map(data, fn x -> x * x end)
IO.puts("Squares: #{inspect(Enum.take(squares, 10))}")
# Parallel aggregate
sum = FlowExample.parallel_aggregate(data)
IO.puts("Sum: #{inspect(sum)}")Broadway is a data processing pipeline library for Elixir, built on GenStage for high-throughput and fault-tolerant processing with built-in error handling.
- Pipeline Processing: Process data in stages
- Batch Processing: Process data in batches
- Fault Tolerance: Automatic retry and failure handling
- Use Cases: Data ingestion, ETL, stream processing
- Back-pressure: Built-in flow control
# Broadway Example (Data Processing Pipeline)
defmodule MyBroadway do
use Broadway
def start_link(_opts) do
Broadway.start_link(__MODULE__,
name: __MODULE__,
producer: [
module: {Broadway.DummyProducer, []},
concurrency: 5
],
processors: [
default: [concurrency: 10]
],
batchers: [
default: [concurrency: 5, batch_size: 100, batch_timeout: 1000]
]
)
end
def handle_message(_, message, _) do
# Process individual message
data = message.data
processed = String.upcase(data)
message
|> Broadway.Message.update_data(fn _ -> processed end)
end
def handle_batch(_, messages, _, _) do
# Process batch of messages
Enum.each(messages, fn message ->
IO.puts("Batch processed: #{message.data}")
end)
messages
end
def handle_failure(_, failure, _, _) do
# Handle failures
IO.puts("Failed to process: #{inspect(failure)}")
failure
end
def handle_message_with_timeout(_, message, _) do
try do
# Simulate work with timeout
Process.sleep(500)
message
rescue
e ->
IO.puts("Error processing: #{inspect(e)}")
{:error, message}
end
end
end
# Usage
{:ok, _} = MyBroadway.start_link([])
# Send test messages
1..10
|> Enum.each(fn i ->
Broadway.Message.new("Message #{i}")
|> MyBroadway.process()
end)Telemetry provides a standard way to emit and handle events for metrics, logging, and monitoring in Elixir applications with built-in integration support.
- Events: Emit events with measurements
- Handlers: Attach handlers to events
- Metrics: Collect performance and business metrics
- Integration: Works with Prometheus, StatsD, etc.
- Instrumentation: Built-in library instrumentation
# Telemetry Example
defmodule MyApp.Telemetry do
use Supervisor
def start_link do
Supervisor.start_link(__MODULE__, [], name: __MODULE__)
end
def init(_) do
children = [
{Telemetry, []}
]
Supervisor.init(children, strategy: :one_for_one)
end
def setup do
:telemetry.attach(
"my-handler",
[:my_app, :request, :stop],
&handle_event/4,
nil
)
:telemetry.attach(
"error-handler",
[:my_app, :request, :error],
&handle_error/4,
nil
)
end
def handle_event([:my_app, :request, :stop], measurements, metadata, _config) do
duration = measurements.duration
path = metadata.path
status = metadata.status
IO.puts("Request to #{path} completed in #{duration}ms with status #{status}")
# Log slow requests
if duration > 1000 do
IO.puts("Slow request: #{path} took #{duration}ms")
end
end
def handle_error([:my_app, :request, :error], measurements, metadata, _config) do
error = metadata.error
path = metadata.path
IO.puts("Error on #{path}: #{inspect(error)}")
end
def measure_request(path, status, function) do
start = System.monotonic_time()
try do
result = function.()
stop = System.monotonic_time()
duration = System.convert_time_unit(stop - start, :native, :millisecond)
:telemetry.execute([:my_app, :request, :stop], %{duration: duration}, %{
path: path,
status: status
})
result
rescue
e ->
stop = System.monotonic_time()
duration = System.convert_time_unit(stop - start, :native, :millisecond)
:telemetry.execute([:my_app, :request, :error], %{duration: duration}, %{
path: path,
error: e
})
{:error, e}
end
end
end
# Usage
MyApp.Telemetry.start_link()
MyApp.Telemetry.setup()
# Measure a successful request
MyApp.Telemetry.measure_request("/api/users", 200, fn ->
Process.sleep(500)
{:ok, "Response data"}
end)
# Measure a slow request
MyApp.Telemetry.measure_request("/api/reports", 200, fn ->
Process.sleep(1500)
{:ok, "Report data"}
end)
# Measure a failed request
MyApp.Telemetry.measure_request("/api/error", 500, fn ->
Process.sleep(100)
raise "Something went wrong"
end)Logger is the built-in logging system in Elixir, providing structured logging with configurable levels and backends for comprehensive logging capabilities.
- Log Levels: debug, info, warning, error
- Metadata: Attach metadata to log messages
- Backends: console, file, custom backends
- Configuration: Configure via
config.exs - Formatter: Custom log formatting
# Logger Example
defmodule MyApp.Logger do
require Logger
def configure do
Logger.configure(fn config ->
%{config | level: :debug, format: "$time $level $message"}
end)
Logger.add_backend(:console)
Logger.add_backend(MyApp.CustomLogger)
end
def log_info(message) do
Logger.info(message)
end
def log_debug(message) do
Logger.debug(message)
end
def log_warning(message) do
Logger.warning(message)
end
def log_error(message) do
Logger.error(message)
end
def log_with_metadata(message, metadata) do
Logger.metadata(metadata)
Logger.info(message)
Logger.reset_metadata()
end
def log_with_context(message, context) do
Logger.metadata(context)
Logger.info(message)
Logger.reset_metadata()
end
end
defmodule MyApp.CustomLogger do
use GenServer
def start_link do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end
def init(:ok) do
{:ok, %{logs: [], errors: 0}}
end
def handle_call(:get_logs, _from, state) do
{:reply, state.logs, state}
end
def handle_call(:get_stats, _from, state) do
{:reply, %{total: length(state.logs), errors: state.errors}, state}
end
def handle_info({:log, level, message, timestamp}, state) do
logs = [{timestamp, level, message} | state.logs]
errors = if level == :error, do: state.errors + 1, else: state.errors
{:noreply, %{state | logs: logs, errors: errors}}
end
end
# Usage
require Logger
MyApp.Logger.configure()
# Log messages
Logger.info("Application started")
Logger.debug("Debug information")
Logger.warning("Low memory warning")
Logger.error("Error occurred")
# Log with metadata
Logger.metadata(user_id: 123, request_id: "abc-123")
Logger.info("User action logged")
Logger.reset_metadata()
# Log with context
MyApp.Logger.log_with_context("User login", %{user_id: 456, ip: "192.168.1.1"})Elixir provides a comprehensive configuration system through Application module, environment variables, and config files for flexible application setup.
- Application.get_env: Access configuration
- config/config.exs: Configuration files
- Environment Variables: System environment variables
- Runtime Configuration:
Configmodule - Release Configuration: Runtime config for releases
# Application Configuration
defmodule MyApp.Config do
@moduledoc """
Application configuration module
"""
def get(key, default \ nil) do
Application.get_env(:my_app, key, default)
end
def set(key, value) do
Application.put_env(:my_app, key, value)
end
def load_config do
# Load from environment variables
database_url = System.get_env("DATABASE_URL") || "postgres://localhost/myapp"
port = System.get_env("PORT") || "4000"
secret_key = System.get_env("SECRET_KEY_BASE")
set(:database_url, database_url)
set(:port, String.to_integer(port))
set(:environment, System.get_env("MIX_ENV") || "development")
if secret_key do
set(:secret_key_base, secret_key)
end
# Load from config file
config_file = Path.join(File.cwd!(), "config/config.exs")
if File.exists?(config_file) do
Code.require_file(config_file)
end
# Load from config file
config_file = Path.join(File.cwd!(), "config/config.exs")
if File.exists?(config_file) do
Code.require_file(config_file)
end
end
def get_database_config do
%{
url: get(:database_url),
pool_size: get(:pool_size, 10),
timeout: get(:timeout, 5000),
loggers: get(:db_loggers, [])
}
end
def get_server_config do
%{
port: get(:port, 4000),
host: get(:host, "localhost"),
environment: get(:environment, "development"),
secret_key: get(:secret_key_base)
}
end
def get_cache_config do
%{
ttl: get(:cache_ttl, 3600),
max_size: get(:cache_max_size, 1000),
strategy: get(:cache_strategy, :lru)
}
end
end
# Usage
MyApp.Config.load_config()
# Access config
db_config = MyApp.Config.get_database_config()
IO.puts("Database URL: #{db_config.url}")
server_config = MyApp.Config.get_server_config()
IO.puts("Server port: #{server_config.port}")
# Update config
MyApp.Config.set(:pool_size, 20)
IO.puts("Pool size: #{MyApp.Config.get(:pool_size)}")Phoenix Channels provide real-time communication over WebSockets, enabling bidirectional messaging between clients and servers.
- WebSocket Layer: Persistent connection for real-time
- PubSub: Built-in publish/subscribe
- Presence: Track online users and state
- Authentication: Built-in socket authentication
# Phoenix Channels Example
defmodule MyAppWeb.UserSocket do
use Phoenix.Socket
channel "room:*", MyAppWeb.RoomChannel
channel "user:*", MyAppWeb.UserChannel
def connect(params, socket, _connect_info) do
{:ok, assign(socket, :user_id, params["user_id"])}
end
def id(_socket), do: nil
end
defmodule MyAppWeb.RoomChannel do
use Phoenix.Channel
def join("room:" <> room_id, _message, socket) do
{:ok, assign(socket, :room_id, room_id)}
end
def handle_in("new_msg", %{"body" => body}, socket) do
broadcast!(socket, "new_msg", %{body: body, user: socket.assigns.user_id})
{:noreply, socket}
end
endPhoenix Presence is a feature for tracking user presence across nodes, enabling real-time visibility of online users.
- User Tracking: Track online/offline status
- CRDT: Conflict-free replicated data types
- Presence State: Sync state across nodes
- Join/Leave Events: React to user presence changes
# Phoenix Presence Example
defmodule MyAppWeb.Presence do
use Phoenix.Presence,
otp_app: :my_app,
pubsub_server: MyApp.PubSub
end
defmodule MyAppWeb.RoomChannel do
use Phoenix.Channel
alias MyAppWeb.Presence
def join("room:" <> room_id, _params, socket) do
send(self(), :after_join)
{:ok, assign(socket, :room_id, room_id)}
end
def handle_info(:after_join, socket) do
{:ok, _} = Presence.track(socket, socket.assigns.user_id, %{
online_at: inspect(System.system_time(:second))
})
push(socket, "presence_state", Presence.list(socket))
{:noreply, socket}
end
endExUnit is Elixir's built-in testing framework, providing a simple and powerful way to write and run tests.
- Test Cases:
test/2macro for defining tests - Assertions:
assert,refute, and custom assertions - Setup/Teardown:
setupandsetup_allcallbacks - Async Tests: Run tests in parallel
# ExUnit Testing Example
defmodule MyApp.MathTest do
use ExUnit.Case
doctest MyApp.Math
test "add/2 works correctly" do
assert MyApp.Math.add(2, 3) == 5
assert MyApp.Math.add(-1, 1) == 0
end
test "divide/2 handles division by zero" do
assert_raise ArithmeticError, fn ->
MyApp.Math.divide(10, 0)
end
end
describe "factorial/1" do
test "returns 1 for 0" do
assert MyApp.Math.factorial(0) == 1
end
test "returns correct value for positive numbers" do
assert MyApp.Math.factorial(5) == 120
end
end
setup do
%{user: %{name: "Alice", age: 25}}
end
test "uses setup data", %{user: user} do
assert user.name == "Alice"
end
endMix is Elixir's build tool for creating, compiling, testing, and managing dependencies in Elixir projects.
- Project Management:
mix newto create projects - Dependencies: Manage dependencies with Hex
- Tasks:
mix test,mix compile,mix run - Custom Tasks: Create your own Mix tasks
# Mix Tasks Example
defmodule Mix.Tasks.Hello do
use Mix.Task
@shortdoc "Prints Hello, World!"
def run(_args) do
IO.puts("Hello, World!")
end
end
defmodule Mix.Tasks.Greet do
use Mix.Task
@shortdoc "Greets a person by name"
def run(args) do
name = case args do
[name] -> name
_ -> "World"
end
IO.puts("Hello, #{name}!")
end
end
defmodule Mix.Tasks.Setup do
use Mix.Task
def run(_args) do
Mix.shell().info("Setting up application...")
# Run database migrations
Mix.Task.run("ecto.create")
Mix.Task.run("ecto.migrate")
# Seed data
Mix.Task.run("run", ["priv/repo/seeds.exs"])
Mix.shell().info("Setup complete!")
end
endHex is the package manager for Elixir, providing a central repository for libraries and dependencies.
- Package Management: Install and manage packages
- Versioning: Semantic versioning support
- Dependencies: Resolve and fetch dependencies
- Publishing: Publish your own packages
# Hex Package Example
defmodule MyApp.MixProject do
use Mix.Project
def project do
[
app: :my_app,
version: "0.1.0",
elixir: "~> 1.12",
start_permanent: Mix.env() == :prod,
deps: deps(),
package: package(),
description: description()
]
end
def application do
[
extra_applications: [:logger],
mod: {MyApp.Application, []}
]
end
defp deps do
[
{:phoenix, "~> 1.6.0"},
{:phoenix_live_view, "~> 0.17.0"},
{:ecto_sql, "~> 3.0"},
{:postgrex, ">= 0.0.0"},
{:jason, "~> 1.2"},
{:plug_cowboy, "~> 2.5"}
]
end
defp package do
[
files: ["lib", "priv", "mix.exs", "README.md"],
licenses: ["MIT"],
links: %{"GitHub" => "https://github.com/username/my_app"}
]
end
defp description do
"A sample Elixir application with Hex packaging"
end
endDynamicSupervisor allows starting and stopping child processes dynamically at runtime, ideal for worker pools and temporary processes with one-for-one strategy.
- Dynamic Children: Start children on demand
- Strategies:
:one_for_oneonly - Use Cases: Worker pools, temporary processes
- Management:
start_child,terminate_child - Count Children:
count_childrenfor monitoring
# Supervisor with Dynamic Children
defmodule MyApp.DynamicSupervisor do
use DynamicSupervisor
def start_link(init_arg) do
DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
end
def start_child(worker_module, args) do
DynamicSupervisor.start_child(__MODULE__, {worker_module, args})
end
def terminate_child(pid) do
DynamicSupervisor.terminate_child(__MODULE__, pid)
end
def count_children do
DynamicSupervisor.count_children(__MODULE__)
end
def init(_init_arg) do
DynamicSupervisor.init(strategy: :one_for_one)
end
end
defmodule MyApp.Worker do
use GenServer
def start_link(args) do
GenServer.start_link(__MODULE__, args, name: via_tuple(args[:name]))
end
def via_tuple(name) do
{:via, Registry, {MyApp.Registry, name}}
end
def init(args) do
{:ok, args}
end
def handle_call(:get_state, _from, state) do
{:reply, state, state}
end
end
# Usage
{:ok, _} = MyApp.DynamicSupervisor.start_link([])
# Start dynamic children
{:ok, pid1} = MyApp.DynamicSupervisor.start_child(MyApp.Worker, [name: "worker1"])
{:ok, pid2} = MyApp.DynamicSupervisor.start_child(MyApp.Worker, [name: "worker2"])
# Check children count
IO.puts("Children count: #{MyApp.DynamicSupervisor.count_children()}")GenStage supports multiple producers with broadcast dispatcher, enabling parallel event processing with back-pressure and flow control.
- Multiple Producers: Multiple event sources
- Broadcast Dispatcher: Send events to all consumers
- Back-pressure: Flow control via demand
- Use Cases: Event processing pipelines
- Parallel Processing: Process events concurrently
# GenStage with Multiple Producers
defmodule MultiProducer do
use GenStage
def start_link(producers) do
GenStage.start_link(__MODULE__, producers, name: __MODULE__)
end
def init(producers) do
{:producer, producers, dispatcher: GenStage.BroadcastDispatcher}
end
def handle_demand(demand, state) when demand > 0 do
events = Enum.map(1..demand, fn i -> "Event #{i}" end)
{:noreply, events, state}
end
end
defmodule MultiConsumer do
use GenStage
def start_link do
GenStage.start_link(__MODULE__, :ok, name: __MODULE__)
end
def init(:ok) do
{:consumer, :ok}
end
def handle_events(events, _from, state) do
Enum.each(events, fn event ->
IO.puts("Consumer received: #{event}")
end)
{:noreply, [], state}
end
end
# Usage
{:ok, producer} = MultiProducer.start_link([])
{:ok, consumer1} = MultiConsumer.start_link()
{:ok, consumer2} = MultiConsumer.start_link()
GenStage.sync_subscribe(consumer1, to: producer)
GenStage.sync_subscribe(consumer2, to: producer)
GenStage.demand(producer, 3)Phoenix LiveView with Ecto enables building interactive UIs with database integration, providing real-time updates and form handling with Ecto changesets.
- Database Integration: Ecto for data persistence
- Real-time Updates: LiveView for interactive UIs
- Form Handling: Changesets for validation
- Data Binding: Bind database data to UI
- CRUD Operations: Create, read, update, delete
# Phoenix LiveView with Ecto
defmodule MyAppWeb.UserLive do
use Phoenix.LiveView
alias MyApp.{Repo, User}
def mount(_params, _session, socket) do
users = Repo.all(User)
{:ok, assign(socket, users: users, form: nil)}
end
def render(assigns) do
~H"""
<div>
<h2>Users</h2>
<table>
<thead>
<tr><th>Name</th><th>Email</th><th>Age</th></tr>
</thead>
<tbody>
<%= for user <- @users do %>
<tr>
<td><%= user.name %></td>
<td><%= user.email %></td>
<td><%= user.age %></td>
</tr>
<% end %>
</tbody>
</table>
<h3>Add User</h3>
<.form let={f} for={@form} phx-submit="save">
<%= text_input(f, :name, placeholder: "Name") %>
<%= text_input(f, :email, placeholder: "Email") %>
<%= number_input(f, :age, placeholder: "Age") %>
<%= submit("Save") %>
</.form>
</div>
"""
end
def handle_event("save", %{"user" => user_params}, socket) do
changeset = User.changeset(%User{}, user_params)
case Repo.insert(changeset) do
{:ok, user} ->
users = [user | socket.assigns.users]
{:noreply, assign(socket, users: users, form: nil)}
{:error, changeset} ->
{:noreply, assign(socket, form: changeset)}
end
end
endPhoenix LiveView with PubSub enables real-time communication between LiveViews, allowing broadcasting of messages and updates across connected clients.
- PubSub Integration: Phoenix PubSub for messaging
- Real-time Updates: Broadcast to multiple LiveViews
- Event Handling: Handle incoming messages
- Use Cases: Chat, notifications, real-time feeds
- Cross-View Communication: Communicate between views
# Phoenix LiveView with PubSub
defmodule MyAppWeb.ChatLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
if connected?(socket) do
Phoenix.PubSub.subscribe(MyApp.PubSub, "chat")
end
{:ok, assign(socket, messages: [])}
end
def render(assigns) do
~H"""
<div>
<h2>Chat</h2>
<div id="messages">
<%= for msg <- @messages do %>
<div><%= msg %></div>
<% end %>
</div>
<form phx-submit="send">
<input type="text" name="message" placeholder="Type a message..." />
<button type="submit">Send</button>
</form>
</div>
"""
end
def handle_event("send", %{"message" => message}, socket) do
Phoenix.PubSub.broadcast(MyApp.PubSub, "chat", {:new_message, message})
{:noreply, socket}
end
def handle_info({:new_message, message}, socket) do
{:noreply, update(socket, :messages, fn msgs -> [message | msgs] end)}
end
endPhoenix Controllers handle HTTP requests with Plug for authentication, authorization, and request/response processing in a pipeline pattern.
- Controllers: Handle HTTP requests
- Plug Pipeline: Process requests through plugs
- Authentication: JWT and session-based auth
- JSON APIs: Render JSON responses
- Error Handling: Handle errors gracefully
# Phoenix Controller with Plug
defmodule MyAppWeb.AuthController do
use MyAppWeb, :controller
def login(conn, %{"email" => email, "password" => password}) do
case MyApp.Auth.authenticate(email, password) do
{:ok, user} ->
conn
|> put_session(:user_id, user.id)
|> put_status(:ok)
|> json(%{token: MyApp.Auth.generate_token(user)})
{:error, reason} ->
conn
|> put_status(:unauthorized)
|> json(%{error: reason})
end
end
def logout(conn, _params) do
conn
|> delete_session(:user_id)
|> put_status(:ok)
|> json(%{message: "Logged out"})
end
def me(conn, _params) do
user = conn.assigns.current_user
json(conn, %{user: user})
end
end
defmodule MyAppWeb.AuthPlug do
import Plug.Conn
def init(options), do: options
def call(conn, _opts) do
case get_req_header(conn, "authorization") do
["Bearer " <> token] ->
case MyApp.Auth.verify_token(token) do
{:ok, user} ->
assign(conn, :current_user, user)
{:error, _} ->
conn |> send_resp(401, "Unauthorized") |> halt()
end
_ ->
conn |> send_resp(401, "Unauthorized") |> halt()
end
end
endPhoenix Router defines routes for both traditional controllers and LiveViews, enabling seamless integration of server-rendered and real-time pages.
- Routing: Define URL routes
- LiveView Routes:
livemacro for LiveViews - Scopes: Group routes by scope
- Pipelines: Apply plugs to routes
- Nested Routes: Resource nesting and routing
# Phoenix Router with LiveView
defmodule MyAppWeb.Router do
use MyAppWeb, :router
import Phoenix.LiveView.Router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", MyAppWeb do
pipe_through :browser
get "/", PageController, :index
live "/counter", CounterLive
live "/chat", ChatLive
live "/users", UserLive
live "/dashboard", DashboardLive
live "/profile/:id", ProfileLive
end
scope "/api", MyAppWeb do
pipe_through :api
resources "/users", UserController, except: [:new, :edit]
resources "/posts", PostController, except: [:new, :edit]
end
# Admin routes
scope "/admin", MyAppWeb.Admin, as: :admin do
pipe_through [:browser, :admin_auth]
resources "/users", UserController
resources "/posts", PostController
live "/dashboard", DashboardLive
end
endPhoenix with Ecto Associations enables working with related data through associations like belongs_to, has_many, and has_one.
- Associations:
belongs_to,has_many,has_one - Preloading:
preloadfor eager loading - Build Associations:
build_assocfor nested creation - Query Joins: Join tables in queries
- Nested Data: Work with nested data structures
# Phoenix with Ecto Associations
defmodule MyApp.Blog do
alias MyApp.{Repo, User, Post, Comment}
def create_post(user_id, attrs) do
user = Repo.get(User, user_id)
user
|> Ecto.build_assoc(:posts)
|> Post.changeset(attrs)
|> Repo.insert()
end
def get_posts_with_comments do
query = from p in Post,
join: c in assoc(p, :comments),
preload: [comments: c],
order_by: [desc: p.inserted_at]
Repo.all(query)
end
def add_comment(post_id, user_id, content) do
post = Repo.get(Post, post_id)
user = Repo.get(User, user_id)
Ecto.build_assoc(post, :comments)
|> Comment.changeset(%{content: content, user_id: user_id})
|> Repo.insert()
end
def get_user_posts(user_id) do
query = from u in User,
where: u.id == ^user_id,
preload: [posts: from(p in Post, order_by: [desc: p.inserted_at])]
Repo.one(query)
end
endPhoenix with Ecto Multi enables performing multiple database operations in a single transaction with built-in error handling and rollback support.
- Ecto.Multi: Bundle multiple operations
- Transactions: ACID transactions with rollback
- Error Handling: Handle errors gracefully
- Run/Insert/Update: Multiple operation types
- Complex Operations: Combine inserts, updates, and custom logic
# Phoenix with Ecto Multi
defmodule MyApp.Accounts do
alias MyApp.{Repo, User, Profile}
import Ecto.Multi
def create_user_with_profile(attrs, profile_attrs) do
Ecto.Multi.new()
|> Ecto.Multi.insert(:user, User.changeset(%User{}, attrs))
|> Ecto.Multi.run(:profile, fn repo, %{user: user} ->
changeset = Profile.changeset(%Profile{}, profile_attrs)
changeset = Ecto.Changeset.put_change(changeset, :user_id, user.id)
repo.insert(changeset)
end)
|> Ecto.Multi.run(:notify, fn repo, %{user: user} ->
MyApp.Notifications.user_created(user)
{:ok, user}
end)
|> Repo.transaction()
end
def update_user_with_email(user_id, attrs, email_attrs) do
Ecto.Multi.new()
|> Ecto.Multi.update(:user, fn changes ->
user = Repo.get(User, user_id)
User.changeset(user, attrs)
end)
|> Ecto.Multi.run(:email, fn repo, %{user: user} ->
MyApp.Email.send_welcome(user.email)
{:ok, user}
end)
|> Repo.transaction()
end
endPhoenix with Absinthe GraphQL provides GraphQL API support with schema definition, resolvers, and subscriptions for real-time GraphQL queries.
- GraphQL Schema: Define types, queries, mutations
- Resolvers: Handle GraphQL queries
- Subscriptions: Real-time GraphQL updates
- Middleware: Authentication and authorization
- Complexity Analysis: Query complexity limits
# Phoenix with Absinthe GraphQL
defmodule MyAppWeb.Schema do
use Absinthe.Schema
import_types MyAppWeb.Types
query do
field :users, list_of(:user) do
resolve fn _parent, _args, _resolution ->
{:ok, MyApp.Repo.all(MyApp.User)}
end
end
field :user, :user do
arg :id, non_null(:id)
resolve fn %{id: id}, _resolution ->
{:ok, MyApp.Repo.get(MyApp.User, id)}
end
end
end
mutation do
field :create_user, :user do
arg :name, non_null(:string)
arg :email, non_null(:string)
arg :age, :integer
resolve fn %{name: name, email: email, age: age}, _resolution ->
case MyApp.Accounts.create_user(%{name: name, email: email, age: age}) do
{:ok, user} -> {:ok, user}
{:error, changeset} -> {:error, changeset}
end
end
end
end
subscription do
field :user_created, :user do
config fn _args, _resolution ->
{:ok, topic: "user_created"}
end
end
end
endPhoenix with Absinthe Subscriptions enables real-time GraphQL subscriptions with Phoenix PubSub, allowing clients to subscribe to data changes.
- GraphQL Subscriptions: Real-time data streaming
- PubSub Integration: Phoenix PubSub for subscriptions
- Topic Management: Manage subscription topics
- Broadcasting: Broadcast updates to subscribers
- Use Cases: Live data, notifications, real-time feeds
# Phoenix with Absinthe Subscriptions
defmodule MyAppWeb.Schema do
use Absinthe.Schema
subscription do
field :user_created, :user do
config fn _args, _resolution ->
{:ok, topic: "user_created"}
end
end
field :post_created, :post do
config fn _args, _resolution ->
{:ok, topic: "post_created"}
end
end
end
end
defmodule MyAppWeb.Resolvers do
def create_user(_parent, args, _resolution) do
case MyApp.Accounts.create_user(args) do
{:ok, user} ->
Absinthe.Subscription.publish(MyAppWeb.Endpoint, user, user_created: "user_created")
{:ok, user}
{:error, changeset} -> {:error, changeset}
end
end
end
defmodule MyAppWeb.UserSubscription do
use Absinthe.Subscription
def publish(doc_result, _args, _resolution) do
{:ok, doc_result}
end
endPhoenix Context is a module that encapsulates related functionality and data, providing a clear boundary between different parts of the application.
- Context Definition: Bounded context for domain logic
- Data Management: Handle data operations
- Business Logic: Encapsulate business rules
- Separation of Concerns: Clear module boundaries
- Testing: Easily test contexts
# Phoenix Context Example
defmodule MyApp.Accounts do
@moduledoc """
The Accounts context for user management
"""
alias MyApp.{Repo, User, Profile}
def get_user(id) do
Repo.get(User, id)
end
def get_user_by_email(email) do
Repo.get_by(User, email: email)
end
def list_users do
Repo.all(User)
end
def create_user(attrs \ %{}) do
%User{}
|> User.changeset(attrs)
|> Repo.insert()
end
def update_user(%User{} = user, attrs) do
user
|> User.changeset(attrs)
|> Repo.update()
end
def delete_user(%User{} = user) do
Repo.delete(user)
end
end
defmodule MyApp.Blog do
@moduledoc """
The Blog context for post management
"""
alias MyApp.{Repo, Post, Comment}
def list_posts do
Repo.all(Post)
end
def get_post(id) do
Repo.get(Post, id)
end
def create_post(attrs \ %{}) do
%Post{}
|> Post.changeset(attrs)
|> Repo.insert()
end
endPhoenix Context with Ecto combines domain logic with database operations, providing a clean separation between business rules and data access.
- Data Access: Ecto for database operations
- Business Logic: Encapsulated in contexts
- Preloading: Eager loading of associations
- Pagination: Page through large datasets
- Search: Full-text and partial search
# Phoenix Context with Ecto
defmodule MyApp.Accounts do
alias MyApp.{Repo, User, Profile}
def get_user_with_profile(id) do
query = from u in User,
where: u.id == ^id,
preload: [:profile]
Repo.one(query)
end
def get_user_with_posts(id) do
query = from u in User,
where: u.id == ^id,
preload: [posts: from(p in Post, order_by: [desc: p.inserted_at])]
Repo.one(query)
end
def get_user_with_comments(id) do
query = from u in User,
where: u.id == ^id,
preload: [comments: from(c in Comment, order_by: [desc: c.inserted_at])]
Repo.one(query)
end
def search_users(search_term) do
query = from u in User,
where: ilike(u.name, ^"%#{search_term}%") or ilike(u.email, ^"%#{search_term}%"),
order_by: u.name
Repo.all(query)
end
def paginate_users(page \ 1, page_size \ 10) do
query = from u in User, order_by: u.name
Repo.paginate(query, page: page, page_size: page_size)
end
endPhoenix with Cachex provides caching capabilities with TTL support, enabling efficient data caching and performance optimization.
- Cachex: Caching library for Elixir
- TTL: Time-to-live for cache entries
- Cache Operations: Get, put, delete, clear
- Cache Miss Handling: Compute on cache miss
- Use Cases: Query caching, API response caching
# Phoenix with Cachex
defmodule MyApp.Cache do
use Cachex
def start_link do
Cachex.start_link(:my_cache)
end
def get(key) do
Cachex.get(:my_cache, key)
end
def put(key, value, ttl \ 3600) do
Cachex.put(:my_cache, key, value, ttl: ttl)
end
def delete(key) do
Cachex.del(:my_cache, key)
end
def clear do
Cachex.clear(:my_cache)
end
def exists?(key) do
Cachex.exists?(:my_cache, key)
end
def get_or_compute(key, function, ttl \ 3600) do
case Cachex.get(:my_cache, key) do
{:ok, value} when not is_nil(value) -> value
_ ->
value = function.()
put(key, value, ttl)
value
end
end
end
# Usage
MyApp.Cache.put("user:1", %{name: "Alice", age: 25})
user = MyApp.Cache.get("user:1")
IO.puts("User: #{inspect(user)}")Phoenix with Rate Limiting provides request throttling to prevent abuse and ensure fair usage, with configurable limits and periods.
- Hammer: Rate limiting library
- Limit Configuration: Requests per time period
- Rate Limiting Plug: Plug for rate limiting
- Headers: Rate limit response headers
- Use Cases: API protection, DDoS prevention
# Phoenix with Rate Limiting
defmodule MyApp.RateLimiter do
use Hammer
def start_link do
Hammer.start_link(backend: :ets)
end
def check_rate_limit(key, limit \ 60, period \ 3600) do
case Hammer.check_rate(key, limit, period) do
{:allow, count} ->
{:ok, count}
{:deny, count} ->
{:error, "Rate limit exceeded. Limit: #{limit}, Count: #{count}"}
end
end
def get_current_count(key) do
Hammer.get_rate(key)
end
def reset_rate_limit(key) do
Hammer.delete_rate(key)
end
end
defmodule MyAppWeb.RateLimiterPlug do
import Plug.Conn
def init(options), do: options
def call(conn, opts) do
key = "api:#{conn.remote_ip}"
limit = Keyword.get(opts, :limit, 60)
period = Keyword.get(opts, :period, 3600)
case MyApp.RateLimiter.check_rate_limit(key, limit, period) do
{:ok, count} ->
conn
|> put_resp_header("x-ratelimit-limit", to_string(limit))
|> put_resp_header("x-ratelimit-remaining", to_string(limit - count))
{:error, message} ->
conn
|> send_resp(429, Jason.encode!(%{error: message}))
|> halt()
end
end
endPhoenix with File Upload enables handling file uploads with validation, storage, and streaming support for large files.
- File Upload: Handle multipart file uploads
- Storage: Store files on disk or cloud
- Validation: File size, type, and content validation
- Streaming: Stream large files efficiently
- Security: Validate file content securely
# Phoenix with File Upload
defmodule MyAppWeb.UploadController do
use MyAppWeb, :controller
def upload(conn, %{"upload" => upload}) do
case MyApp.Uploader.store(upload) do
{:ok, path} ->
json(conn, %{success: true, path: path})
{:error, reason} ->
conn |> put_status(400) |> json(%{error: reason})
end
end
def upload_multiple(conn, %{"uploads" => uploads}) do
results = Enum.map(uploads, fn upload ->
MyApp.Uploader.store(upload)
end)
json(conn, %{results: results})
end
end
defmodule MyApp.Uploader do
def store(upload) do
path = Path.join(["uploads", upload.filename])
case File.write(path, upload.content) do
:ok -> {:ok, path}
{:error, reason} -> {:error, reason}
end
end
def stream_upload(upload, chunk_size \ 1024) do
path = Path.join(["uploads", upload.filename])
File.open(path, [:write], fn file ->
Enum.each(upload.stream, fn chunk ->
IO.binwrite(file, chunk)
end)
end)
end
endPhoenix with Email Sending provides email capabilities with templates, attachments, and delivery through various email providers.
- Bamboo: Email sending library
- Email Templates: HTML and text templates
- Delivery: SMTP, SendGrid, Mailgun, etc.
- Attachments: Include file attachments
- Background Sending: Send emails asynchronously
# Phoenix with Email Sending
defmodule MyApp.Mailer do
use Bamboo.Mailer, otp_app: :my_app
end
defmodule MyApp.Email do
import Bamboo.Email
def welcome_email(user) do
new_email()
|> to(user.email)
|> from("no-reply@myapp.com")
|> subject("Welcome to MyApp!")
|> text_body("Hello #{user.name}, welcome to MyApp!")
|> html_body("<h1>Hello #{user.name}</h1><p>Welcome to MyApp!</p>")
end
def reset_password_email(user, token) do
new_email()
|> to(user.email)
|> from("no-reply@myapp.com")
|> subject("Reset your password")
|> text_body("Click the link to reset your password: /reset?token=#{token}")
|> html_body("<a href='/reset?token=#{token}'>Reset Password</a>")
end
def notification_email(user, message) do
new_email()
|> to(user.email)
|> from("notifications@myapp.com")
|> subject("New Notification")
|> text_body(message)
|> html_body("<p>#{message}</p>")
end
end
# Usage
user = %{name: "Alice", email: "alice@email.com"}
email = MyApp.Email.welcome_email(user)
MyApp.Mailer.deliver_now(email)Phoenix with Background Jobs enables processing long-running tasks asynchronously with job scheduling, retries, and error handling.
- Oban: Background job processing library
- Job Scheduling: Schedule jobs with cron
- Retries: Automatic retry on failure
- Queues: Prioritize jobs with multiple queues
- Monitoring: Track job status and performance
# Phoenix with Background Jobs
defmodule MyApp.Worker do
use Oban.Worker
def perform(%Oban.Job{args: args}) do
IO.puts("Processing job: #{inspect(args)}")
# Do work
:ok
end
end
defmodule MyApp.Scheduler do
use Oban.Worker
@impl Oban.Worker
def perform(%Oban.Job{args: %{"schedule" => schedule}}) do
# Schedule work
:ok
end
end
defmodule MyApp.Jobs do
alias MyApp.Repo
def process_user(user_id) do
%{user_id: user_id}
|> MyApp.Worker.new()
|> Oban.insert()
end
def schedule_cleanup(days \ 30) do
%{days: days}
|> MyApp.Scheduler.new(schedule: "0 0 * * *")
|> Oban.insert()
end
def process_bulk(users) do
Enum.each(users, fn user ->
process_user(user.id)
end)
end
end
# Oban configuration
defmodule MyApp.Application do
use Application
def start(_type, _args) do
children = [
{Oban, oban_config()}
]
Supervisor.start_link(children, strategy: :one_for_one)
end
defp oban_config do
[
repo: MyApp.Repo,
plugins: [Oban.Plugins.Pruner],
queues: [
default: 10,
mailers: 5,
heavy: 2
]
]
end
endPhoenix with CORS enables Cross-Origin Resource Sharing with configurable origins, methods, and headers for API security.
- CORS Plug: Handle CORS requests
- Origin Configuration: Allow specific origins
- Methods: Allow specific HTTP methods
- Headers: Allow specific headers
- Preflight: Handle OPTIONS preflight requests
# Phoenix with CORS
defmodule MyAppWeb.CORSPlug do
import Plug.Conn
def init(options), do: options
def call(conn, _opts) do
conn
|> put_resp_header("access-control-allow-origin", "*")
|> put_resp_header("access-control-allow-methods", "GET, POST, PUT, DELETE, OPTIONS")
|> put_resp_header("access-control-allow-headers", "Content-Type, Authorization")
|> put_resp_header("access-control-max-age", "86400")
|> handle_preflight()
end
defp handle_preflight(conn) do
if conn.method == "OPTIONS" do
conn
|> send_resp(204, "")
|> halt()
else
conn
end
end
end
defmodule MyAppWeb.Router do
use MyAppWeb, :router
pipeline :api do
plug MyAppWeb.CORSPlug
plug :accepts, ["json"]
end
scope "/api", MyAppWeb do
pipe_through :api
resources "/users", UserController
end
endPhoenix with JSON Web Tokens provides authentication and authorization using JWT tokens with built-in token generation and verification.
- JWT: JSON Web Token authentication
- Token Generation: Create JWT tokens
- Token Verification: Verify and decode tokens
- Authentication Plug: Authenticate requests with JWT
- Refresh Tokens: Token refresh mechanism
# Phoenix with JSON Web Tokens
defmodule MyApp.Auth do
import Bcrypt
def generate_token(user) do
{:ok, token, _claims} = JWT.encode(%{user_id: user.id, exp: exp_time()}, secret_key())
token
end
def verify_token(token) do
case JWT.decode(token, secret_key()) do
{:ok, claims} ->
{:ok, claims}
{:error, reason} ->
{:error, reason}
end
end
def authenticate(email, password) do
case MyApp.Accounts.get_user_by_email(email) do
nil -> {:error, "Invalid email or password"}
user ->
if verify_password(password, user.password_hash) do
{:ok, user}
else
{:error, "Invalid email or password"}
end
end
end
defp secret_key do
Application.get_env(:my_app, :secret_key_base)
end
defp exp_time do
System.system_time(:second) + 3600
end
end
defmodule MyAppWeb.AuthPlug do
import Plug.Conn
def init(options), do: options
def call(conn, _opts) do
case get_req_header(conn, "authorization") do
["Bearer " <> token] ->
case MyApp.Auth.verify_token(token) do
{:ok, claims} ->
user = MyApp.Accounts.get_user(claims["user_id"])
assign(conn, :current_user, user)
{:error, _} ->
conn |> send_resp(401, "Unauthorized") |> halt()
end
_ ->
conn |> send_resp(401, "Unauthorized") |> halt()
end
end
endPhoenix with Websockets provides WebSocket support with channels for real-time communication, enabling bidirectional message passing.
- WebSocket: Persistent bidirectional connection
- Channels: Group WebSocket connections
- PubSub: Broadcast messages to channels
- Client-side Integration: Phoenix client for JavaScript
- Authentication: Socket authentication
# Phoenix with Websockets
defmodule MyAppWeb.RoomChannel do
use Phoenix.Channel
alias MyAppWeb.Presence
def join("room:" <> room_id, _params, socket) do
{:ok, assign(socket, :room_id, room_id)}
end
def handle_in("new_msg", %{"body" => body}, socket) do
broadcast!(socket, "new_msg", %{body: body, user: socket.assigns.user_id})
{:noreply, socket}
end
def handle_in("typing", %{"typing" => typing}, socket) do
broadcast!(socket, "typing", %{user: socket.assigns.user_id, typing: typing})
{:noreply, socket}
end
def handle_out("new_msg", payload, socket) do
push(socket, "new_msg", payload)
{:noreply, socket}
end
end
# Client-side JavaScript
# let socket = new Phoenix.Socket("/socket", {params: {token: userToken}})
# socket.connect()
# let channel = socket.channel("room:lobby", {})
# channel.join()
# .receive("ok", resp => { console.log("Joined successfully", resp) })
# .receive("error", resp => { console.log("Unable to join", resp) })
# channel.on("new_msg", payload => {
# console.log("New message:", payload.body)
# })
# channel.push("new_msg", {body: "Hello!"})Phoenix LiveView Forms enables building interactive forms with real-time validation and error handling using Ecto changesets.
- Form Building:
form_forandtext_input - Real-time Validation: Validate on input change
- Error Display: Show errors inline
- Ecto Integration: Changeset-based forms
- Nested Forms: Handle nested data structures
# Phoenix with LiveView Forms
defmodule MyAppWeb.UserFormLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, changeset: User.changeset(%User{}, %{}))}
end
def render(assigns) do
~H"""
<div>
<.form let={f} for={@changeset} phx-submit="save" phx-change="validate">
<div>
<%= label(f, :name) %>
<%= text_input(f, :name) %>
<%= error_tag(f, :name) %>
</div>
<div>
<%= label(f, :email) %>
<%= email_input(f, :email) %>
<%= error_tag(f, :email) %>
</div>
<div>
<%= label(f, :age) %>
<%= number_input(f, :age) %>
<%= error_tag(f, :age) %>
</div>
<div>
<%= submit("Save") %>
</div>
</.form>
</div>
"""
end
def handle_event("validate", %{"user" => user_params}, socket) do
changeset = User.changeset(%User{}, user_params)
{:noreply, assign(socket, changeset: changeset)}
end
def handle_event("save", %{"user" => user_params}, socket) do
case MyApp.Accounts.create_user(user_params) do
{:ok, user} ->
{:noreply, socket |> put_flash(:info, "User created")}
{:error, changeset} ->
{:noreply, assign(socket, changeset: changeset)}
end
end
endPhoenix LiveView Pagination enables handling large datasets with pagination controls and efficient data loading.
- Pagination Controls: Next/Previous buttons
- Page Size: Configurable items per page
- Efficient Loading: Load only visible data
- Database Pagination: Ecto pagination with offset/limit
- State Management: Track current page
# Phoenix with LiveView Pagination
defmodule MyAppWeb.UsersLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, page: 1, users: [], total: 0)}
end
def render(assigns) do
~H"""
<div>
<h2>Users</h2>
<table>
<thead>
<tr><th>Name</th><th>Email</th></tr>
</thead>
<tbody>
<%= for user <- @users do %>
<tr><td><%= user.name %></td><td><%= user.email %></td></tr>
<% end %>
</tbody>
</table>
<div>
<%= if @page > 1 do %>
<button phx-click="prev">Previous</button>
<% end %>
<span>Page <%= @page %></span>
<%= if @page * 10 < @total do %>
<button phx-click="next">Next</button>
<% end %>
</div>
</div>
"""
end
def handle_event("next", _params, socket) do
{:noreply, load_page(socket, socket.assigns.page + 1)}
end
def handle_event("prev", _params, socket) do
{:noreply, load_page(socket, socket.assigns.page - 1)}
end
defp load_page(socket, page) do
{users, total} = MyApp.Accounts.paginate_users(page)
assign(socket, page: page, users: users, total: total)
end
endPhoenix LiveView Search provides real-time search functionality with debouncing and efficient result filtering.
- Real-time Search: Search as you type
- Debouncing: Prevent excessive searches
- Efficient Filtering: Database search with ILIKE
- Results Display: Show search results dynamically
- Search State: Track search query and results
# Phoenix with LiveView Search
defmodule MyAppWeb.SearchLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, query: "", results: [])}
end
def render(assigns) do
~H"""
<div>
<h2>Search</h2>
<form phx-submit="search">
<input type="text" name="query" value={@query} phx-change="update_query" />
<button type="submit">Search</button>
</form>
<ul>
<%= for result <- @results do %>
<li><%= result.name %> - <%= result.email %></li>
<% end %>
</ul>
</div>
"""
end
def handle_event("update_query", %{"query" => query}, socket) do
{:noreply, assign(socket, query: query)}
end
def handle_event("search", _params, socket) do
results = MyApp.Accounts.search_users(socket.assigns.query)
{:noreply, assign(socket, results: results)}
end
endPhoenix LiveView Notifications provides real-time notification delivery with PubSub integration and toast notifications.
- PubSub Integration: Phoenix PubSub for notifications
- Real-time Delivery: Instant notification display
- Toast Notifications: Popup notifications
- Notification State: Track unread notifications
- Mark as Read: Handle notification interactions
# Phoenix with LiveView Notifications
defmodule MyAppWeb.NotificationsLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
if connected?(socket) do
Phoenix.PubSub.subscribe(MyApp.PubSub, "notifications")
end
{:ok, assign(socket, notifications: [])}
end
def render(assigns) do
~H"""
<div>
<h2>Notifications</h2>
<ul>
<%= for notification <- @notifications do %>
<li><%= notification %></li>
<% end %>
</ul>
</div>
"""
end
def handle_info({:notification, message}, socket) do
{:noreply, update(socket, :notifications, fn msgs -> [message | msgs] end)}
end
end
defmodule MyApp.Notifications do
def notify(message) do
Phoenix.PubSub.broadcast(MyApp.PubSub, "notifications", {:notification, message})
end
endPhoenix LiveView Components are reusable UI building blocks that encapsulate rendering logic and state for consistent user interfaces.
- Components: Reusable UI elements
- Slots: Content placeholder for components
- Event Handling: Handle events within components
- State Management: Component-specific state
- Composition: Compose complex UIs from components
# Phoenix with LiveView Components
defmodule MyAppWeb.Components do
use Phoenix.Component
def button(assigns) do
~H"""
<button class="btn btn-primary" phx-click={@click}>
<%= @label %>
</button>
"""
end
def card(assigns) do
~H"""
<div class="card">
<div class="card-header">
<%= @title %>
</div>
<div class="card-body">
<%= render_slot(@inner_block) %>
</div>
</div>
"""
end
def modal(assigns) do
~H"""
<div class="modal" style="display: #{if @open, do: 'block', else: 'none'}">
<div class="modal-content">
<div class="modal-header">
<h3><%= @title %></h3>
<button phx-click={@close}>Close</button>
</div>
<div class="modal-body">
<%= render_slot(@inner_block) %>
</div>
</div>
</div>
"""
end
end
# Usage in LiveView
defmodule MyAppWeb.DemoLive do
use MyAppWeb, :live_view
import MyAppWeb.Components
def render(assigns) do
~H"""
<div>
<.button click="increment" label="Increment" />
<.card title="User Info">
<p>Name: <%= @user.name %></p>
<p>Email: <%= @user.email %></p>
</.card>
<.modal open={@show_modal} title="Edit User" close="close_modal">
<form phx-submit="save_user">
<input type="text" name="name" value={@user.name} />
<input type="email" name="email" value={@user.email} />
<button type="submit">Save</button>
</form>
</.modal>
</div>
"""
end
endPhoenix LiveView Slots enable component composition by providing placeholder content that can be filled by the parent component.
- Named Slots: Named content placeholders
- Render Slots:
render_slotfunction - Default Slot:
@inner_blockfor default content - Component Composition: Build complex UIs
- Template Inheritance: Reusable layouts
# Phoenix with LiveView Slots
defmodule MyAppWeb.TableComponent do
use Phoenix.Component
slot :header
slot :row, required: true
slot :footer
def table(assigns) do
~H"""
<table class="table">
<thead>
<tr>
<%= for header <- @header do %>
<th><%= render_slot(header) %></th>
<% end %>
</tr>
</thead>
<tbody>
<%= for row <- @row do %>
<tr><%= render_slot(row) %></tr>
<% end %>
</tbody>
<tfoot>
<%= render_slot(@footer) %>
</tfoot>
</table>
"""
end
end
defmodule MyAppWeb.UsersLive do
use MyAppWeb, :live_view
import MyAppWeb.TableComponent
def render(assigns) do
~H"""
<.table>
<:header>Name</:header>
<:header>Email</:header>
<:header>Age</:header>
<%= for user <- @users do %>
<:row>
<td><%= user.name %></td>
<td><%= user.email %></td>
<td><%= user.age %></td>
</:row>
<% end %>
<:footer>
<tr><td colspan="3">Total: <%= length(@users) %> users</td></tr>
</:footer>
</.table>
"""
end
endPhoenix LiveView Upload enables file uploads with progress tracking and real-time feedback for large file uploads.
- File Upload: Handle file uploads in LiveView
- Progress Tracking: Show upload progress
- File Validation: Validate file types and sizes
- Multiple Files: Upload multiple files
- Storage: Store files on disk or cloud
# Phoenix with LiveView Upload
defmodule MyAppWeb.UploadLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, uploads: [])}
end
def render(assigns) do
~H"""
<div>
<h2>File Upload</h2>
<form phx-submit="upload" phx-change="validate">
<input type="file" name="file" accept=".jpg,.png,.pdf" multiple />
<button type="submit">Upload</button>
</form>
<ul>
<%= for upload <- @uploads do %>
<li><%= upload.filename %> - <%= upload.size %> bytes</li>
<% end %>
</ul>
</div>
"""
end
def handle_event("validate", %{"file" => file}, socket) do
{:noreply, assign(socket, uploads: [file])}
end
def handle_event("upload", %{"file" => file}, socket) do
case MyApp.Uploader.store(file) do
{:ok, path} ->
uploads = [%{filename: file.filename, path: path} | socket.assigns.uploads]
{:noreply, assign(socket, uploads: uploads)}
{:error, reason} ->
{:noreply, put_flash(socket, :error, reason)}
end
end
endPhoenix LiveView Charts enables creating interactive charts and visualizations with LiveView and JavaScript hooks.
- Chart Integration: JavaScript libraries like Chart.js
- LiveView Hooks:
phx-hookfor client-side integration - Real-time Updates: Update charts on data change
- Data Binding: Bind LiveView data to charts
- Interactive Charts: User interaction with charts
# Phoenix with LiveView Charts
defmodule MyAppWeb.ChartLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, data: generate_data())}
end
def render(assigns) do
~H"""
<div>
<h2>Chart</h2>
<div id="chart" phx-hook="Chart" data-data={inspect(@data)}>
<canvas id="chart-canvas"></canvas>
</div>
<button phx-click="refresh">Refresh</button>
</div>
"""
end
def handle_event("refresh", _params, socket) do
{:noreply, assign(socket, data: generate_data())}
end
defp generate_data do
Enum.map(1..10, fn i ->
%{label: "Item #{i}", value: :rand.uniform(100)}
end)
end
end
# JavaScript hook
# const Chart = {
# mounted() {
# this.drawChart(this.el.dataset.data)
# },
# updated() {
# this.drawChart(this.el.dataset.data)
# },
# drawChart(data) {
# const parsedData = JSON.parse(data)
# // Draw chart using Chart.js or other library
# }
# }
# window.Chart = ChartPhoenix LiveView Maps enables integrating interactive maps with LiveView for location-based features and real-time updates.
- Map Integration: Leaflet, Mapbox, Google Maps
- LiveView Hooks:
phx-hookfor map integration - Markers: Add and update map markers
- Real-time Updates: Update maps on data change
- Interactive Maps: Click, drag, zoom interactions
# Phoenix with LiveView Maps
defmodule MyAppWeb.MapLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, markers: [], center: %{lat: 0, lng: 0})}
end
def render(assigns) do
~H"""
<div>
<h2>Map</h2>
<div id="map" phx-hook="Map" data-markers={inspect(@markers)} data-center={inspect(@center)}>
<div id="map-container" style="height: 400px;"></div>
</div>
<button phx-click="add_marker">Add Marker</button>
</div>
"""
end
def handle_event("add_marker", _params, socket) do
markers = [%{lat: :rand.uniform(180) - 90, lng: :rand.uniform(360) - 180} | socket.assigns.markers]
{:noreply, assign(socket, markers: markers)}
end
def handle_event("map_click", %{"lat" => lat, "lng" => lng}, socket) do
markers = [%{lat: lat, lng: lng} | socket.assigns.markers]
{:noreply, assign(socket, markers: markers)}
end
endPhoenix LiveView Charts enables creating interactive charts and visualizations with real-time data updates and client-side rendering.
- Chart Libraries: Chart.js, Nivo, Recharts
- LiveView Hooks:
phx-hookfor chart integration - Real-time Data: Update charts with LiveView state
- Interactive Charts: Hover, click, zoom interactions
- Data Visualization: Present data visually
# Phoenix with LiveView Charts
defmodule MyAppWeb.ChartLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, data: generate_data())}
end
def render(assigns) do
~H"""
<div>
<h2>Chart</h2>
<div id="chart" phx-hook="Chart" data-data={inspect(@data)}>
<canvas id="chart-canvas"></canvas>
</div>
<button phx-click="refresh">Refresh</button>
</div>
"""
end
def handle_event("refresh", _params, socket) do
{:noreply, assign(socket, data: generate_data())}
end
defp generate_data do
Enum.map(1..10, fn i ->
%{label: "Item #{i}", value: :rand.uniform(100)}
end)
end
endPhoenix LiveView Stream enables efficient streaming of data with lazy loading and pagination for large datasets.
- Stream Data: Process data in chunks
- Lazy Loading: Load data on demand
- Memory Efficiency: Process large datasets
- Use Cases: Infinite scroll, large reports
- Data Processing: Transform data on stream
# Phoenix with LiveView Stream
defmodule MyAppWeb.StreamLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
stream = 1..1000
|> Stream.map(& &1)
|> Stream.chunk_every(10)
{:ok, assign(socket, stream: stream, items: [])}
end
def render(assigns) do
~H"""
<div>
<h2>Stream</h2>
<ul>
<%= for item <- @items do %>
<li><%= item %></li>
<% end %>
</ul>
<button phx-click="load_more">Load More</button>
</div>
"""
end
def handle_event("load_more", _params, socket) do
{items, stream} = Enum.split(socket.assigns.stream, 10)
{:noreply, assign(socket, items: items, stream: stream)}
end
endPhoenix LiveView Infinite Scroll provides infinite scrolling with lazy loading and efficient data fetching.
- Infinite Scroll: Load more data on scroll
- Lazy Loading: Load data as needed
- Scroll Detection: Detect scroll end
- Loading State: Show loading indicators
- Performance: Efficient data loading
# Phoenix with LiveView Infinite Scroll
defmodule MyAppWeb.InfiniteScrollLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
items = 1..20 |> Enum.to_list()
{:ok, assign(socket, items: items, loading: false, page: 1)}
end
def render(assigns) do
~H"""
<div id="infinite-scroll" phx-hook="InfiniteScroll">
<ul>
<%= for item <- @items do %>
<li><%= item %></li>
<% end %>
</ul>
<div id="loader" class={if @loading, do: "visible", else: "hidden"}>
Loading...
</div>
</div>
"""
end
def handle_event("load_more", _params, socket) do
{:noreply, load_more(socket)}
end
defp load_more(socket) do
page = socket.assigns.page + 1
new_items = Enum.map((page - 1) * 20 + 1..page * 20, & &1)
items = socket.assigns.items ++ new_items
assign(socket, items: items, page: page, loading: false)
end
endPhoenix LiveView Drag and Drop enables drag-and-drop interactions with real-time reordering and state updates.
- Drag and Drop: HTML5 drag and drop API
- Real-time Updates: Update LiveView state on drag
- Reordering: Drag to reorder items
- LiveView Hooks:
phx-hookfor drag events - Visual Feedback: Show drag indicators
# Phoenix with LiveView Drag and Drop
defmodule MyAppWeb.DragDropLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
items = 1..5 |> Enum.map(fn i -> %{id: i, text: "Item #{i}"} end)
{:ok, assign(socket, items: items)}
end
def render(assigns) do
~H"""
<div>
<h2>Drag and Drop</h2>
<ul id="drag-drop" phx-hook="DragDrop">
<%= for item <- @items do %>
<li data-id={item.id} draggable="true" phx-value-id={item.id}>
<%= item.text %>
</li>
<% end %>
</ul>
</div>
"""
end
def handle_event("drop", %{"from" => from_id, "to" => to_id}, socket) do
items = reorder_items(socket.assigns.items, from_id, to_id)
{:noreply, assign(socket, items: items)}
end
defp reorder_items(items, from_id, to_id) do
from_idx = Enum.find_index(items, &(&1.id == from_id))
to_idx = Enum.find_index(items, &(&1.id == to_id))
items
|> List.delete_at(from_idx)
|> List.insert_at(to_idx, Enum.at(items, from_idx))
end
endPhoenix LiveView Authentication provides user authentication with login, registration, and session management.
- Login/Logout: User authentication flows
- Session Management: Track user sessions
- Protected Routes: Require authentication
- Authentication Hooks:
on_mountfor auth - Current User: Access current user in LiveView
# Phoenix with LiveView Authentication
defmodule MyAppWeb.AuthLive do
use Phoenix.LiveView
def mount(_params, session, socket) do
{:ok, assign(socket, current_user: session["current_user"])}
end
def render(assigns) do
~H"""
<div>
<%= if @current_user do %>
<div>Welcome, <%= @current_user.name %>!</div>
<button phx-click="logout">Logout</button>
<% else %>
<form phx-submit="login">
<input type="email" name="email" placeholder="Email" />
<input type="password" name="password" placeholder="Password" />
<button type="submit">Login</button>
</form>
<a href="/register">Register</a>
<% end %>
</div>
"""
end
def handle_event("login", %{"email" => email, "password" => password}, socket) do
case MyApp.Auth.authenticate(email, password) do
{:ok, user} ->
{:noreply, assign(socket, current_user: user)}
{:error, _} ->
{:noreply, put_flash(socket, :error, "Invalid email or password")}
end
end
def handle_event("logout", _params, socket) do
{:noreply, assign(socket, current_user: nil)}
end
endPhoenix LiveView Authorization enables role-based access control with permission checks and authorization hooks.
- Role-based Access: Admin, user, guest roles
- Authorization Hooks:
on_mountfor permissions - Protected Views: Restrict access by role
- Conditional Rendering: Show/hide content by permission
- Use Cases: Admin panels, user-specific features
# Phoenix with LiveView Authorization
defmodule MyAppWeb.Auth do
import Phoenix.LiveView
def on_mount(:current_user, _params, session, socket) do
{:cont, assign(socket, current_user: session["current_user"])}
end
def on_mount(:require_user, _params, _session, socket) do
if socket.assigns.current_user do
{:cont, socket}
else
{:halt, redirect(socket, to: "/login")}
end
end
def on_mount(:require_admin, _params, _session, socket) do
if socket.assigns.current_user && socket.assigns.current_user.is_admin do
{:cont, socket}
else
{:halt, redirect(socket, to: "/unauthorized")}
end
end
end
defmodule MyAppWeb.Router do
use MyAppWeb, :router
live_session :authenticated, on_mount: {MyAppWeb.Auth, :require_user} do
live "/dashboard", DashboardLive
live "/profile", ProfileLive
end
live_session :admin, on_mount: {MyAppWeb.Auth, :require_admin} do
live "/admin", AdminLive
live "/admin/users", AdminUsersLive
end
endPhoenix LiveView Charts enables creating interactive charts and visualizations with real-time data updates.
- Chart Integration: Chart.js, ECharts, Nivo
- LiveView Hooks:
phx-hookfor chart rendering - Real-time Updates: Update charts on data change
- Interactive Charts: Hover, click, zoom
- Data Binding: Bind LiveView data to charts
# Phoenix with LiveView Charts
defmodule MyAppWeb.ChartLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, data: generate_data())}
end
def render(assigns) do
~H"""
<div>
<h2>Chart</h2>
<div id="chart" phx-hook="Chart" data-data={inspect(@data)}>
<canvas id="chart-canvas"></canvas>
</div>
<button phx-click="refresh">Refresh</button>
</div>
"""
end
def handle_event("refresh", _params, socket) do
{:noreply, assign(socket, data: generate_data())}
end
defp generate_data do
Enum.map(1..10, fn i ->
%{label: "Item #{i}", value: :rand.uniform(100)}
end)
end
endPhoenix LiveView Calendar provides calendar views with event management and navigation through months.
- Calendar Views: Monthly, weekly, daily views
- Event Management: Add, edit, delete events
- Navigation: Previous/next month navigation
- Click Events: Click on dates for events
- Real-time Updates: Update calendar on event changes
# Phoenix with LiveView Calendar
defmodule MyAppWeb.CalendarLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
today = Date.utc_today()
{:ok, assign(socket, date: today, events: [])}
end
def render(assigns) do
~H"""
<div>
<h2>Calendar</h2>
<div>
<button phx-click="prev_month">‹</button>
<span><%= Calendar.strftime(@date, "%B %Y") %></span>
<button phx-click="next_month">›</button>
</div>
<table>
<thead>
<tr>
<th>Mon</th><th>Tue</th><th>Wed</th>
<th>Thu</th><th>Fri</th><th>Sat</th><th>Sun</th>
</tr>
</thead>
<tbody>
<%= for week <- calendar(@date) do %>
<tr>
<%= for day <- week do %>
<td class={if day, do: "day", else: "empty"}>
<%= if day do %>
<span phx-click="select_date" phx-value-day={day}><%= day %></span>
<% end %>
</td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
</div>
"""
end
defp calendar(date) do
# Generate calendar grid
# Implementation omitted for brevity
[]
end
def handle_event("prev_month", _params, socket) do
date = Date.add(socket.assigns.date, -30)
{:noreply, assign(socket, date: date)}
end
def handle_event("next_month", _params, socket) do
date = Date.add(socket.assigns.date, 30)
{:noreply, assign(socket, date: date)}
end
endPhoenix LiveView PDF Generation enables generating PDF documents from LiveView content with server-side rendering.
- PDF Generation: Generate PDF from HTML
- LiveView Rendering: Render LiveView to HTML
- Server-side Generation: Generate PDF on server
- Download: Download generated PDF
- Use Cases: Invoices, reports, documents
# Phoenix with LiveView PDF Generation
defmodule MyAppWeb.PDFLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, data: generate_data())}
end
def render(assigns) do
~H"""
<div>
<h2>PDF Generator</h2>
<button phx-click="generate_pdf">Generate PDF</button>
<div id="pdf-preview">
<h3>Preview</h3>
<table>
<thead>
<tr><th>Name</th><th>Value</th></tr>
</thead>
<tbody>
<%= for item <- @data do %>
<tr><td><%= item.name %></td><td><%= item.value %></td></tr>
<% end %>
</tbody>
</table>
</div>
</div>
"""
end
def handle_event("generate_pdf", _params, socket) do
html = render_to_string(socket)
case MyApp.PDFGenerator.generate(html) do
{:ok, pdf} ->
{:noreply, push_event(socket, "download_pdf", %{data: Base.encode64(pdf)})}
{:error, reason} ->
{:noreply, put_flash(socket, :error, reason)}
end
end
defp render_to_string(socket) do
# Render LiveView to HTML string
# Implementation omitted for brevity
"<html>...</html>"
end
endPhoenix LiveView Export enables exporting data in various formats like CSV, JSON, and XML with user interface controls.
- Export Formats: CSV, JSON, XML, Excel
- Format Selection: Choose export format
- Data Processing: Transform data for export
- Download: Download exported files
- Progress Tracking: Show export progress
# Phoenix with LiveView Export
defmodule MyAppWeb.ExportLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, data: [], format: "csv", exporting: false)}
end
def render(assigns) do
~H"""
<div>
<h2>Export Data</h2>
<div>
<label>Format:</label>
<select phx-change="format" phx-value-format={@format}>
<option value="csv">CSV</option>
<option value="json">JSON</option>
<option value="xml">XML</option>
</select>
</div>
<button phx-click="export" disabled={@exporting}>
<%= if @exporting do %>
Exporting...
<% else %>
Export
<% end %>
</button>
<div>
<%= if @exporting do %>
<div class="progress">Processing...</div>
<% end %>
</div>
</div>
"""
end
def handle_event("format", %{"format" => format}, socket) do
{:noreply, assign(socket, format: format)}
end
def handle_event("export", _params, socket) do
{:noreply, assign(socket, exporting: true)}
end
endPhoenix LiveView Import provides data import functionality with file upload, validation, and processing feedback.
- File Upload: Upload import files
- Validation: Validate import data
- Processing: Process imported data
- Feedback: Show import results and errors
- Progress Tracking: Show import progress
# Phoenix with LiveView Import
defmodule MyAppWeb.ImportLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, file: nil, importing: false, results: nil)}
end
def render(assigns) do
~H"""
<div>
<h2>Import Data</h2>
<form phx-submit="import" phx-change="validate">
<input type="file" name="file" accept=".csv,.json" />
<button type="submit" disabled={@importing}>
<%= if @importing do %>
Importing...
<% else %>
Import
<% end %>
</button>
</form>
<%= if @results do %>
<div class="results">
<h3>Import Results</h3>
<p>Imported: <%= @results.imported %></p>
<p>Failed: <%= @results.failed %></p>
<ul>
<%= for error <- @results.errors do %>
<li><%= error %></li>
<% end %>
</ul>
</div>
<% end %>
</div>
"""
end
def handle_event("validate", %{"file" => file}, socket) do
{:noreply, assign(socket, file: file)}
end
def handle_event("import", _params, socket) do
{:noreply, assign(socket, importing: true)}
end
endPhoenix LiveView Form Validation provides real-time form validation with Ecto changesets and inline error messages.
- Real-time Validation: Validate on input change
- Changeset Integration: Use Ecto changesets
- Error Display: Show inline errors
- Field Errors: Display errors per field
- Form Submission: Validate on submit
# Phoenix with LiveView Form Validation
defmodule MyAppWeb.FormLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
changeset = User.changeset(%User{}, %{})
{:ok, assign(socket, changeset: changeset, submitted: false)}
end
def render(assigns) do
~H"""
<div>
<h2>Form</h2>
<.form let={f} for={@changeset} phx-submit="save" phx-change="validate">
<div>
<label>Name</label>
<input name="user[name]" value={get_change(f, :name)} />
<div class="error"><%= error(f, :name) %></div>
</div>
<div>
<label>Email</label>
<input name="user[email]" type="email" value={get_change(f, :email)} />
<div class="error"><%= error(f, :email) %></div>
</div>
<div>
<label>Age</label>
<input name="user[age]" type="number" value={get_change(f, :age)} />
<div class="error"><%= error(f, :age) %></div>
</div>
<button type="submit">Submit</button>
</.form>
</div>
"""
end
def handle_event("validate", %{"user" => user_params}, socket) do
changeset = User.changeset(%User{}, user_params)
{:noreply, assign(socket, changeset: changeset)}
end
def handle_event("save", %{"user" => user_params}, socket) do
case MyApp.Accounts.create_user(user_params) do
{:ok, user} ->
{:noreply, assign(socket, submitted: true)}
{:error, changeset} ->
{:noreply, assign(socket, changeset: changeset)}
end
end
endPhoenix LiveView Multi-step Form enables creating multi-step forms with navigation, data persistence, and validation across steps.
- Step Navigation: Next/Previous navigation
- Data Persistence: Preserve data across steps
- Step Validation: Validate each step
- Progress Indicator: Show current step
- Review Step: Review before submission
# Phoenix with LiveView Multi-step Form
defmodule MyAppWeb.MultiStepFormLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, step: 1, data: %{})}
end
def render(assigns) do
~H"""
<div>
<h2>Multi-step Form</h2>
<div class="steps">
<div class={if @step >= 1, do: "active"}>Step 1</div>
<div class={if @step >= 2, do: "active"}>Step 2</div>
<div class={if @step >= 3, do: "active"}>Step 3</div>
</div>
<form phx-submit="next_step">
<%= if @step == 1 do %>
<h3>Personal Information</h3>
<input name="name" placeholder="Name" value={@data[:name]} />
<input name="email" placeholder="Email" value={@data[:email]} />
<% end %>
<%= if @step == 2 do %>
<h3>Address</h3>
<input name="address" placeholder="Address" value={@data[:address]} />
<input name="city" placeholder="City" value={@data[:city]} />
<% end %>
<%= if @step == 3 do %>
<h3>Review</h3>
<p>Name: <%= @data[:name] %></p>
<p>Email: <%= @data[:email] %></p>
<p>Address: <%= @data[:address] %></p>
<p>City: <%= @data[:city] %></p>
<% end %>
<div>
<%= if @step > 1 do %>
<button type="button" phx-click="prev_step">Previous</button>
<% end %>
<button type="submit">
<%= if @step == 3 do %>
Submit
<% else %>
Next
<% end %>
</button>
</div>
</form>
</div>
"""
end
def handle_event("next_step", params, socket) do
step = socket.assigns.step
data = Map.merge(socket.assigns.data, params)
if step == 3 do
# Submit form
{:noreply, assign(socket, data: data, submitted: true)}
else
{:noreply, assign(socket, step: step + 1, data: data)}
end
end
def handle_event("prev_step", _params, socket) do
{:noreply, assign(socket, step: socket.assigns.step - 1)}
end
endPhoenix LiveView Modal enables creating modal dialogs with content rendering, event handling, and state management.
- Modal Display: Show/hide modals
- Content Rendering: Render content in modal
- Event Handling: Handle modal events
- State Management: Track modal state
- Customizable: Custom modal styling
# Phoenix with LiveView Modal
defmodule MyAppWeb.ModalLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, show_modal: false, modal_content: nil)}
end
def render(assigns) do
~H"""
<div>
<h2>Modal Example</h2>
<button phx-click="open_modal">Open Modal</button>
<%= if @show_modal do %>
<div class="modal-overlay" phx-click="close_modal">
<div class="modal">
<div class="modal-header">
<h3>Modal Title</h3>
<button phx-click="close_modal">×</button>
</div>
<div class="modal-body">
<%= @modal_content %>
</div>
<div class="modal-footer">
<button phx-click="close_modal">Close</button>
<button phx-click="confirm_modal">Confirm</button>
</div>
</div>
</div>
<% end %>
</div>
"""
end
def handle_event("open_modal", _params, socket) do
{:noreply, assign(socket, show_modal: true, modal_content: "Modal content here")}
end
def handle_event("close_modal", _params, socket) do
{:noreply, assign(socket, show_modal: false)}
end
def handle_event("confirm_modal", _params, socket) do
{:noreply, assign(socket, show_modal: false)}
end
endPhoenix LiveView Tabs provides tabbed navigation with content switching and state management for multi-panel interfaces.
- Tab Navigation: Switch between tabs
- Content Switching: Show/hide tab content
- Active Tab: Highlight current tab
- State Management: Track active tab
- Dynamic Tabs: Create tabs dynamically
# Phoenix with LiveView Tabs
defmodule MyAppWeb.TabsLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, active_tab: "tab1")}
end
def render(assigns) do
~H"""
<div>
<h2>Tabs</h2>
<div class="tabs">
<button class={if @active_tab == "tab1", do: "active"}
phx-click="switch_tab" phx-value-tab="tab1">
Tab 1
</button>
<button class={if @active_tab == "tab2", do: "active"}
phx-click="switch_tab" phx-value-tab="tab2">
Tab 2
</button>
<button class={if @active_tab == "tab3", do: "active"}
phx-click="switch_tab" phx-value-tab="tab3">
Tab 3
</button>
</div>
<div class="tab-content">
<%= if @active_tab == "tab1" do %>
<h3>Tab 1 Content</h3>
<p>Content for tab 1</p>
<% end %>
<%= if @active_tab == "tab2" do %>
<h3>Tab 2 Content</h3>
<p>Content for tab 2</p>
<% end %>
<%= if @active_tab == "tab3" do %>
<h3>Tab 3 Content</h3>
<p>Content for tab 3</p>
<% end %>
</div>
</div>
"""
end
def handle_event("switch_tab", %{"tab" => tab}, socket) do
{:noreply, assign(socket, active_tab: tab)}
end
endPhoenix LiveView Accordion enables creating expandable content sections with toggle functionality and state management.
- Expandable Sections: Toggle section visibility
- State Management: Track open/closed state
- Multiple Open: Allow multiple open sections
- Toggle Events: Handle section toggle
- Use Cases: FAQs, menus, collapsible content
# Phoenix with LiveView Accordion
defmodule MyAppWeb.AccordionLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
items = [
%{id: 1, title: "Section 1", content: "Content 1", open: false},
%{id: 2, title: "Section 2", content: "Content 2", open: false},
%{id: 3, title: "Section 3", content: "Content 3", open: false}
]
{:ok, assign(socket, items: items)}
end
def render(assigns) do
~H"""
<div>
<h2>Accordion</h2>
<div class="accordion">
<%= for item <- @items do %>
<div class="accordion-item">
<div class="accordion-header" phx-click="toggle" phx-value-id={item.id}>
<%= item.title %>
</div>
<div class="accordion-body" style={if item.open, do: "display: block;", else: "display: none;"}>
<%= item.content %>
</div>
</div>
<% end %>
</div>
</div>
"""
end
def handle_event("toggle", %{"id" => id}, socket) do
items = Enum.map(socket.assigns.items, fn item ->
%{item | open: if item.id == id, do: !item.open, else: false}
end)
{:noreply, assign(socket, items: items)}
end
endPhoenix LiveView Carousel provides image and content carousels with navigation controls and auto-play functionality.
- Slides: Display multiple slides
- Navigation: Previous/Next controls
- Indicators: Show slide indicators
- Auto-play: Auto-advance slides
- State Management: Track current slide
# Phoenix with LiveView Carousel
defmodule MyAppWeb.CarouselLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
images = [
"image1.jpg", "image2.jpg", "image3.jpg", "image4.jpg"
]
{:ok, assign(socket, images: images, current: 0)}
end
def render(assigns) do
~H"""
<div>
<h2>Carousel</h2>
<div class="carousel">
<button phx-click="prev">‹</button>
<div class="carousel-slides">
<img src={Enum.at(@images, @current)} alt="Slide" />
</div>
<button phx-click="next">›</button>
</div>
<div class="carousel-indicators">
<%= for {_, index} <- Enum.with_index(@images) do %>
<span class={if index == @current, do: "active"}
phx-click="goto" phx-value-index={index}>
</span>
<% end %>
</div>
</div>
"""
end
def handle_event("prev", _params, socket) do
current = if socket.assigns.current > 0, do: socket.assigns.current - 1, else: length(socket.assigns.images) - 1
{:noreply, assign(socket, current: current)}
end
def handle_event("next", _params, socket) do
current = if socket.assigns.current < length(socket.assigns.images) - 1, do: socket.assigns.current + 1, else: 0
{:noreply, assign(socket, current: current)}
end
def handle_event("goto", %{"index" => index}, socket) do
{:noreply, assign(socket, current: String.to_integer(index))}
end
endPhoenix LiveView Tooltip enables creating tooltips with hover events and dynamic content display.
- Tooltip Display: Show on hover
- Content Management: Dynamic tooltip content
- Positioning: Position tooltips
- Event Handling: Show/hide on hover
- Customizable: Custom tooltip styling
# Phoenix with LiveView Tooltip
defmodule MyAppWeb.TooltipLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, tooltip: nil)}
end
def render(assigns) do
~H"""
<div>
<h2>Tooltip</h2>
<div phx-hook="Tooltip">
<button phx-click="show_tooltip">Hover me</button>
<%= if @tooltip do %>
<div class="tooltip">
<%= @tooltip %>
</div>
<% end %>
</div>
</div>
"""
end
def handle_event("show_tooltip", _params, socket) do
{:noreply, assign(socket, tooltip: "This is a tooltip!")}
end
endPhoenix LiveView Notification provides real-time notification delivery with unread counts and notification history.
- Real-time Notifications: Push notifications via PubSub
- Unread Count: Track unread notifications
- Notification List: Show notification history
- Mark as Read: Mark notifications as read
- Toast Notifications: Popup notifications
# Phoenix with LiveView Notification
defmodule MyAppWeb.NotificationLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
if connected?(socket) do
Phoenix.PubSub.subscribe(MyApp.PubSub, "notifications")
end
{:ok, assign(socket, notifications: [], unread_count: 0)}
end
def render(assigns) do
~H"""
<div>
<h2>Notifications</h2>
<button phx-click="toggle_notifications">
Notifications (<%= @unread_count %>)
</button>
<%= if @show_notifications do %>
<div class="notification-list">
<%= for notification <- @notifications do %>
<div class="notification-item">
<span class={if notification.read, do: "read", else: "unread"}>
<%= notification.message %>
</span>
<small><%= notification.timestamp %></small>
</div>
<% end %>
</div>
<% end %>
</div>
"""
end
def handle_info({:new_notification, message}, socket) do
notifications = [%{message: message, read: false, timestamp: DateTime.utc_now()} | socket.assigns.notifications]
{:noreply, assign(socket, notifications: notifications, unread_count: socket.assigns.unread_count + 1)}
end
def handle_event("toggle_notifications", _params, socket) do
{:noreply, assign(socket, show_notifications: !socket.assigns.show_notifications)}
end
endPhoenix LiveView Chat provides real-time chat functionality with message history, user typing indicators, and presence tracking.
- Real-time Messages: Send/receive messages instantly
- Message History: Persistent message storage
- Typing Indicators: Show user typing status
- User Presence: Show online users
- User Authentication: Authenticated chat
# Phoenix with LiveView Chat
defmodule MyAppWeb.ChatLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
if connected?(socket) do
Phoenix.PubSub.subscribe(MyApp.PubSub, "chat")
end
{:ok, assign(socket, messages: [], message: "", user: "Guest")}
end
def render(assigns) do
~H"""
<div>
<h2>Chat</h2>
<div class="chat-messages">
<%= for msg <- @messages do %>
<div class={if msg.user == @user, do: "mine", else: "theirs"}>
<strong><%= msg.user %>:</strong>
<span><%= msg.message %></span>
<small><%= msg.timestamp %></small>
</div>
<% end %>
</div>
<form phx-submit="send_message">
<input type="text" name="message" value={@message} phx-change="update_message" />
<button type="submit">Send</button>
</form>
<div>
<input type="text" value={@user} phx-change="update_user" placeholder="Username" />
</div>
</div>
"""
end
def handle_event("send_message", %{"message" => message}, socket) do
message = %{
user: socket.assigns.user,
message: message,
timestamp: DateTime.utc_now()
}
Phoenix.PubSub.broadcast(MyApp.PubSub, "chat", {:new_message, message})
{:noreply, assign(socket, message: "")}
end
def handle_event("update_message", %{"message" => message}, socket) do
{:noreply, assign(socket, message: message)}
end
def handle_event("update_user", %{"value" => user}, socket) do
{:noreply, assign(socket, user: user)}
end
def handle_info({:new_message, message}, socket) do
{:noreply, update(socket, :messages, fn msgs -> [message | msgs] end)}
end
endPhoenix LiveView Socket provides WebSocket socket communication with connection lifecycle management and event handling.
- Socket Connection: WebSocket connection
- Socket Authentication: Authenticate sockets
- Event Handling: Handle socket events
- Connection State: Track connection status
- Use Cases: Real-time features, messaging
# Phoenix with LiveView Socket
defmodule MyAppWeb.SocketLive do
use Phoenix.LiveView
def mount(_params, session, socket) do
socket = socket
|> assign(:current_user, session["current_user"])
|> assign(:socket_id, Phoenix.Socket.ID)
if connected?(socket) do
Phoenix.PubSub.subscribe(MyApp.PubSub, "user:#{socket.assigns.current_user.id}")
end
{:ok, socket}
end
def render(assigns) do
~H"""
<div>
<h2>Socket</h2>
<p>User: <%= @current_user.name %></p>
<p>Socket ID: <%= @socket_id %></p>
<div class="events">
<h3>Events</h3>
<ul>
<%= for event <- @events do %>
<li><%= event %></li>
<% end %>
</ul>
</div>
</div>
"""
end
def handle_info({:user_event, event}, socket) do
{:noreply, update(socket, :events, fn events -> [event | events] end)}
end
endPhoenix LiveView Session provides session management with user-specific data persistence across requests.
- Session Data: Store user-specific data
- Session Management: Create, read, update, delete
- User Context: Access session data in LiveView
- Persistence: Data persists across page reloads
- Secure: Signed session cookies
# Phoenix with LiveView Session
defmodule MyAppWeb.SessionLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, count: 0, session_data: %{})}
end
def render(assigns) do
~H"""
<div>
<h2>Session</h2>
<div>
<p>Count: <%= @count %></p>
<button phx-click="increment">Increment</button>
<button phx-click="decrement">Decrement</button>
<button phx-click="reset">Reset</button>
</div>
<div>
<h3>Session Data</h3>
<p><%= inspect(@session_data) %></p>
<button phx-click="save_session">Save Session</button>
<button phx-click="clear_session">Clear Session</button>
</div>
</div>
"""
end
def handle_event("increment", _params, socket) do
{:noreply, update(socket, :count, &(&1 + 1))}
end
def handle_event("decrement", _params, socket) do
{:noreply, update(socket, :count, &(&1 - 1))}
end
def handle_event("reset", _params, socket) do
{:noreply, assign(socket, count: 0)}
end
def handle_event("save_session", _params, socket) do
session_data = %{count: socket.assigns.count, timestamp: DateTime.utc_now()}
{:noreply, assign(socket, session_data: session_data)}
end
def handle_event("clear_session", _params, socket) do
{:noreply, assign(socket, session_data: %{})}
end
endPhoenix LiveView Cookie provides cookie management with reading, writing, and deleting cookies from LiveView.
- Cookie Reading: Access cookie values
- Cookie Writing: Set cookie values
- Cookie Deletion: Delete cookies
- Cookie Options: Expiry, domain, secure, httpOnly
- Use Cases: User preferences, themes, tracking
# Phoenix with LiveView Cookie
defmodule MyAppWeb.CookieLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
cookie = get_cookie(socket)
{:ok, assign(socket, cookie: cookie, theme: cookie["theme"] || "light")}
end
def render(assigns) do
~H"""
<div class={"theme-#{@theme}"}>
<h2>Cookie Example</h2>
<div>
<h3>Theme</h3>
<button phx-click="set_theme" phx-value-theme="light">Light</button>
<button phx-click="set_theme" phx-value-theme="dark">Dark</button>
<button phx-click="set_theme" phx-value-theme="blue">Blue</button>
</div>
<div>
<h3>Cookie Data</h3>
<p><%= inspect(@cookie) %></p>
<button phx-click="clear_cookie">Clear Cookie</button>
</div>
</div>
"""
end
def handle_event("set_theme", %{"theme" => theme}, socket) do
cookie = Map.put(socket.assigns.cookie, "theme", theme)
{:noreply, assign(socket, theme: theme, cookie: cookie)}
end
def handle_event("clear_cookie", _params, socket) do
{:noreply, assign(socket, cookie: %{}, theme: "light")}
end
defp get_cookie(socket) do
# Get cookie from connection
%{"theme" => "light"}
end
endPhoenix LiveView Storage provides client-side storage with localStorage and sessionStorage integration.
- localStorage: Persistent client storage
- sessionStorage: Session-specific storage
- Set/Get/Delete: Storage operations
- LiveView Integration: Sync storage with LiveView
- Use Cases: Form data, user preferences, cache
# Phoenix with LiveView Storage
defmodule MyAppWeb.StorageLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, data: %{}, key: "", value: "")}
end
def render(assigns) do
~H"""
<div>
<h2>Storage</h2>
<div>
<h3>Set Value</h3>
<form phx-submit="set_value">
<input type="text" name="key" placeholder="Key" value={@key} phx-change="update_key" />
<input type="text" name="value" placeholder="Value" value={@value} phx-change="update_value" />
<button type="submit">Set</button>
</form>
</div>
<div>
<h3>Get Value</h3>
<form phx-submit="get_value">
<input type="text" name="key" placeholder="Key" />
<button type="submit">Get</button>
</form>
</div>
<div>
<h3>All Data</h3>
<pre><%= inspect(@data) %></pre>
<button phx-click="clear_all">Clear All</button>
</div>
</div>
"""
end
def handle_event("update_key", %{"key" => key}, socket) do
{:noreply, assign(socket, key: key)}
end
def handle_event("update_value", %{"value" => value}, socket) do
{:noreply, assign(socket, value: value)}
end
def handle_event("set_value", %{"key" => key, "value" => value}, socket) do
data = Map.put(socket.assigns.data, key, value)
{:noreply, assign(socket, data: data, key: "", value: "")}
end
def handle_event("get_value", %{"key" => key}, socket) do
value = Map.get(socket.assigns.data, key, "Not found")
{:noreply, put_flash(socket, :info, "Value: #{value}")}
end
def handle_event("clear_all", _params, socket) do
{:noreply, assign(socket, data: %{})}
end
endPhoenix LiveView LocalStorage enables persistent client-side storage with data persistence across browser sessions.
- Persistent Storage: Data survives page reloads
- Data Operations: Set, get, remove items
- JSON Support: Store complex data structures
- LiveView Sync: Sync with LiveView state
- Use Cases: Shopping carts, saved preferences
# Phoenix with LiveView LocalStorage
defmodule MyAppWeb.LocalStorageLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, items: [], new_item: "")}
end
def render(assigns) do
~H"""
<div>
<h2>Local Storage</h2>
<div>
<form phx-submit="add_item">
<input type="text" name="item" value={@new_item} phx-change="update_item" />
<button type="submit">Add</button>
</form>
</div>
<ul>
<%= for item <- @items do %>
<li>
<%= item %>
<button phx-click="remove_item" phx-value-item={item}>×</button>
</li>
<% end %>
</ul>
<button phx-click="clear_items">Clear All</button>
</div>
"""
end
def handle_event("update_item", %{"item" => item}, socket) do
{:noreply, assign(socket, new_item: item)}
end
def handle_event("add_item", %{"item" => item}, socket) do
{:noreply, update(socket, :items, fn items -> items ++ [item] end)}
end
def handle_event("remove_item", %{"item" => item}, socket) do
{:noreply, update(socket, :items, fn items -> List.delete(items, item) end)}
end
def handle_event("clear_items", _params, socket) do
{:noreply, assign(socket, items: [])}
end
endPhoenix LiveView History provides browser history management with undo/redo functionality and state tracking.
- History Tracking: Track state changes
- Undo/Redo: Navigate through history
- State Snapshots: Save state snapshots
- Browser History: Push/pop browser history
- Use Cases: Form undo, navigation history
# Phoenix with LiveView History
defmodule MyAppWeb.HistoryLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, history: [], input: "", position: 0)}
end
def render(assigns) do
~H"""
<div>
<h2>History</h2>
<div>
<form phx-submit="add_history">
<input type="text" name="input" value={@input} phx-change="update_input" />
<button type="submit">Add</button>
</form>
</div>
<div>
<button phx-click="undo">Undo</button>
<button phx-click="redo">Redo</button>
<button phx-click="clear_history">Clear</button>
</div>
<ul>
<%= for item <- Enum.reverse(@history) do %>
<li><%= item %></li>
<% end %>
</ul>
</div>
"""
end
def handle_event("update_input", %{"input" => input}, socket) do
{:noreply, assign(socket, input: input)}
end
def handle_event("add_history", %{"input" => input}, socket) do
history = [input | socket.assigns.history]
position = length(history)
{:noreply, assign(socket, history: history, input: "", position: position)}
end
def handle_event("undo", _params, socket) do
if socket.assigns.position > 0 do
{:noreply, update(socket, :position, &(&1 - 1))}
else
{:noreply, socket}
end
end
def handle_event("redo", _params, socket) do
if socket.assigns.position < length(socket.assigns.history) do
{:noreply, update(socket, :position, &(&1 + 1))}
else
{:noreply, socket}
end
end
def handle_event("clear_history", _params, socket) do
{:noreply, assign(socket, history: [], position: 0)}
end
endPhoenix LiveView Timer provides countdown and stopwatch functionality with real-time updates and state management.
- Countdown Timer: Count down from set time
- Stopwatch: Measure elapsed time
- Start/Stop/Reset: Timer controls
- Real-time Updates: Update every second
- Use Cases: Countdowns, elapsed time tracking
# Phoenix with LiveView Timer
defmodule MyAppWeb.TimerLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
if connected?(socket) do
Process.send_after(self(), :tick, 1000)
end
{:ok, assign(socket, seconds: 0, running: true)}
end
def render(assigns) do
~H"""
<div>
<h2>Timer</h2>
<div class="timer">
<h1><%= format_time(@seconds) %></h1>
<div>
<button phx-click="start">Start</button>
<button phx-click="pause">Pause</button>
<button phx-click="reset">Reset</button>
</div>
</div>
</div>
"""
end
def handle_info(:tick, socket) do
if socket.assigns.running do
Process.send_after(self(), :tick, 1000)
{:noreply, update(socket, :seconds, &(&1 + 1))}
else
Process.send_after(self(), :tick, 1000)
{:noreply, socket}
end
end
def handle_event("start", _params, socket) do
{:noreply, assign(socket, running: true)}
end
def handle_event("pause", _params, socket) do
{:noreply, assign(socket, running: false)}
end
def handle_event("reset", _params, socket) do
{:noreply, assign(socket, seconds: 0, running: false)}
end
defp format_time(seconds) do
hours = div(seconds, 3600)
minutes = div(rem(seconds, 3600), 60)
seconds = rem(seconds, 60)
"#{pad(hours)}:#{pad(minutes)}:#{pad(seconds)}"
end
defp pad(n) when n < 10, do: "0#{n}"
defp pad(n), do: to_string(n)
endPhoenix LiveView Canvas enables interactive canvas drawing with real-time updates and user interactions.
- Canvas Drawing: Draw on HTML5 canvas
- Real-time Updates: Sync canvas state
- Mouse Events: Drawing with mouse/pointer
- Color/Size Controls: Customize drawing tools
- Use Cases: Whiteboard, drawing apps, signatures
# Phoenix with LiveView Canvas
defmodule MyAppWeb.CanvasLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, points: [], color: "#000000", size: 5)}
end
def render(assigns) do
~H"""
<div>
<h2>Canvas</h2>
<div id="canvas" phx-hook="Canvas" data-points={inspect(@points)}>
<canvas id="canvas-element" width="400" height="400"></canvas>
</div>
<div>
<label>Color:</label>
<input type="color" name="color" value={@color} phx-change="update_color" />
<label>Size:</label>
<input type="range" name="size" min="1" max="20" value={@size} phx-change="update_size" />
<button phx-click="clear_canvas">Clear</button>
</div>
</div>
"""
end
def handle_event("update_color", %{"color" => color}, socket) do
{:noreply, assign(socket, color: color)}
end
def handle_event("update_size", %{"size" => size}, socket) do
{:noreply, assign(socket, size: String.to_integer(size))}
end
def handle_event("clear_canvas", _params, socket) do
{:noreply, assign(socket, points: [])}
end
def handle_event("draw", %{"points" => points}, socket) do
{:noreply, assign(socket, points: points)}
end
endPhoenix LiveView Animation provides CSS animations and transitions with state-driven animation triggers and controls.
- CSS Animations: Animate with CSS
- State-driven: Trigger on state change
- Transitions: Smooth transitions
- Animation Controls: Start/Stop/Reset
- Use Cases: UI animations, interactive elements
# Phoenix with LiveView Animation
defmodule MyAppWeb.AnimationLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
if connected?(socket) do
Process.send_after(self(), :animate, 50)
end
{:ok, assign(socket, position: 0, direction: 1)}
end
def render(assigns) do
~H"""
<div>
<h2>Animation</h2>
<div id="animation-container" style="position: relative; height: 200px; overflow: hidden;">
<div id="animated-element" style="
position: absolute;
left: <%= @position %>px;
width: 50px;
height: 50px;
background: blue;
border-radius: 50%;
transition: left 0.05s;
">
</div>
</div>
<div>
<button phx-click="start_animation">Start</button>
<button phx-click="stop_animation">Stop</button>
<button phx-click="reset_animation">Reset</button>
</div>
</div>
"""
end
def handle_info(:animate, socket) do
if socket.assigns.animation_running do
position = socket.assigns.position + socket.assigns.direction * 5
direction = if position >= 350 or position <= 0, do: -socket.assigns.direction, else: socket.assigns.direction
Process.send_after(self(), :animate, 50)
{:noreply, assign(socket, position: position, direction: direction)}
else
Process.send_after(self(), :animate, 50)
{:noreply, socket}
end
end
def handle_event("start_animation", _params, socket) do
{:noreply, assign(socket, animation_running: true)}
end
def handle_event("stop_animation", _params, socket) do
{:noreply, assign(socket, animation_running: false)}
end
def handle_event("reset_animation", _params, socket) do
{:noreply, assign(socket, position: 0, animation_running: false)}
end
endPhoenix LiveView Audio provides audio player controls with playback, volume, and progress tracking.
- Audio Playback: Play/pause audio
- Volume Control: Adjust volume
- Progress Tracking: Track playback progress
- Audio Controls: Play, pause, seek, volume
- Use Cases: Music player, podcast player, audio content
# Phoenix with LiveView Audio
defmodule MyAppWeb.AudioLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, playing: false, volume: 50, current_time: 0)}
end
def render(assigns) do
~H"""
<div>
<h2>Audio Player</h2>
<div class="audio-player">
<button phx-click="toggle_play">
<%= if @playing, do: "Pause", else: "Play" %>
</button>
<input type="range" min="0" max="100" value={@volume} phx-change="set_volume" />
<span><%= @volume %>%</span>
<div class="progress">
<input type="range" min="0" max="100" value={@current_time} phx-change="seek" />
<span><%= format_time(@current_time) %></span>
</div>
</div>
</div>
"""
end
def handle_event("toggle_play", _params, socket) do
{:noreply, assign(socket, playing: !socket.assigns.playing)}
end
def handle_event("set_volume", %{"value" => volume}, socket) do
{:noreply, assign(socket, volume: String.to_integer(volume))}
end
def handle_event("seek", %{"value" => time}, socket) do
{:noreply, assign(socket, current_time: String.to_integer(time))}
end
defp format_time(seconds) do
minutes = div(seconds, 60)
seconds = rem(seconds, 60)
"#{pad(minutes)}:#{pad(seconds)}"
end
defp pad(n) when n < 10, do: "0#{n}"
defp pad(n), do: to_string(n)
endA Complete Library Management System in Elixir demonstrates OTP principles, Ecto for database, and Phoenix for web interface with real-time updates.
- Book Management: Add, search, track availability
- Member Management: Registration and borrowing
- Borrow/Return: Transaction processing with Ecto
- Real-time Updates: Phoenix Channels for live updates
- Reports: Generate library statistics and reports
# Complete Library Management System in Elixir
defmodule MyApp.Library do
alias MyApp.{Repo, Book, Member, Borrowing}
def add_book(attrs) do
%Book{}
|> Book.changeset(attrs)
|> Repo.insert()
end
def register_member(attrs) do
%Member{}
|> Member.changeset(attrs)
|> Repo.insert()
end
def borrow_book(member_id, book_id) do
member = Repo.get(Member, member_id)
book = Repo.get(Book, book_id)
if book.available_copies > 0 do
Repo.transaction(fn ->
# Decrement available copies
book
|> Book.available_changeset(%{available_copies: book.available_copies - 1})
|> Repo.update!()
# Create borrowing record
%Borrowing{}
|> Borrowing.changeset(%{member_id: member_id, book_id: book_id})
|> Repo.insert!()
end)
{:ok, "Book borrowed successfully"}
else
{:error, "No copies available"}
end
end
def return_book(member_id, book_id) do
borrowing = Repo.get_by(Borrowing, member_id: member_id, book_id: book_id, returned_at: nil)
if borrowing do
Repo.transaction(fn ->
# Update borrowing record
borrowing
|> Borrowing.return_changeset()
|> Repo.update!()
# Increment available copies
book = Repo.get(Book, book_id)
book
|> Book.available_changeset(%{available_copies: book.available_copies + 1})
|> Repo.update!()
end)
{:ok, "Book returned successfully"}
else
{:error, "No active borrowing record found"}
end
end
def search_books(query) do
from b in Book,
where: ilike(b.title, ^"%#{query}%") or ilike(b.author, ^"%#{query}%"),
order_by: b.title
|> Repo.all()
end
def get_member_borrowings(member_id) do
query = from b in Borrowing,
where: b.member_id == ^member_id,
preload: [:book],
order_by: [desc: b.borrowed_at]
Repo.all(query)
end
def get_overdue_books do
query = from b in Borrowing,
where: b.returned_at == nil and b.due_date < ^Date.utc_today(),
preload: [:member, :book]
Repo.all(query)
end
def get_available_books do
query = from b in Book,
where: b.available_copies > 0,
order_by: b.title
Repo.all(query)
end
def get_library_stats do
%{
total_books: Repo.aggregate(Book, :count, :id),
total_members: Repo.aggregate(Member, :count, :id),
active_borrowings: Repo.aggregate(Borrowing, :count, :id, where: [returned_at: nil]),
overdue_books: length(get_overdue_books())
}
end
end
# Phoenix Controller
defmodule MyAppWeb.LibraryController do
use MyAppWeb, :controller
def index(conn, _params) do
books = MyApp.Library.get_available_books()
stats = MyApp.Library.get_library_stats()
render(conn, "index.html", books: books, stats: stats)
end
def search(conn, %{"q" => query}) do
results = MyApp.Library.search_books(query)
json(conn, results)
end
def borrow(conn, %{"member_id" => member_id, "book_id" => book_id}) do
case MyApp.Library.borrow_book(member_id, book_id) do
{:ok, message} ->
json(conn, %{success: true, message: message})
{:error, error} ->
conn |> put_status(400) |> json(%{success: false, error: error})
end
end
def return(conn, %{"member_id" => member_id, "book_id" => book_id}) do
case MyApp.Library.return_book(member_id, book_id) do
{:ok, message} ->
json(conn, %{success: true, message: message})
{:error, error} ->
conn |> put_status(400) |> json(%{success: false, error: error})
end
end
end