Files
elixir-patterns/comparison/elixir-vs-phoenix.md
T
Aaron Weiker 2e7a822b6b docs: idiomatic Elixir and Phoenix patterns with verified source citations
Extracted patterns from Elixir core and Phoenix source code with specific
file:line citations, then verified all citations against the actual source
in a second pass.

Structure:
- patterns/ — Elixir core patterns (GenServer, errors, data, types, etc.)
- phoenix/ — Phoenix-specific patterns and deviations
- comparison/ — Elixir vs Phoenix side-by-side
- smells/ — Anti-patterns and common mistakes
- changelog/ — Daily Elixir/Phoenix PR digest (auto-updated)
2026-04-29 22:59:17 -07:00

6.7 KiB

Elixir Core vs Phoenix: Side-by-Side Comparison

How the same concepts are approached differently (or similarly) between Elixir core and Phoenix.

Process Lifecycle

Aspect Elixir Core Phoenix
Default restart :permanent (GenServer, Supervisor) :temporary (Channel)
Hibernation Not set by default 15s idle → hibernate (Channel)
Process identity Registry :via tuples Topic-based (channels identified by topic)
Supervision Direct supervisor reference Endpoint supervisor manages all

Source (Elixir): lib/elixir/lib/gen_server.ex:911-919 (child_spec defaults to :permanent via Supervisor.child_spec) Source (Phoenix): lib/phoenix/channel.ex:464-472 (explicit restart: :temporary)


Error Handling

Aspect Elixir Core Phoenix
Exception design Minimal struct fields HTTP-aware (plug_status)
Bang functions File.read! raises broadcast! raises
Failure response {:error, reason} tuple {:error, reason} + HTTP status
Recovery Supervisor restart Client reconnection

Source (Elixir): lib/elixir/lib/agent.ex:187 (standard on_start type: {:ok, pid} | {:error, ...}) Source (Phoenix): lib/phoenix/router.ex:2-6 (NoRouteError with plug_status: 404)


Behaviour Design

Aspect Elixir Core Phoenix
Required callbacks Most are optional Only join/3 required (Channel)
__using__ generates child_spec/1 + @behaviour child_spec + behaviour + config + imports
Configuration Via use Module, opts Via use Module, opts + module attributes
Before-compile Rarely used Heavily used (routes, intercepts)

Source (Elixir): lib/elixir/lib/gen_server.ex:899-919 (using generates child_spec + @behaviour) Source (Phoenix): lib/phoenix/channel.ex:450-485 (using generates child_spec + behaviour + DSL setup)


Macro Usage

Aspect Elixir Core Phoenix
Philosophy Minimal, prefer functions Justified by performance
__using__ Generates 1-2 functions Generates functions + sets up DSL
DSL creation Avoided (except Kernel/SpecialForms) Embraced (Router DSL)
Attribute accumulation Rare Central pattern (routes, sockets)

Source (Elixir): lib/elixir/lib/gen_server.ex:899 — simple __using__ (behaviour + child_spec + defaults) Source (Phoenix): lib/phoenix/router.ex:288-312 — complex DSL setup with attribute accumulation, imports, and @before_compile


Module Organization

Aspect Elixir Core Phoenix
File naming gen_server.ex (snake_case) controller.ex (snake_case)
Nesting 2 levels max (Task.Supervised) 2-3 levels (Phoenix.Channel.Server)
Internal modules @moduledoc false @moduledoc false
Public API Functions on the main module Functions + macros on the main module

Both follow the same convention: public API on the parent module, implementation details in nested submodules with @moduledoc false.


State Management

Aspect Elixir Core Phoenix
Agent Simple state, function-based access Socket assigns (assign/2)
GenServer Full control, handle_call/cast/info Channel handles (same callbacks)
State shape Any term (developer's choice) %Socket{} struct (framework-defined)
State access Direct in callbacks Via socket.assigns

Source (Elixir): lib/elixir/lib/agent.ex:62-82 (compute in server vs client pattern) Source (Phoenix): lib/phoenix/channel.ex:463 (import Phoenix.Socket, only: [assign: 3, assign: 2])


Documentation

Aspect Elixir Core Phoenix
Moduledoc size Very large (GenServer: 530 lines) Large (Router: ~260 lines)
Examples Doctests (verified by tests) Examples in docs (not always doctests)
Admonitions Info blocks for use Info blocks for use
Guides Linked from moduledoc Linked from moduledoc
Deprecation @doc deprecated: "Use X instead" Inline comments (TODO markers)

Both use the same documentation infrastructure (ExDoc), but Elixir core tends toward more exhaustive docs (GenServer's moduledoc is essentially a tutorial).


Configuration

Aspect Elixir Core Phoenix
Compile-time Module attributes Application.compile_env
Runtime Application env / init args config/2 callback + Application env
Per-instance Options to start_link Endpoint config per environment

Source (Phoenix): lib/phoenix/endpoint.ex:422-430 (compile-time config checking)

var!(code_reloading?) =
  Application.compile_env(@otp_app, [__MODULE__, :code_reloader], false)

This pattern — reading config at compile time and validating it against runtime — is Phoenix-specific. Elixir core reads config only at runtime.


Telemetry

Aspect Elixir Core Phoenix
Built-in events None (telemetry is a separate library) Extensive event catalog
Instrumentation Manual by library authors Baked into router, endpoint, socket
Event naming Varies by library [:phoenix, :component, :phase] convention
Logging Logger calls Telemetry → Logger adapter (Phoenix.Logger)

Source (Phoenix): lib/phoenix/logger.ex:7-50 (telemetry event catalog) Source (Phoenix): lib/phoenix/router.ex:400-438 (telemetry in router dispatch)

Phoenix wraps every request dispatch in telemetry start/stop/exception events. This provides distributed tracing, monitoring, and logging without any application code changes.


Testing

Aspect Elixir Core Phoenix
Test helper ExUnit.Case Phoenix.ConnTest, Phoenix.ChannelTest
Test subject Module functions Endpoint (full plug pipeline)
Communication Direct function calls HTTP verbs (ConnTest), messages (ChannelTest)
Isolation Process per test Process per test + sandbox (Ecto)

Source (Phoenix): lib/phoenix/test/conn_test.ex:1-30 (endpoint-based integration testing) Source (Phoenix): lib/phoenix/test/channel_test.ex:1-30 (process-based channel testing)

Phoenix test helpers test at the integration level by default — ConnTest dispatches through the full plug pipeline, ChannelTest exercises the full channel lifecycle via message passing. This catches middleware bugs that unit tests miss.