elixir
Use when building Elixir applications with Phoenix and OTP — JSON APIs described with OpenAPI, scheduled work with supervised processes, outbound webhooks (for example a daily chat digest), ExUnit tes
SKILL.mdskills/languages/elixir/SKILL.md
GitHubElixir, Phoenix and OTP
Default: current Elixir and Erlang/OTP, Phoenix for HTTP (API-only with --no-html --no-assets when there is no UI), Req for HTTP clients, ExUnit, mix release in a Docker image.
Setup
mix phx.new app --no-ecto --no-html --no-assetsfor a pure API (keep Ecto if you store data).- Format and lint from day one:
mix format,mix credo(optional),mix compile --warnings-as-errors. - Pin Elixir/OTP versions in
.tool-versionsand in the Dockerfile base image together.
Choosing the process shape
| Need | Use |
|---|---|
| Pure computation | Plain module functions (no process) |
| One-off concurrent work | Task / Task.async_stream under a Task.Supervisor |
| State or a timer that must survive errors | GenServer started by the application supervisor |
| Many independent workers | DynamicSupervisor + Registry |
Start with plain functions; add a process only when you need state, concurrency or fault isolation.
Periodic jobs
- A
GenServerthat schedules itself withProcess.send_after(self(), :tick, ms)ininit/1and again inhandle_info/2. - Compute the next run from the wall clock (e.g. next 07:00 in the configured time zone), not a fixed interval, so drift and restarts do not shift it.
- Make the job idempotent per day (remember "sent for date X") so a restart does not send twice.
- Wrap the work in
try/rescueor aTaskso a failure is logged and the schedule continues; otherwise a crash loop can exhaust the supervisor's restart budget.
APIs and webhooks
- Describe the JSON API with an OpenAPI 3 document (e.g. via
open_api_spex) and test that responses match it; clients can then be generated. - Send outgoing webhooks with
Req, a timeout and a small retry; keep the webhook URL in runtime configuration (config/runtime.exsreading the environment), never in source.
Testing
mix testwithasync: truewhere tests do not share global state.- Inject the clock and the HTTP client (behaviours + Mox, or a function argument) so schedule logic is testable without waiting.
Verification loop
mix format --check-formattedandmix compile --warnings-as-errors.mix test.- Build the release image and hit a health endpoint.
Pitfalls
- Compile-time vs runtime config: values in
config/config.exsare baked in at build time; secrets and URLs belong inconfig/runtime.exs. - Large mailboxes: a GenServer that receives faster than it handles grows memory without bound; use back-pressure or
callinstead ofcast. GenServer.calldefault timeout is 5 s; long work inside a call blocks every other caller.- Time zones need a database package (
tzdataortz) configured;DateTimealone only knows UTC. - Atoms are not garbage-collected; never convert untrusted strings with
String.to_atom/1.