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
Sections: End-to-End Workflow · Quick-Reference: Request Types · Retries · Streaming Responses
Follow this sequence when integrating an external API:
Step 1 — Add dependency
# mix.exs
defp deps do
[
{:req, "~> 0.5"}
]
endCheckpoint: run mix deps.get and confirm Req compiles without errors.
Step 2 — Create a configured client module
defmodule MyApp.ApiClient do
def base_request do
Req.new(
base_url: Application.get_env(:my_app, :api_base_url),
headers: [{"authorization", "Bearer #{api_token()}"}],
receive_timeout: 30_000,
retry: :transient
)
end
def fetch_user(id) do
case Req.get(base_request(), url: "/users/#{id}") do
{:ok, %{status: 200, body: body}} -> {:ok, body}
{:ok, %{status: 404}} -> {:error, :not_found}
{:ok, %{status: 429}} -> {:error, :rate_limited} # extend with additional codes as needed
{:ok, %{status: status}} when status >= 500 -> {:error, :server_error}
{:ok, %{status: status}} -> {:error, {:unexpected_status, status}}
{:error, %Mint.TransportError{reason: :timeout}} -> {:error, :timeout}
{:error, exception} -> {:error, Exception.message(exception)}
end
end
defp api_token, do: Application.get_env(:my_app, :api_token)
endCheckpoint: verify the module compiles with mix compile.
Step 3 — Test with Req.Test before touching a real API
defmodule MyApp.ApiClientTest do
use ExUnit.Case, async: true
setup do
Req.Test.adapter(MyApp.ApiClient)
:ok
end
test "fetches user successfully" do
Req.Test.stub(MyApp.ApiClient, fn conn ->
Req.Test.json(conn, %{"id" => 1, "name" => "John"})
end)
assert {:ok, %{"name" => "John"}} = MyApp.ApiClient.fetch_user(1)
end
test "handles not found" do
Req.Test.stub(MyApp.ApiClient, fn conn ->
conn |> Plug.Conn.put_status(404) |> Req.Test.json(%{"error" => "not found"})
end)
assert {:error, :not_found} = MyApp.ApiClient.fetch_user(999)
end
endCheckpoint: run mix test — all stubs must pass before using the real API.
Step 4 — Verify in IEx against the real endpoint
iex> MyApp.ApiClient.fetch_user(1)
{:ok, %{"id" => 1, "name" => "John", ...}}Checkpoint: confirm a {:ok, body} tuple is returned; check logs for retry warnings if the request is slow.
| Pattern | Example |
|---|---|
| GET | Req.get!(url, params: %{page: 1}) |
| POST JSON | Req.post!(url, json: %{name: "John"}) |
| POST form | Req.post!(url, form: [username: "john", password: "secret"]) |
| With error handling | Use Req.get/1 (not bang) and pattern match {:ok, %{status: _, body: _}} / {:error, _} |
# Automatic retries for transient failures
Req.get!("https://api.example.com/data",
retry: :transient, # Retry on 5xx and network errors
retry_delay: &(&1 * 1000), # Exponential backoff: 1s, 2s, 4s, ...
max_retries: 3, # Max 3 retries
retry_log_level: :warning
)
# Custom retry logic (e.g. also retry on 429)
Req.get!("https://api.example.com/data",
retry: fn response ->
case response do
%{status: 429} -> true
%{status: s} when s >= 500 -> true
_ -> false
end
end,
max_retries: 3
)# Stream large responses to a file
Req.get!("https://api.example.com/large-file",
into: File.stream!("download.txt")
)
# Stream with a callback
Req.get!("https://api.example.com/stream",
into: fn {:data, data}, {req, resp} ->
IO.puts("Received #{byte_size(data)} bytes")
{:cont, {req, resp}}
end
)