Phoenix is Elixir's web framework — it powers production systems handling millions of simultaneous WebSocket connections. Today you build a Phoenix application with routes, controllers, templates, and Ecto database access.
Phoenix follows the MVC pattern: Router maps URLs to controller actions, Controllers handle requests and call context modules, Views/Templates render HTML or JSON, Contexts are domain-logic modules wrapping Ecto schemas. Phoenix Channels handle real-time WebSocket connections. Phoenix LiveView renders reactive UIs with server-side state — no JavaScript framework needed for most interactivity.
Ecto is Elixir's database wrapper and query language. Schemas define the mapping between Elixir structs and database tables. Changesets validate and transform data before insertion. Queries are composable and compiled to SQL. Repo executes queries: Repo.get/2, Repo.all/1, Repo.insert/1, Repo.update/1, Repo.delete/1. Ecto supports PostgreSQL, MySQL, SQLite, and MSSQL.
LiveView maintains a WebSocket connection between browser and server. Server-side state lives in a mount/3 callback. handle_event/3 responds to browser events (clicks, form inputs) and updates state. The client re-renders only the changed HTML fragments. This eliminates 90% of custom JavaScript for most apps while matching SPA interactivity. WhatsApp Web is partially LiveView.
# Router (lib/my_app_web/router.ex)
scope "/api", MyAppWeb do
pipe_through :api
resources "/posts", PostController, only: [:index, :show, :create]
end
# Controller (lib/my_app_web/controllers/post_controller.ex)
defmodule MyAppWeb.PostController do
use MyAppWeb, :controller
alias MyApp.Blog
def index(conn, _params) do
posts = Blog.list_posts()
json(conn, %{posts: posts})
end
def create(conn, %{"title" => title, "body" => body}) do
case Blog.create_post(%{title: title, body: body}) do
{:ok, post} -> json(conn, %{post: post})
{:error, changeset} ->
conn
|> put_status(:unprocessable_entity)
|> json(%{errors: changeset.errors})
end
end
end
# Ecto Schema (lib/my_app/blog/post.ex)
defmodule MyApp.Blog.Post do
use Ecto.Schema
import Ecto.Changeset
schema "posts" do
field :title, :string
field :body, :string
timestamps()
end
def changeset(post, attrs) do
post
|> cast(attrs, [:title, :body])
|> validate_required([:title, :body])
|> validate_length(:title, min: 3, max: 200)
end
end
Build a Phoenix LiveView todo app with real-time updates. Add a new todo with a form, mark todos complete, and delete them — all without a page refresh, using only LiveView and no custom JavaScript.