🐿️ SquirrElix β€” type-safe SQL in Elixir

SquirrElix (package squirr_elix)

Hex Docs

SquirrElix is a simple database interface that aims to reduce cognitive load when working with SQL through Postgres and Postgrex.

You stay close to the database by writing plain .sql files, which are converted into typed Elixir modules. You write one query per file; the generator discovers those files, resolves parameter and return types, and writes a sibling sql.ex module with @spec-annotated functions that run the queries through Postgrex.

For many applications that is enough: generated convenience functions and typing, full control over your SQL, no extra abstraction to learn, and easier visibility when you need to optimise queries.

SquirrElix requires Elixir ~> 1.18 (stdlib JSON). Elixir 1.20+ is recommended for the compiler’s gradual typing and typechecking. Generated Dialyzer @specs remain part of the codegen story on all supported versions.

SquirrElix is an independent Elixir library inspired by Gleam Squirrel. It reimplements similar SQL-file discovery, inference, and codegen ideas in native Elixir with Elixir-native typing and idioms. It is not affiliated with, endorsed by, or maintained by the Gleam Squirrel project.

What's SquirrElix?

Database access in Elixir is often handled using an ORM such as Ecto, or by writing something like this:

def find_user(conn, id) do
Postgrex.query!(
conn,
"select name, age from users where id = $1",
[id]
)
|> Map.fetch!(:rows)
|> Enum.map(fn [name, age] -> %{name: name, age: age} end)
end

That works for a few queries, but it grows painful: SQL is a plain string (no editor SQL niceties), you cannot easily run the query with external tools, and row decoding drifts out of sync with the select list.

SquirrElix takes a different approach: instead of hiding SQL, it embraces it. Write queries in plain *.sql files; SquirrElix generates the corresponding typed functions.

-- lib/my_app/accounts/sql/find_user.sql
-- Find a user and their age given their id.
select
name,
age
from
users
where
id = $1
mix squirrelix.gen --infer
alias MyApp.Accounts.SQL
rows = SQL.find_user(conn, 42)
# => [%{name: "Ada", age: 36}]

You keep plain SQL files (editor support, explain, external tools) while SquirrElix keeps encoders, decoders, and @specs in sync.

When SquirrElix may not be the right fit

SquirrElix is opinionated: Postgres only, one query per file, convention over configuration, and typed SQL codegen (default: Postgrex). Optional --runner ecto is for Repo connection ownership only β€” not schemas or an ORM replacement.

Consider something else when:

Alternatives:Ecto / Ecto.Query, direct Postgrex, or Gleam Squirrel on Gleam. Using SquirrElix alongside Ecto (migrations/schemas via Ecto; typed .sql via SquirrElix) is a common Phoenix setup β€” see the Phoenix + CI Cookbook.

Requirements

Installation

Add SquirrElix as a dev/test dependency and keep Postgrex as a runtime dependency:

def deps do
[
{:squirr_elix, "~> 0.5.0", only: [:dev, :test], runtime: false},
{:postgrex, "~> 0.22"},
# Optional β€” only for `mix squirrelix.gen --watch`
{:file_system, "~> 1.0", only: [:dev, :test], runtime: false}
]
end

Then mix deps.get. Full docs: https://hexdocs.pm/squirr_elix (or mix docs locally).

Quick start

Put one SQL query per file under a sql/ directory:

lib/my_app/accounts/sql/find_user.sql β†’ MyApp.Accounts.SQL.find_user/2
lib/my_app/accounts/sql.ex # generated
mix squirrelix.gen --infer --database my_app_dev
mix squirrelix.check --infer --database my_app_dev # CI drift check

Copy-pasteable GitHub Actions workflows live in examples/github-actions/. Walkthrough: Getting Started.

Mix tasks

Both accept the same query-source / connection options (--infer, --metadata, DATABASE_URL, PG*, optional --repo / --runner, …). Details: Configuration.

Guides

GuideTopics
Getting StartedInstall, layout, first generate, soft companions
Writing QueriesNaming, comments, nullability, commands
TypesPostgres β†’ Elixir mapping, unsupported types
ConfigurationInfer vs metadata, SSL, watch, public API, CI
Phoenix + CI CookbookMigrate-then-gen, Mix aliases, optional Repo runner
Adopter CI workflowsCopy-pasteable GitHub Actions

FAQ

What flavour of SQL does SquirrElix support?

Postgres only, versions >= 16 (for --infer).

Why isn't SquirrElix highly configurable?

Convention over configuration: the same sql/ layout and sql.ex modules everywhere, less bike-shedding about where queries live.

Can SquirrElix read my .env file?

No. Use your shell, direnv, or similar so the environment owns env vars β€” not the application.

How do I deal with nullable query parameters?

Postgres does not expose parameter nullability. See Writing Queries for workarounds.

Why aren't Postgres composite types supported?

Intentional reject-with-hints policy (flat required/1 row maps). Select fields or cast in SQL β€” see Types.

Does SquirrElix integrate with Ecto Repo?

Optionally, for connection ownership only β€” not as an ORM:

It does not produce schema structs, changesets, or Multi. Default remains Postgrex. See What the optional Repo integration is for.

What is intentionally out of scope?

Product non-goals for 1.0 (for example PostGIS, ULID, a built-in SQL formatter) are listed in the roadmap β€” intentional boundaries, not a backlog.

Errors and troubleshooting

ErrorTypical causeWhat to do
OutdatedFileSQL changed, sql.ex not regeneratedRun mix squirrelix.gen
CannotOverwriteFileNon-generated file would be overwrittenRemove or rename the existing sql.ex
PostgresSyntaxErrorInvalid SQLFix the query and re-run
MissingPostgresTable / MissingPostgresColumnSchema mismatchApply migrations before --infer
DuplicateReturnColumnsDuplicate result column namesAdd as aliases
QueryFileHasInvalidNameInvalid Elixir function filenameRename the .sql file
UnsupportedPostgresTypeType not mappedSee Types; hints in the error
MissingQueryMetadata / MissingQueryMetadataFieldIncomplete metadataFix squirr_elix.exs or use --infer
InvalidQueryMetadataFileMetadata not a map / eval failedFix or regenerate with --write-metadata
CannotConnectToPostgres / PostgresConnectionTimeoutInfer cannot reach DBCheck PG* / URL; or use metadata mode

Generation is project-wide atomic (query errors refuse all writes; write pass uses temp + rename with rollback). See Configuration.

Relationship to Gleam Squirrel

SquirrElix is an independent project inspired by Gleam Squirrel by Giacomo Cavalieri. It is not an official port of that project and is not affiliated with its authors or maintainers. SquirrElix follows similar query conventions β€” one query per file, sql/ directory layout, comment-to-doc mapping, parameter name inference, and Postgres type inference β€” while producing idiomatic Elixir output:

Both projects are licensed under Apache 2.0. See LICENSE and NOTICE for attribution. This package also draws inspiration from yesql and sqlx.

Contributing

Maintainer notes live under docs/ on GitHub (not published on HexDocs):

License

SquirrElix is licensed under the Apache License 2.0. See LICENSE.