What Is Ecto?
Ecto is Elixir's database toolkit — not exactly an ORM in the traditional sense, since it deliberately avoids hiding SQL behind object mapping magic. It's composed of four main pieces: Ecto.Repo (the interface to your database), Ecto.Schema (mapping database tables to Elixir structs), Ecto.Changeset (validating and casting data before writes), and Ecto.Query (a composable, type-checked DSL for building SQL queries).
Cricket analogy: Ecto acts as the team's official scorer, translating raw play (database rows) into a structured scorecard (Elixir structs) that the rest of the team can trust.
Schemas and Changesets
A schema defines the shape of a table using schema/2, listing fields and their types, e.g., field :title, :string. A changeset takes a struct (or schema) and a map of parameters, casts the allowed fields with cast/3, then applies validations like validate_required/2 or validate_length/3. Only a valid changeset can be successfully passed to Repo.insert/1 or Repo.update/1 — invalid ones return {:error, changeset} with structured error messages.
Cricket analogy: A changeset is like the third umpire reviewing a run-out — it checks the incoming data against the rules (validations) before confirming it's out, rejecting anything that doesn't hold up.
defmodule MyApp.Blog.Post do
use Ecto.Schema
import Ecto.Changeset
schema "posts" do
field :title, :string
field :body, :string
field :published, :boolean, default: false
timestamps()
end
def changeset(post, attrs) do
post
|> cast(attrs, [:title, :body, :published])
|> validate_required([:title, :body])
|> validate_length(:title, min: 3, max: 200)
end
endQuerying with Ecto.Query and Repo
Ecto.Query lets you build queries with a composable, pipeable syntax: from p in Post, where: p.published == true, order_by: [desc: p.inserted_at]. Because clauses can be added incrementally to a query variable across function calls, it's common to build dynamic queries by piping a base query through several where or join calls before finally passing it to Repo.all/1, Repo.one/1, or Repo.get/2.
Cricket analogy: Ecto.Query composing filters is like a captain setting a field placement step by step — first slips, then gully, then point — building up the final query the same incremental way.
Migrations
Migrations are versioned Elixir modules (generated with mix ecto.gen.migration) that describe structural changes to the database — creating tables, adding columns, adding indexes — using functions like create table/1 and add/3. Running mix ecto.migrate applies pending migrations in order and records which have run in a schema_migrations table, so the schema's history stays reproducible across environments.
Cricket analogy: A migration is like laying a new pitch before the season starts — a permanent, versioned change to the playing surface (schema) that every subsequent match relies on.
Never edit a migration file that has already been run in a shared environment (staging or production) — Ecto tracks migrations by version number and won't re-run an already-applied one, so your change will silently be missing from other environments. Write a new migration instead.
- Ecto has four core pieces: Repo, Schema, Changeset, and Query.
- Schemas map database tables to structs; fields and their types are declared with field/3.
- Changesets cast and validate incoming data before it reaches the database via cast/3 and validate_* functions.
- Ecto.Query is a composable DSL — queries can be built incrementally by piping where/order_by/join clauses.
- Repo.insert/update/get/all execute queries and changesets against the configured database.
- Migrations are versioned, one-directional records of schema changes tracked in schema_migrations.
- Never modify an already-run migration in a shared environment — write a new one instead.
Practice what you learned
1. What is the primary purpose of an Ecto changeset?
2. Which function whitelists which fields can be set from user-supplied parameters in a changeset?
3. What does mix ecto.migrate do?
4. In Ecto.Query, which construct lets you incrementally build a query by piping clauses?
5. What happens if Repo.insert/1 is called with an invalid changeset?
Was this page helpful?
You May Also Like
Phoenix Framework Basics
An introduction to Phoenix's router, controllers, contexts, and plug pipeline — the core building blocks of a Phoenix web application.
Testing with ExUnit
A practical guide to writing, organizing, and running tests in Elixir using ExUnit's assertions, fixtures, tags, and doctests.
Mix and Hex Packages
How Elixir's built-in build tool Mix and the Hex package manager handle project structure, dependencies, and production releases.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics