Curated library of 38 atomic skills, 7 personas, and 1 orchestrator for Elixir and Phoenix development. Organized by category: fundamentals, phoenix, database, testing, auth, infrastructure, quality, security, integrations, tooling, frameworks, personas, and orchestration. Covers core Elixir patterns, Phoenix LiveView, Ecto, OTP, Oban, testing, security, deployment, real-time, and modern tooling (Req, Swoosh, Cachex, Broadway, Ash).
73
91%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Advisory
Suggest reviewing before use
Cachex.fetch/3 for get-or-set — never check-then-set (race condition risk)Cachex.del/2 after any data mutationFollow this sequence when adding caching to a feature:
{:cachex, "~> 3.6"} to mix.exs and run mix deps.getCachex.fetch/3 for atomic cache-aside patternCachex.del/2 after mutating datastats: true in cache optionsCachex.stats/1 and confirm hit_rate is non-zero; see Monitoring Cache Stats for interpretation thresholds and remediation guidanceCachex operations return {:ok, result} or {:error, reason}. Always handle errors gracefully:
def get_user(id) do
case Cachex.fetch(:my_cache, "user:#{id}", fn _ ->
{:commit, Repo.get(User, id)}
end) do
{:ok, user} -> user
{:error, _} -> Repo.get(User, id) # Fallback to database
end
end# mix.exs
defp deps do
[
{:cachex, "~> 3.6"}
]
endIn your application supervisor:
# lib/my_app/application.ex
defmodule MyApp.Application do
use Application
@impl true
def start(_type, _args) do
children = [
# Basic cache with 1000 entry limit
{Cachex, name: :my_cache, limit: 1000},
# Cache with stats enabled for monitoring
{Cachex, name: :stats_cache, limit: 5000, stats: true},
# Cache with TTL and LRW eviction policy
{Cachex,
name: :ttl_cache,
limit: 10_000,
ttl: :timer.minutes(10),
policy: Cachex.Policy.LRW}
]
Supervisor.start_link(children, strategy: :one_for_one)
end
endcase Cachex.get(:my_cache, "user:123") do
{:ok, nil} -> :miss
{:ok, value} -> {:hit, value}
end
# put with explicit TTL (overrides cache-level default)
Cachex.put(:my_cache, "session:abc", data, ttl: :timer.minutes(30))
# Atomic delete — returns {:ok, true | false}; false means key was absent
Cachex.del(:my_cache, "user:123")
# Wipe entire cache
Cachex.clear(:my_cache){status, value} =
Cachex.fetch(:my_cache, "user:123", fn key ->
user = MyApp.Accounts.get_user(123)
{:commit, user, ttl: :timer.minutes(5)}
end)
case status do
:ok -> IO.puts("Cache hit")
:commit -> IO.puts("Cache miss - computed and cached")
enddefmodule MyApp.CacheWarmer do
use Cachex.Warmer
def execute(state) do
users = MyApp.Accounts.list_active_users()
actions =
Enum.map(users, fn user ->
{:put, "user:#{user.id}", user, ttl: :timer.hours(1)}
end)
{:ok, actions}
end
end
children = [
{Cachex,
name: :my_cache,
warmers: [
%Cachex.Warmer{module: MyApp.CacheWarmer, interval: :timer.minutes(5)}
]}
]children = [
{Cachex,
name: :my_cache,
limit: 10_000,
policy: Cachex.Policy.LRW,
ttl: :timer.minutes(10),
stats: true,
hooks: [
%Cachex.Hook{module: MyApp.CacheLogger}
]}
]defmodule MyApp.Accounts do
def update_user(user, attrs) do
with {:ok, updated_user} <- Repo.update(User.changeset(user, attrs)) do
case Cachex.del(:my_cache, "user:#{user.id}") do
{:ok, _} ->
:ok
{:error, reason} ->
Logger.warning("Cache invalidation failed for user:#{user.id}: #{inspect(reason)}")
end
{:ok, updated_user}
end
end
def get_user(id) do
Cachex.fetch(:my_cache, "user:#{id}", fn _key ->
user = Repo.get(User, id)
{:commit, user, ttl: :timer.minutes(5)}
end)
|> elem(1)
end
end{Cachex, name: :my_cache, stats: true}
{:ok, stats} = Cachex.stats(:my_cache)
# %{hit_rate: 85.5, hits: 8550, misses: 1450, gets: 10000, sets: 1200, evictions: 100}Interpret hit rate:
> 80% — excellent, cache is very effective60-80% — good, normal for read-heavy workloads< 60% — investigate TTL values and check for over-invalidation< 20% — cache may be ineffective; revisit TTL strategy and key design before deploying to productionEmit telemetry events for cache operations to monitor in production:
defmodule MyApp.Telemetry do
def execute(:cache, :hit, cache_name, key) do
:telemetry.execute(
[:my_app, :cache, cache_name],
%{hits: 1},
%{key: key}
)
end
def execute(:cache, :miss, cache_name, key) do
:telemetry.execute(
[:my_app, :cache, cache_name],
%{misses: 1},
%{key: key}
)
end
end
def get_user(id) do
case Cachex.fetch(:my_cache, "user:#{id}", fn _ ->
{:commit, Repo.get(User, id)}
end) do
{:ok, user} ->
Telemetry.execute(:cache, :hit, :my_cache, "user:#{id}")
user
{:error, _} ->
Telemetry.execute(:cache, :miss, :my_cache, "user:#{id}")
Repo.get(User, id)
end
endBroadcast-based invalidation across nodes:
defmodule MyApp.CacheSync do
@topic "cache:invalidate"
def broadcast_delete(key) do
Phoenix.PubSub.broadcast(MyApp.PubSub, @topic, {:invalidate, key})
end
def handle_info({:invalidate, key}, state) do
Cachex.del(:my_cache, key)
{:noreply, state}
end
end
# Subscribe in your GenServer or LiveView
Phoenix.PubSub.subscribe(MyApp.PubSub, "cache:invalidate")Remote reads via RPC (single authoritative node pattern):
def get_user_distributed(id) do
case Cachex.get(:my_cache, "user:#{id}") do
{:ok, nil} ->
:rpc.call(primary_node(), Cachex, :get, [:my_cache, "user:#{id}"])
|> case do
{:ok, nil} -> fetch_from_db(id)
{:ok, value} -> value
end
{:ok, value} ->
value
end
end
defp primary_node, do: Application.fetch_env!(:my_app, :primary_cache_node)