Never Look Back - Phoenix - The Framework That Respects You

Posted on Nov 5, 2025

Chapter 4: Phoenix - The Framework That Respects You

If Elixir is the language, Phoenix is its web-facing manifestation. And while it shares some superficial similarities with Rails (it has controllers, views, and a router), its philosophy is fundamentally different. Phoenix values explicitness over magic.

  1. Ecto and Changesets: Sanity in Data Handling

    Ecto is the database wrapper and query language for Phoenix. It is not an ActiveRecord clone. Its most important innovation is the Changeset.

    In Rails, validation rules live on the model. When you call model.valid?, it checks its internal state. When you call model.update(params), it tries to update its attributes and save, all in one go.

    In Phoenix, data and validation are decoupled. A changeset is a dedicated data structure that represents a proposed change.

    def changeset(user, attrs) do
      user
      |> cast(attrs, [:name, :email])
      |> validate_required([:name, :email])
      |> validate_format(:email, ~r/@/)
      |> unique_constraint(:email)
    end
    

    This function takes a user struct and a map of attrs (the incoming parameters). It then pipes this data through a series of transformations and validations.

    1. cast: Specifies which attributes are allowed to be changed. This is a security-first default.
    2. validate_required: Checks for presence.
    3. validate_format: Checks against a regex.
    4. unique_constraint: Checks for uniqueness at the database level.

    The key is that this function returns a changeset. The changeset contains the original data, the proposed changes, a list of validations, and a valid? flag. No side effects have occurred. The database has not been touched.

    Your controller then explicitly uses this changeset to perform the database operation:

    case MyApp.Accounts.create_user(params) do
      {:ok, user} ->
        # Redirect to show page
      {:error, %Ecto.Changeset{} = changeset} ->
        # Re-render the form, passing the changeset back
        # so we can display detailed error messages.
        render(conn, "new.html", changeset: changeset)
    end
    

    This explicitness is liberating. The flow of data is crystal clear. There is no hidden state, no callback spaghetti. You can see the transformation of data from raw parameters to a validated changeset to a database insert. It’s more verbose than Rails, but that verbosity buys you clarity, security, and maintainability.

  2. Plug: A Composable Architecture

    Phoenix is built on a specification called Plug. A plug is just a function that takes a connection struct (conn) and some options, and returns a (possibly modified) conn.

    Your entire request-response lifecycle is a pipeline of plugs.

    pipeline :browser do
      plug :accepts, ["html"]
      plug :fetch_session
      plug :fetch_live_flash
      plug :protect_from_forgery
      plug :put_secure_browser_headers
    end
    

    This is from the router. When a request comes in, it flows through each of these plugs in order. fetch_session loads the session data into the conn. protect_from_forgery validates the CSRF token. Controllers themselves are just plugs. This is a beautifully simple and composable idea. It’s Elixir’s functional nature applied to the web. It’s explicit, easy to follow, and easy to test.


Read prev: A New Paradigm: The Elixir of Life | Read next: The Real-Time Revolution with LiveView