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 Before moving on, make sure you can answer these without looking: