15 Elixir Libraries I Reach For in 2026
The Hex packages I install on day one of a new Elixir project in 2026 — what each earns its place doing, and the ones I dropped along the way.
TL;DR: A curated day-one stack beats the awesome-list dump every time. The famous awesome-elixir page is a thousand links sorted alphabetically — useful as a reference, useless as a decision. This is the opposite: the ~15 packages I actually
mix deps.geton the first morning of a new Elixir project in 2026, the one job each earns its place doing, and the libraries I dropped to get here (HTTPoison, Tesla, Quantum, hand-rolled mocks). The honest tradeoff: a tight stack means you sometimes write 20 lines you could have pulled from a dependency — and that’s the trade I take every time, because a dependency you don’t understand is a liability you can’t debug at 2am.
There’s a moment on every new project where the deps
block in mix.exs is empty and you have to decide who you’re
inviting to the party. Get it right and the next six months are smooth.
Get it wrong — pick the unmaintained thing, the over-abstracted thing,
the thing with no escape hatch — and you spend those months fighting
your own foundation.
I’ve made enough of those mistakes to have opinions. What follows isn’t every good library; it’s the ones I reach for by default, before I know much about the project, because they’ve earned trust across many of them. At the end I’ll give you the rubric I use so you can judge your own stack instead of cargo-culting mine.
A note on what this is not: it’s not a substitute for
mix phx.new. Phoenix, Plug, Telemetry, and friends come in
the box. This is about the deliberate additions on top.
HTTP & talking to the outside world
Req — the single most consequential default I’ve changed in years. Req is a batteries-included HTTP client built on top of Finch, and it is the answer to “what do I use to make an HTTP request in Elixir?” in 2026. Retries, redirects, JSON encode/decode, compression, and streaming all work out of the box with sane defaults. I reach for it the instant a project needs to call anything — a payment API, an LLM endpoint, a webhook.
This is the clearest “what I dropped” story in the whole list. I used to reach for HTTPoison, then Tesla. HTTPoison is effectively in maintenance mode and its ergonomics show their age. Tesla is still actively maintained and genuinely good — its middleware model is powerful — but for most projects Req gives me 95% of what Tesla’s middleware stack did with a fraction of the ceremony. I keep Tesla in my back pocket for the rare case where I need a very specific adapter or a custom middleware pipeline shared across many clients. For everything else, Req. (José Valim maintains it; that’s not why it’s good, but it’s a decent proxy for “this will still be here in three years.”)
Finch —
I rarely write Finch.request/2 directly anymore, because
Req sits on top of it. But Finch is what I name explicitly when I need
connection pooling tuned for a specific high-volume downstream — a
metrics sink, an LLM provider I’m hammering. Knowing it’s the layer
underneath Req is the difference between “the HTTP client is slow” and
“I need a dedicated pool with 50 connections to this one host.” Reach
for it directly when pool topology matters.
Jason —
JSON in and out. It’s so ubiquitous that half your other deps already
pull it in. One honest caveat worth knowing in 2026: Jason’s last
stable release predates much of this list — recent versions on
Hex are alpha-tagged. In practice it’s rock-solid and battle-tested at
enormous scale, so I still install it without hesitation, but it’s worth
keeping an eye on whether the wider ecosystem shifts toward stdlib JSON
(Elixir now ships a built-in JSON module) over the next
year. For now, Jason stays.
Data & Ecto
Ecto + ecto_sql — not optional, not interesting to argue about. If your project touches a database, Ecto is the data-mapping and query layer, full stop. What’s worth saying to newer Elixir developers: resist the urge to treat changesets as annoying boilerplate. They’re the single best place to validate input at the boundary, and leaning into them is how you keep garbage out of your domain. I write changesets even for embedded schemas that never hit a database, purely for the validation pipeline.
The Ecto-adjacent thing I’ll flag: separate queries for
has_many, JOINs for belongs_to. The number of
N+1 problems and accidental Cartesian products I’ve reviewed because
someone reached for a clever single query is not small. Ecto gives you
the tools to do it right; the discipline is on you.
Background work — the part Elixir does better than anyone
Oban — if I could only install one library from this entire list, it would be Oban. Postgres-backed background job processing with a real database transaction guarantee, a UI, cron, uniqueness, rate limiting, and a maturity level that’s frankly rare in any ecosystem. The day-one move on any project with a database: add Oban before you need it, because the first time you reach for “I’ll just spawn a Task” for something that absolutely must not be lost, you’ll wish you had it.
Two non-negotiables I enforce on every Oban job, no exceptions: jobs must be idempotent (they will run twice eventually), and args must use string keys and contain only serializable data — never a struct, never a PID. I’ve written a deeper piece on driving AI agents through Oban queues (coming soon — the durable-execution-for-LLM-calls pattern deserves its own post), because the retry/backoff/uniqueness machinery turns out to be exactly what flaky, expensive model calls need.
Broadway — not every project, but I reach for it the moment the shape is “ingest a firehose and process each message with backpressure” — SQS, Kafka, RabbitMQ, GCP Pub/Sub. Oban is for jobs you enqueue; Broadway is for streams you consume. Knowing which problem you have keeps you from bending one into the other. Skip it entirely if you don’t have a real pipeline — it’s not a default, it’s a when-you-need-it.
A note on scheduling: I no longer install Quantum for new projects. It’s a fine cron-style scheduler, but Oban Cron covers the same ground with the same durability guarantees as the rest of my background work, so I’d rather have one mental model than two. If a project already has Quantum and no Oban, that’s fine — I just don’t add it fresh.
Web & LiveView
Phoenix
LiveView — the reason a one-person team can ship
interactive product without a separate frontend stack. I default to
LiveView for essentially all internal tools and most product UI, and
only reach for a heavier client-side approach when there’s a genuine
offline or ultra-low-latency requirement. The two disciplines that
separate good LiveView from janky LiveView: use streams
for any list that can grow past a hundred items (don’t hold ten thousand
rows in socket assigns), and never run an unconditional DB query in
mount — use assign_async or gate on
connected?/1. I go deeper on the runtime model that makes
this cheap in Elixir’s
concurrency model and on why the BEAM is a natural fit for agentic workloads.
Bandit — the pure-Elixir HTTP server that’s now the default under new Phoenix projects, replacing Cowboy. I don’t usually add it so much as keep it — but I’ll call it out because if you’re upgrading an older app still on Cowboy, moving to Bandit is one of the lower-risk, higher-clarity upgrades available: a stack trace that stays in Elixir instead of disappearing into Erlang internals is worth a lot when something goes wrong.
Testing & developer experience
Mox —
mocks based on explicit behaviours, not magic. This is the one that
enforces an architecture I’d want anyway: define a behaviour for your
boundary (the payment gateway, the LLM client), mock that, and
your production code is forced to depend on a contract rather than a
concrete module. I reach for it on day one specifically because
retrofitting “wrap third-party APIs behind a behaviour” later is
painful. Mox is mature and stable — infrequent releases here are a
feature, not neglect. The thing I dropped: hand-rolled mock modules and
:meck-style runtime patching, both of which let you mock
things that should have been refactored instead.
ExMachina — test
factories. The job it earns its place doing: making test data setup
readable so your tests document intent instead of drowning in struct
literals. Mature, low-cadence, exactly what you want from a factory
library. I’ll note the honest alternative — many teams now prefer plain
factory functions in a test support module over a library — and
that’s a legitimate choice. I still reach for ExMachina out of habit and
because build/insert/params_for
cover the cases I hit; if you’re starting fresh and want zero deps,
plain functions are fine.
StreamData — property-based testing. I don’t property-test everything, but for anything with a parser, a serializer, a state machine, or non-trivial data transformation, generating a thousand random valid inputs finds the edge case your three hand-written examples missed. Reach for it on the gnarly 20% of the codebase; example-based tests for the rest.
Credo — static analysis for consistency. On a solo project it’s a second opinion; on a team it’s how you stop bikeshedding style in code review. I wire it into CI on day one so the conversation is “the linter says” rather than “I personally prefer.”
Dialyxir — the friendly Mix wrapper over Dialyzer for success typing. The honest take: Dialyzer’s error messages can be genuinely cryptic, and the first PLT build is slow. But the class of bug it catches — passing the wrong shape into a function three call-hops away — is exactly the class that’s expensive to catch any other way. I add it, I tune it to not be noisy, and I accept that it’s a long-term investment, not an instant payoff. Skip it on a throwaway prototype; add it the moment the project is going to outlive the quarter.
Sobelow —
security-focused static analysis built specifically for Phoenix. It
catches the Phoenix-shaped footguns: missing CSRF protection,
raw/1 on untrusted content (XSS), SQL injection via string
interpolation, config leaks. Runs in seconds, lives in CI, and the
false-positive rate is low enough that the signal is worth the noise.
Day-one install on anything with a web surface.
The AI-era stack — where Elixir is quietly excellent
This is the section that didn’t exist on my list three years ago, and it’s the reason this post anchors the site’s Elixir-and-AI cluster. The short version: the BEAM is a genuinely good place to run AI workloads — concurrent, fault-tolerant, with first-class streaming — and I’ve argued separately that Elixir is one of the languages AI writes best. Here’s what I install.
Nx — multi-dimensional tensors and numerical computing, the foundation of the entire Elixir ML stack. You rarely use it directly in a product, but it’s the layer everything else stands on, and when you need to do math on embeddings — cosine similarity, normalization — this is where it lives.
Bumblebee — pre-trained transformer models (embeddings, Whisper, BERT-family, and more) running in your BEAM process, no Python sidecar. The single highest-leverage use I reach for: generating embeddings locally for search and RAG instead of paying a per-token API for every chunk. It’s a young library (downloads are modest), so I treat it as “excellent for the embedding/inference cases it covers” rather than “drop-in for any model on HuggingFace.” Verify your specific model is supported before betting on it.
Instructor — structured, schema-validated output from LLMs. You define an Ecto schema, the library coaxes the model into returning data that conforms to it, and validation failures get fed back to the model to retry. The honest status: it’s early-stage with low adoption and an infrequent release cadence, so I wrap it behind my own boundary module (so I can swap the implementation) rather than letting it leak through the codebase. But the pattern — “make the LLM return validated structs, not strings I have to parse” — is non-negotiable for any serious LLM feature, and Instructor is the cleanest expression of it in Elixir today. If you’d rather not take the dependency, the pattern is reproducible in ~50 lines with Req + Ecto changesets + a retry loop.
pgvector — vector
columns and nearest-neighbor search wired into Ecto. This is my honest
recommendation for RAG in Elixir, and it’s deliberately not a
RAG framework. There are dedicated RAG libraries on Hex —
arcana and rag_ex both exist and have had
recent releases — but their adoption is so thin (hundreds to
low-thousands of downloads) that I won’t put them under a production
feature yet. What I actually build with is the boring, durable
combination: pgvector for storage and similarity
search, Bumblebee or an embedding API for the vectors,
and Req for the LLM calls — no framework in the middle.
Postgres you already operate, queries you can read, an escape hatch at
every layer. I’m writing a full walkthrough of this exact stack
(coming soon); the headline is that you do not need a RAG
framework to ship RAG in Elixir, and in 2026 you’re better off without
one.
Operations — the two I add before I think I need them
Hammer — rate limiting with pluggable backends. The day-one case: you have one external API endpoint, one expensive LLM call, one auth route that you do not want hammered, and Hammer gives you a clean token-bucket without standing up Redis if you don’t want to. Cheap to add, painful to retrofit under load.
PromEx — Prometheus metrics plus pre-built Grafana dashboards for the libraries you’re already running (Phoenix, Ecto, Oban, the BEAM itself). I add observability before the incident, not during it. The “before I need it” framing is the whole point: the metrics you wish you had are always the ones you didn’t instrument.
tzdata
— the unglamorous one. If your app does anything with time zones, you
need a tz database, and tzdata is it. I mention it only
because forgetting it produces confusing runtime errors the first time
you try to shift a datetime into a named zone. Install it, configure it
to auto-update, move on.
The rubric — how to judge your own stack
I didn’t pick these by popularity. Each one clears four bars, and you can apply the same bars to anything you’re tempted to add:
| Criterion | The question I ask |
|---|---|
| Maintained | Is there a recent release, or is recency simply unnecessary because it’s done? (Mox being quiet is fine; a half-finished thing being quiet is not.) |
| Does one thing | Can I describe its job in a single sentence? Libraries that do five things are five things that can break. |
| Escape hatch | When it doesn’t do what I need, can I drop to the layer below — or am I trapped inside an abstraction? (Req → Finch is the model here.) |
| Debuggable at 2am | When it fails in production, does the stack trace make sense, and could I read the source if I had to? |
A dependency that fails any of these is one I’d rather replace with code I own. That’s the trade behind the whole list: I’ll write a little more myself to depend on a little less that I don’t understand.
The verdict
| Layer | Reach for | Skip / dropped |
|---|---|---|
| HTTP | Req (Finch underneath) | HTTPoison; Tesla unless you need its middleware |
| JSON | Jason (watch stdlib JSON) |
— |
| Data | Ecto + ecto_sql | — |
| Background jobs | Oban | Quantum (Oban Cron covers it) |
| Stream ingestion | Broadway (when you have a pipeline) | — |
| Web UI | Phoenix LiveView + Bandit | Heavy SPA unless truly needed |
| Testing | Mox, ExMachina, StreamData | hand-rolled mocks; :meck |
| Static analysis | Credo, Dialyxir, Sobelow | — |
| AI / ML | Nx, Bumblebee, Instructor, pgvector | dedicated RAG frameworks (too early) |
| Ops | Hammer, PromEx, tzdata | — |
Fifteen-ish names, but really one principle: a small stack of well-understood, single-purpose libraries with escape hatches beats a sprawling one you can’t fully reason about. Steal the ones that fit your project, run them through the rubric, and drop anything that fails it — including, if it fails for you, anything on this list.
If you’re building on the BEAM for AI specifically, start with why the runtime fits agentic work, then the concurrency model that makes it cheap. The Oban-for-agents and pgvector-RAG deep dives are next in the cluster — and they’re built on exactly this stack.