Day 3 of 5
⏱ ~60 minutes
Elixir in 5 Days — Day 3

Phoenix Framework Basics

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 Architecture

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: Database Interactions

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.

Phoenix LiveView

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.

elixir
# 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
💡
Generate Phoenix CRUD scaffolding with: mix phx.gen.json Blog Post posts title:string body:text. This creates the schema, migration, context, controller, and tests in one command.
📝 Day 3 Exercise
Build a Phoenix JSON API
  1. Install Phoenix: mix archive.install hex phx_new
  2. Create a project: mix phx.new blog --no-html --no-assets
  3. Generate a Post resource: mix phx.gen.json Blog Post posts title:string body:text
  4. Run migrations: mix ecto.migrate
  5. Test with curl: POST /api/posts with JSON body, then GET /api/posts

Day 3 Summary

  • Phoenix Router, Controller, Context, Schema is the standard architecture
  • Ecto Changesets validate data before database operations
  • Repo.insert/get/update/delete are the primary database operations
  • Phoenix LiveView enables reactive UIs without a JavaScript framework
  • mix phx.gen.json generates complete CRUD scaffolding in one command
Challenge

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.

Finished this lesson?