DEV Community

Cover image for Ecto for Elixir: A Beginner's Guide to Database Interactions
Rafael Andrade
Rafael Andrade

Posted on

1

Ecto for Elixir: A Beginner's Guide to Database Interactions

Introduction

During my journey learning Elixir, I discovered Ecto, a powerful database wrapper and query generator that enables seamless interaction with SQL databases. While often compared to ORMs like Entity Framework Core or Ruby on Rails' ActiveRecord, Ecto diverges by avoiding automatic state tracking, requiring developers to manage changes explicitly. This article explores Ecto's core concepts, including repositories, schemas, migrations, and CRUD operations. For advanced features, refer to Ecto’s official documentation.

What is Ecto?

Ecto is a database wrapper and query generator for Elixir, designed to work with relational databases like PostgreSQL and MySQL. It provides:

  • Map database tables to Elixir structs (via schemas).
  • Generate type-safe queries using Elixir syntax.
  • Validate data before persistence (via changesets).
  • Manage schema evolution with version-controlled migrations .

Why Ecto Isn’t a Traditional ORM

Ecto avoids the pitfalls of traditional ORMs by:

  1. No Automatic State Tracking: Unlike ORMs, Ecto does not track entity states (e.g., dirty or changed fields).
  2. Explicit Data Flow: Developers must manually pass data through changesets before persistence.
  3. Functional Paradigm: Ecto aligns with Elixir’s functional programming model, avoiding the "impedance mismatch" of object-relational mapping.

Requirement

  • Elixir 1.18+ (adjust for older versions if needed)
  • Dependencies in mix.exs
{:ecto_sql, "~> 3.0"},  # Ecto
{:postgrex, ">= 0.0.0"} # PostgreSQL driver
Enter fullscreen mode Exit fullscreen mode

Setting Up a Repository

A repository (or repo) is Ecto's interface to the database, similar to Entity Framework's DbContext.

Manual Setup

  1. Define a repo module:
defmodule Friends.Repo do
  use Ecto.Repo,
      otp_app: :friend,
      adapter: Ecto.Adapters.Postgres
end
Enter fullscreen mode Exit fullscreen mode
  1. Configure in config/config.exs:
config :friends, Friends.Repo,
  database: "friends",
  username: "user",
  password: "pass",
  hostname: "localhost"
Enter fullscreen mode Exit fullscreen mode

Automatic Setup via Mix Task

Run:

mix ecto.gen.repo -r Friends.Repo
Enter fullscreen mode Exit fullscreen mode

Running Ecto at Startup

Add the repo to your application supervisor in lib/<app_name>/application.ex:

def start(_type, _args) do
  children = [Friends.Repo]
  ...
end
Enter fullscreen mode Exit fullscreen mode

Then update the config/config.exs

config :friends, ecto_repos: [Friends.Repo]
Enter fullscreen mode Exit fullscreen mode

Creating a Schema

A schema maps a database table to an Elixir struct. For example, a Person schema:

defmodule Friends.Person do
  use Ecto.Schema
  import Ecto.Changeset

  schema "people" do
    field :first_name, :string
    field :last_name, :string
    field :age, :integer
  end

  def changeset(person, params \\ %{}) do
    person
    |> cast(params, [:first_name, :last_name, :age])
    |> validate_required([:first_name, :last_name])
  end
end
Enter fullscreen mode Exit fullscreen mode

Migrations

Migrations define database schema changes incrementally.

Manual Migration

Create a file in priv/repo/migrations/<datetime>_create_people.exs:

defmodule Friends.Repo.Migrations.CreatePeople do
  use Ecto.Migration

  def change do
    create table(:people) do
      add :first_name, :string
      add :last_name, :string
      add :age, :integer
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Automatic Migration via Mix Task

Run:

mix ecto.gen.migration create_people
Enter fullscreen mode Exit fullscreen mode

This command will create and empty migration file.

defmodule Friends.Repo.Migrations.CreatePeople do
  use Ecto.Migration

  def change do

  end
end
Enter fullscreen mode Exit fullscreen mode

Execute the migration

Run:

mix ecto.create # create the your database
mix ecto.migrate # run your migrations
Enter fullscreen mode Exit fullscreen mode

CRUD Operations

Create

Insert a new record:

person = %Friends.Person{first_name: "Alice", last_name: "Smith", age: 30}
{:ok, inserted_person} = Friends.Repo.insert(person)
Enter fullscreen mode Exit fullscreen mode

With validation:

changeset = Friends.Person.changeset(%Friends.Person{}, %{first_name: "Alice"})
case Friends.Repo.insert(changeset) do
  {:ok, person} -> # Success
  {:error, changeset} -> # Handle errors
end
Enter fullscreen mode Exit fullscreen mode

Read

Fetch records:

# By ID
Friends.Repo.get(Friends.Person, 1)

# First record
Friends.Repo.one(from p in Friends.Person, order_by: [asc: p.id], limit: 1)

# All records matching a condition
Friends.Repo.all(from p in Friends.Person, where: like(p.first_name, "A%"))
Enter fullscreen mode Exit fullscreen mode

Update

Update an existing record:

person = Friends.Repo.get!(Friends.Person, 1)
changeset = Friends.Person.changeset(person, %{age: 31})
Friends.Repo.update(changeset)
Enter fullscreen mode Exit fullscreen mode

Delete

Delete a record:

person = Friends.Repo.get!(Friends.Person, 1)
Friends.Repo.delete(person)
Enter fullscreen mode Exit fullscreen mode

That method will return a similar value as creating, but change the action to :delete

Conclusion

Ecto strikes a balance between abstraction and control, offering:

  • Type-Safe Queries: Compile-time macro-generated queries prevent runtime errors.
  • Explicit Workflows: Changesets enforce data validation before persistence.
  • Community Backing: Widely adopted in the Elixir ecosystem.

While not a traditional ORM, Ecto’s functional design avoids leaky abstractions common in object-relational mappers. For advanced patterns (e.g., associations), explore its support for has_many, belongs_to, and many_to_many relationships.

Reference

Heroku

Built for developers, by developers.

Whether you're building a simple prototype or a business-critical product, Heroku's fully-managed platform gives you the simplest path to delivering apps quickly — using the tools and languages you already love!

Learn More

Top comments (0)

DevCycle image

Fast, Flexible Releases with OpenFeature Built-in

Ship faster on the first feature management platform with OpenFeature built-in to all of our open source SDKs.

Start shipping

Join the Runner H "AI Agent Prompting" Challenge: $10,000 in Prizes for 20 Winners!

Runner H is the AI agent you can delegate all your boring and repetitive tasks to - an autonomous agent that can use any tools you give it and complete full tasks from a single prompt.

Check out the challenge

DEV is bringing live events to the community. Dismiss if you're not interested. ❤️