Elixir Cheatsheet
Phoenix, Ecto, and Production
Use this Elixir reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Phoenix Route Shape
scope "/", MyAppWeb do pipe_through :browser get "/", PageController, :home live "/dashboard", DashboardLive end scope "/api", MyAppWeb do pipe_through :api resources "/users", UserController, only: [:index, :show, :create] end
Requests flow through pipelines (plug chains) into controllers, LiveViews, or channels. mix phx.routes lists every route.
Controller Action
def show(conn, %{"id" => id}) do user = Accounts.get_user!(id) render(conn, :show, user: user) end
Controllers receive a conn and a string-keyed params map. Keep business logic in contexts (MyApp.Accounts), not controllers.
LiveView
defmodule MyAppWeb.CounterLive do use MyAppWeb, :live_view def mount(_params, _session, socket) do {:ok, assign(socket, count: 0)} end def handle_event("inc", _params, socket) do {:noreply, update(socket, :count, &(&1 + 1))} end def handle_info({:tick, n}, socket) do # PubSub/timer messages {:noreply, assign(socket, count: n)} end def render(assigns) do ~H""" <h1>Count: {@count}</h1> <button phx-click="inc">+1</button> """ end end
A LiveView is a process: mount/3 sets initial assigns, phx-click/phx-submit/phx-change bindings hit handle_event/3, and changed assigns diff-patch the DOM over the websocket. Keep assigns small and durable data in the database; subscribe in mount with Phoenix.PubSub.subscribe/2 and broadcast with Phoenix.PubSub.broadcast/3.
Ecto Schema and Changesets
defmodule MyApp.Accounts.User do use Ecto.Schema import Ecto.Changeset schema "users" do field :email, :string field :admin, :boolean, default: false has_many :posts, MyApp.Blog.Post belongs_to :team, MyApp.Accounts.Team timestamps() end def changeset(user, attrs) do user |> cast(attrs, [:email, :admin, :team_id]) |> validate_required([:email]) |> validate_format(:email, ~r/@/) |> unique_constraint(:email) |> foreign_key_constraint(:team_id) end end
Changesets cast and validate external data before it reaches the database; *_constraint functions convert database constraint violations into changeset errors instead of exceptions.
Migrations
mix ecto.gen.migration create_users mix ecto.migrate mix ecto.rollback
defmodule MyApp.Repo.Migrations.CreateUsers do use Ecto.Migration def change do create table(:users) do add :email, :string, null: false add :team_id, references(:teams, on_delete: :delete_all) timestamps() end create unique_index(:users, [:email]) create index(:users, [:team_id]) end end
Queries and Associations
import Ecto.Query Repo.get(User, id) # nil if missing; get! raises Repo.get_by(User, email: email) Repo.all(from u in User, where: u.admin == true, order_by: u.email) Repo.one(from u in User, select: count(u.id)) Repo.insert(changeset); Repo.update(changeset); Repo.delete(user) # associations load explicitly — no lazy loading user = Repo.preload(user, :posts) Repo.all(from u in User, preload: [posts: :comments]) Repo.all( from u in User, join: p in assoc(u, :posts), where: p.published, select: {u.email, count(p.id)}, group_by: u.email )
Accessing an unloaded association returns %Ecto.Association.NotLoaded{} — always preload. Parameterized Ecto queries interpolate values with ^ (where: u.email == ^email); never build SQL strings from user input.
Transactions and Ecto.Multi
Repo.transaction(fn -> order = Repo.insert!(order_changeset) Repo.update!(decrement_stock(item)) order # {:ok, order}; raise/Repo.rollback aborts end) Ecto.Multi.new() |> Ecto.Multi.insert(:order, order_changeset) |> Ecto.Multi.update(:stock, fn %{order: o} -> decrement_stock(o) end) |> Repo.transaction() |> case do {:ok, %{order: order}} -> {:ok, order} {:error, step, changeset, _done} -> {:error, step, changeset} end
Ecto.Multi names each step, threads earlier results into later ones, and tells you exactly which step failed — prefer it over nested Repo.transaction logic.
Releases
MIX_ENV=prod mix assets.deploy MIX_ENV=prod mix release _build/prod/rel/my_app/bin/my_app start _build/prod/rel/my_app/bin/my_app eval "MyApp.Release.migrate()" # run migrations _build/prod/rel/my_app/bin/my_app remote # IEx into a live node
Releases package the BEAM runtime, compiled code, and configuration hooks for deployment.
Runtime Configuration
# config/runtime.exs if config_env() == :prod do config :my_app, MyApp.Repo, url: System.fetch_env!("DATABASE_URL") config :my_app, MyAppWeb.Endpoint, secret_key_base: System.fetch_env!("SECRET_KEY_BASE") end
Use runtime.exs for values only known in the deploy environment — it runs at boot, including inside a release.
Observability
Logger.metadata(request_id: id)— attach request/user context to every log line.- Phoenix and Ecto emit
:telemetryevents; attach handlers or usephoenix_live_dashboardin dev. - Expose health checks for load balancers; monitor supervisor restart intensity.
- Track database pool saturation (
queue_timein Ecto telemetry) and request latency. - Keep crash reports actionable; never log secrets.
Production Rule of Thumb
Pure functions for transformations, contexts for business workflows, changesets for validation, supervised processes for stateful services, telemetry/logging for visibility.