पाठ 18 / 25

Contexts, JSON APIs and Authentication

Organise business logic into contexts and secure APIs and pages.

Designing a Phoenix application

Phoenix encourages grouping related functionality into contexts, public modules that form the boundary of a domain area, such as Shop.Catalog, Shop.Orders and Shop.Accounts. Controllers, LiveViews and jobs call context functions like Orders.place_order(customer, cart), never the Repo or schemas directly, which keeps business rules in one tested place and makes later extraction easier. For JSON APIs, use the :api pipeline, render maps with json/2 or JSON view modules, return proper status codes (201 on creation, 422 with changeset errors) and handle failures centrally with action_fallback, a controller that turns {:error, ...} results into responses. Authentication: mix phx.gen.auth generates a secure, customisable email-based login system with hashed passwords (bcrypt, pbkdf2 or argon2), session tokens and confirmation flows; APIs commonly use bearer tokens via Phoenix.Token or libraries. Phoenix includes CSRF protection, secure headers and parameterised queries by default. Validate all input through changesets and authorise actions in contexts.

A JSON controller with action_fallback

Contexts return tuples; the fallback controller renders errors.

defmodule ShopWeb.OrderController do
  use ShopWeb, :controller
  alias Shop.Orders

  action_fallback ShopWeb.FallbackController

  def create(conn, %{"order" => order_params}) do
    customer = conn.assigns.current_customer

    with {:ok, order} <- Orders.place_order(customer, order_params) do
      conn
      |> put_status(:created)
      |> json(%{data: %{id: order.id, total_paise: order.total_paise}})
    end
  end
end

defmodule ShopWeb.FallbackController do
  use ShopWeb, :controller

  def call(conn, {:error, %Ecto.Changeset{} = changeset}) do
    errors =
      Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} ->
        Enum.reduce(opts, msg, fn {key, value}, acc ->
          String.replace(acc, "%{#{key}}", to_string(value))
        end)
      end)

    conn |> put_status(422) |> json(%{errors: errors})
  end

  def call(conn, {:error, :not_found}) do
    conn |> put_status(:not_found) |> json(%{error: "not found"})
  end

  def call(conn, {:error, :out_of_stock}) do
    conn |> put_status(:conflict) |> json(%{error: "item out of stock"})
  end
end

Contexts are boundaries, not folders

A context is valuable because other code goes through its public functions. If controllers start calling Repo directly, the boundary is lost and business rules spread across the web layer.

त्वरित जाँच: What does action_fallback do in a Phoenix controller?

  • Sends non-conn return values such as {:error, ...} to a fallback controller that renders the response
  • Retries the action
  • Caches responses
  • Disables CSRF
Answer

Sends non-conn return values such as {:error, ...} to a fallback controller that renders the response — It centralises error rendering for actions that return error tuples.