# Phoenix

> [Applies to: **/*] Provides definitive guidelines for building robust, maintainable, and performant Phoenix applications using modern Elixir best practices, focusing on context-driven design, strict formatting, and effective testing.

- Skill: `tryboy869/phoenix` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds add tryboy869/phoenix`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tryboy869/phoenix/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Tryboy869 (https://skillmd.com/u/tryboy869)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/tryboy869/phoenix

---


# phoenix Best Practices

This guide outlines the definitive best practices for developing Phoenix applications. Adhere to these rules to ensure consistency, maintainability, and high performance across our codebase.

## Code Organization and Structure

### 1. Context-Driven Design
Encapsulate all data access and business logic for a feature within a dedicated context module. Controllers and LiveViews must remain thin, focusing solely on HTTP/WebSocket handling.

**✅ GOOD:**
```elixir
# lib/my_app/accounts.ex
defmodule MyApp.Accounts do
  alias MyApp.Repo
  alias MyApp.Accounts.User

  def create_user(attrs) do
    %User{}
    |> User.changeset(attrs)
    |> Repo.insert()
  end

  def get_user!(id), do: Repo.get!(User, id)
end

# lib/my_app_web/controllers/user_controller.ex
defmodule MyAppWeb.UserController do
  use MyAppWeb, :controller
  alias MyApp.Accounts

  def create(conn, %{"user" => user_params}) do
    case Accounts.create_user(user_params) do
      {:ok, user} ->
        conn
        |> put_flash(:info, "User created successfully.")
        |> redirect(to: ~p"/users/#{user}")
      {:error, %Ecto.Changeset{} = changeset} ->
        conn
        |> put_flash(:error, "Failed to create user.")
        |> render("new.html", changeset: changeset)
    end
  end
end
```

### 2. Single Module Per Source File
Each `.ex` file must contain a single module. This includes structs.

**❌ BAD:**
```elixir
# lib/payment/worker.ex
defmodule Payment.Worker do
  defmodule WorkerConfiguration do
    defstruct [:retry_period_ms, :error_retry_limit]
  end
  # ...
end
```

**✅ GOOD:**
```elixir
# lib/payment/worker.ex
defmodule Payment.Worker do
  # ...
end

# lib/payment/worker_configuration.ex
defmodule Payment.WorkerConfiguration do
  defstruct [:retry_period_ms, :error_retry_limit]
end
```

### 3. Naming Conventions
Adhere strictly to Elixir's naming conventions.

*   **Modules:** `PascalCase`
*   **Functions, Variables, Files, Directories:** `snake_case`

**❌ BAD:**
```elixir
defmodule Payment.jobs.Inquiry_scheduler do # Module, file, dir naming
  defp calculateBalance(%Entry{bookType: "debit", amount: amount}, balance) do # Function, variable naming
    balance - amount
  end
end
```

**✅ GOOD:**
```elixir
# lib/payment/jobs/inquiry_scheduler.ex
defmodule Payment.Jobs.InquiryScheduler do
  defp calculate_balance(%Entry{book_type: "debit", amount: amount}, balance) do
    balance - amount
  end
end
```

### 4. Code Formatting
Always run `mix format`. Configure `.formatter.exs` with `line_length: 100`. Use 2-space soft tabs. End every file with a newline.

## Common Patterns and Anti-patterns

### 1. Bang Functions (`!`) for Raising Errors
Suffix functions that are expected to raise an exception (e.g., on invalid input or missing record) with `!`. This clearly signals to callers that the function might fail.

**✅ GOOD:**
```elixir
def create_user!(registration_uid, customer_params) do
  # ... logic that might raise ArgumentError or Ecto.NoResultsError
end

defp build_attrs!(%{
  "alien_id" => alien_id,
  "citizen_id" => citizen_id,
}) do
  %{citizen_id: citizen_id, alien_id: alien_id}
end

defp build_attrs!(_params) do
  raise ArgumentError, message: "Unexpected customer profile params"
end
```

### 2. Prefer Pattern Matching and Guards
Use pattern matching and guards in function definitions over `if/else` statements within the function body for clearer, more declarative logic.

**❌ BAD:**
```elixir
defp handle_response({:ok, %Response{status: status, body: body}}) do
  if status in 200..299 do
    {:ok, body}
  else
    {:error, :internal_server_error, reason}
  end
end
```

**✅ GOOD:**
```elixir
defp handle_response({:ok, %Response{status: status, body: body}})
     when status in 200..299,
     do: {:ok, body}

defp handle_response({:error, reason}),
     do: {:error, :internal_server_error, reason}
```

### 3. Pipe Operator Usage
Avoid using the pipe operator for a single function call. Start a pipeline with the raw value. Use `Kernel.then/2` for anonymous functions in a pipeline.

**❌ BAD:**
```elixir
some_string |> String.downcase()
value |> (&(&1 * 10)).()
```

**✅ GOOD:**
```elixir
String.downcase(some_string)
some_string |> String.trim() |> String.downcase() # Raw value start
value |> then(&(&1 * 10)) # then/2 for anonymous functions
```

### 4. Alias Grouping
Use shorthand syntax to group aliases from the same submodule.

**❌ BAD:**
```elixir
alias Payment.Ledger.Balance
alias Payment.Ledger.EntriesGenerator
```

**✅ GOOD:**
```elixir
alias Payment.Ledger.{Balance, EntriesGenerator}
```

### 5. Conditional Statements
Use `do:` for single-line `if/unless`. Never use `unless` with `else`; rewrite with `if`.

**❌ BAD:**
```elixir
if some_condition do "some_stuff" end
unless success do IO.puts("failure") else IO.puts("success") end
```

**✅ GOOD:**
```elixir
if some_condition, do: "some_stuff"
if success, do: IO.puts("success"), else: IO.puts("failure")
```

### 6. Ignored Variables
Use `_variable_name` for ignored variables to improve readability.

**❌ BAD:**
```elixir
def index(conn, _), do: Plug.Conn.resp(conn, :ok, "success")
```

**✅ GOOD:**
```elixir
def index(conn, _params), do: Plug.Conn.resp(conn, :ok, "success")
```

### 7. Private Functions
Place all private functions at the bottom of the module.

## Performance Considerations

Phoenix leverages the Erlang VM for high concurrency and fault tolerance.
*   **Ecto Queries**: Optimize database queries. Use `Ecto.Query.preload/3` to avoid N+1 issues.
*   **LiveView**: Embrace LiveView for interactive UIs to minimize client-side JavaScript and network roundtrips.
*   **Concurrency**: Design long-running tasks to run in separate processes (e.g., using `Task` or `GenServer`) to avoid blocking the request cycle.

## Common Pitfalls and Gotchas

### 1. Scattering Business Logic
The most common pitfall is scattering business logic across controllers, LiveViews, and schemas. Always centralize it in contexts.

### 2. Over-reliance on Metaprogramming
While powerful, excessive `use` or `import` can obscure where functions originate. Rely on documentation and `h/1` in IEx to understand module APIs.

### 3. Deployment Complexity
Elixir Releases with Docker simplify deployment, but still require careful configuration of environment variables and build steps (e.g., `npm install` for assets). Use `mix release` and the generated `Dockerfile`.

## Security Best Practices

### 1. Authentication
Use `mix phx.gen.auth` for a robust, production-ready authentication system.

### 2. Input Validation
Always validate user input using Ecto Changesets. Never trust client-side data.

**✅ GOOD:**
```elixir
def changeset(user, attrs) do
  user
  |> cast(attrs, [:email, :password, :password_confirmation])
  |> validate_required([:email, :password])
  |> validate_length(:password, min: 8)
  |> unique_constraint(:email)
  |> put_assoc(:profile, profile_changeset(user.profile, attrs))
end
```

### 3. XSS and CSRF Protection
Phoenix provides built-in protection against XSS (via HTML escaping in HEEx templates) and CSRF (via `Plug.CSRFProtection`). Ensure these are active.

## Error Handling

### 1. Return Tuples for Expected Outcomes
For operations that can succeed or fail, consistently return `{:ok, result}` or `{:error, reason}` tuples.

**✅ GOOD:**
```elixir
def update_user(user, attrs) do
  user
  |> User.changeset(attrs)
  |> Repo.update()
end
```

### 2. Centralized Logging and Error Tracking
Integrate with a centralized logging solution (e.g., Graylog via `GelfLogger`) and an error tracking service (e.g., Sentry) for production monitoring.

## API Design

### 1. Contexts as API Boundaries
Design your contexts to expose a clear, stable API for interacting with your application's data and business logic.

### 2. RESTful JSON APIs
Use `mix phx.gen.json` to scaffold RESTful JSON APIs. Ensure consistent response formats and HTTP status codes.

## Testing Approaches

### 1. Comprehensive ExUnit Testing
Write tests for all layers: contexts, controllers, LiveViews, and channels. Run tests with `mix test`.

### 2. Leverage Generated Test Cases
Phoenix generators (`mix phx.gen.live`, `mix phx.gen.html`, etc.) provide excellent examples for testing each component.

### 3. `ConnCase` and `DataCase`
Use `HelloWeb.ConnCase` for controller/LiveView tests (provides `conn` and web helpers) and `Hello.DataCase` for context/Ecto tests (manages database sandbox).

**✅ GOOD:**
```elixir
# test/my_app_web/controllers/user_controller_test.exs
defmodule MyAppWeb.UserControllerTest do
  use MyAppWeb.ConnCase, async: true
  alias MyApp.Accounts

  setup [:create_user]

  defp create_user(_) do
    {:ok, user} = Accounts.create_user(%{email: "test@example.com", password: "password"})
    %{user: user}
  end

  test "shows existing user", %{conn: conn, user: user} do
    conn = get(conn, ~p"/users/#{user}")
    assert html_response(conn, 200) =~ user.email
  end
end
```

### 4. LiveView Testing Helpers
For LiveView tests, use `render_hook/2`, `render_component/2`, `form/2`, `assert_patch/2`, etc. When debugging HTML output, use specific element selectors rather than broad string matching.

**✅ GOOD (using custom helpers for clarity):**
```elixir
# test/support/element_helpers.ex
defmodule MyApp.Support.ElementHelpers do
  import Phoenix.LiveViewTest

  def get_by_role(view, :button, text_filter) do
    button = view |> element("button", text_filter)
    assert has_element?(button)
    button
  end

  def get_by_role(view, :textbox, text_filter) do
    label = view |> element("label", text_filter)
    textbox_id = Regex.run(~r/(?<=for=")\w+(?=")/, label |> render) |> Enum.at(0)
    input = view |> element("input##{textbox_id}")
    textbox = if has_element?(input), do: input, else: view |> element("textarea##{textbox_id}")
    assert has_element?(label)
    assert has_element?(textbox)
    textbox
  end
end

# test/my_app_web/live/user_live_test.exs
defmodule MyAppWeb.UserLiveTest do
  use MyAppWeb.ConnCase, async: true
  import Phoenix.LiveViewTest
  import MyApp.Support.ElementHelpers # Import your custom helpers

  test "creates a new user", %{conn: conn} do
    {:ok, lv, _html} = live(conn, ~p"/users/new")

    lv
    |> get_by_role(:textbox, "Email")
    |> render_change(value: "new@example.com")

    lv
    |> get_by_role(:textbox, "Password")
    |> render_change(value: "securepassword")

    lv
    |> get_by_role(:button, "Create User")
    |> render_click()

    assert_patch(lv, ~p"/users")
    assert has_element?(lv.html, "User created successfully.")
  end
end
```

