Mastering Database Integration with Ecto in Elixir

Explore the comprehensive guide on working with databases using Ecto in Elixir, covering everything from setup to advanced querying and optimization.

14.4. Working with Databases using Ecto

In the world of Elixir, Ecto stands out as a robust database wrapper and query generator, designed to simplify database interactions while maintaining the functional programming principles that Elixir is known for. In this section, we will delve into the intricacies of Ecto, exploring its features, supported databases, and how it can be leveraged to build efficient, scalable applications.

Ecto Overview

Ecto is an essential tool for Elixir developers, providing a comprehensive suite of features for interacting with databases. It acts as a bridge between your Elixir application and the database, offering a high-level API for querying, data validation, and migrations.

Key Features of Ecto

  • Migrations: Ecto provides a powerful mechanism for managing database schema changes over time, ensuring that your database evolves alongside your application.
  • Schemas: Define the structure of your data and map it to your database tables, allowing for seamless data manipulation.
  • Changesets: A robust way to validate and cast data before it is inserted or updated in the database, ensuring data integrity and consistency.

Supported Databases

Ecto supports a variety of databases through adapters, making it a versatile choice for different projects. Some of the popular databases supported include:

  • PostgreSQL: Known for its advanced features and reliability, PostgreSQL is a popular choice for many Elixir applications.
  • MySQL: A widely-used open-source relational database management system.
  • SQLite: A lightweight, file-based database, ideal for small to medium-sized applications or development environments.

Setting Up Ecto

To get started with Ecto, you need to include it in your Elixir project. Let’s walk through the setup process:

  1. Add Ecto and a Database Adapter to Your Mix Project

    Open your mix.exs file and add the following dependencies:

    1defp deps do
    2  [
    3    {:ecto_sql, "~> 3.6"},
    4    {:postgrex, ">= 0.0.0"} # For PostgreSQL
    5  ]
    6end
    
  2. Configure Your Database

    In the config/config.exs file, add your database configuration:

    1config :my_app, MyApp.Repo,
    2  username: "postgres",
    3  password: "postgres",
    4  database: "my_app_db",
    5  hostname: "localhost",
    6  show_sensitive_data_on_connection_error: true,
    7  pool_size: 10
    
  3. Create a Repo Module

    The Repo module is your interface to the database. Create a new module in your project:

    1defmodule MyApp.Repo do
    2  use Ecto.Repo,
    3    otp_app: :my_app,
    4    adapter: Ecto.Adapters.Postgres
    5end
    
  4. Run Migrations

    Generate and run migrations to set up your database schema:

    1mix ecto.create
    2mix ecto.migrate
    

Defining Schemas

Schemas in Ecto are used to define the structure of your data and map it to database tables. Let’s define a simple schema for a User:

 1defmodule MyApp.User do
 2  use Ecto.Schema
 3
 4  schema "users" do
 5    field :name, :string
 6    field :email, :string
 7    field :age, :integer
 8
 9    timestamps()
10  end
11end

Working with Changesets

Changesets are a powerful feature in Ecto, allowing you to validate and transform data before it is persisted to the database. Here’s how you can define a changeset for the User schema:

 1defmodule MyApp.User do
 2  use Ecto.Schema
 3  import Ecto.Changeset
 4
 5  schema "users" do
 6    field :name, :string
 7    field :email, :string
 8    field :age, :integer
 9
10    timestamps()
11  end
12
13  def changeset(user, attrs) do
14    user
15    |> cast(attrs, [:name, :email, :age])
16    |> validate_required([:name, :email])
17    |> validate_format(:email, ~r/@/)
18    |> validate_number(:age, greater_than: 0)
19  end
20end

Querying with Ecto

Ecto provides a powerful query API that allows you to build complex queries in a composable and readable manner. Let’s explore some common querying patterns:

Basic Queries

To fetch all users from the database:

1users = MyApp.Repo.all(MyApp.User)

Filtering Data

To filter users by age:

1young_users = from(u in MyApp.User, where: u.age < 30) |> MyApp.Repo.all()

Ordering and Limiting Results

To order users by name and limit the results:

1ordered_users = from(u in MyApp.User, order_by: u.name, limit: 5) |> MyApp.Repo.all()

Advanced Querying Techniques

Ecto’s query API is not only powerful but also flexible, allowing for advanced querying techniques such as joins, subqueries, and aggregations.

Joins

To join users with another table, say posts:

1query = from(u in MyApp.User,
2             join: p in assoc(u, :posts),
3             where: p.published == true,
4             select: {u.name, p.title})
5
6results = MyApp.Repo.all(query)

Subqueries

Subqueries can be used to perform more complex operations:

1subquery = from(p in MyApp.Post, where: p.published == true, select: p.user_id)
2query = from(u in MyApp.User, where: u.id in subquery(subquery))
3
4results = MyApp.Repo.all(query)

Aggregations

To perform aggregations, such as counting the number of users:

1count = from(u in MyApp.User, select: count(u.id)) |> MyApp.Repo.one()

Transactions

Ecto supports transactions, allowing you to execute multiple operations atomically. Here’s how you can use transactions in Ecto:

1MyApp.Repo.transaction(fn ->
2  user = MyApp.Repo.insert!(%MyApp.User{name: "John", email: "john@example.com"})
3  MyApp.Repo.update!(Ecto.Changeset.change(user, age: 30))
4end)

Optimizing Database Interactions

Optimizing database interactions is crucial for building high-performance applications. Here are some tips for optimizing your Ecto queries:

  • Use Indexes: Ensure that your database tables are properly indexed to speed up query execution.
  • Batch Queries: Use Ecto’s preload to batch load associated data, reducing the number of queries executed.
  • Limit Data Retrieval: Only select the fields you need to minimize data transfer.

Visualizing Ecto’s Architecture

To better understand how Ecto interacts with your Elixir application and the database, let’s visualize its architecture:

    graph TD;
	    A["Elixir Application"] -->|Ecto Queries| B["Ecto Repo"];
	    B -->|SQL Queries| C["Database"];
	    C -->|Results| B;
	    B -->|Ecto Structs| A;

Diagram Description: This diagram illustrates the flow of data between your Elixir application, Ecto, and the database. Ecto acts as an intermediary, converting Elixir data structures into SQL queries and vice versa.

Try It Yourself

Now that we’ve covered the basics of working with databases using Ecto, it’s time to experiment. Try modifying the code examples to:

  • Add a new field to the User schema and update the changeset accordingly.
  • Create a new schema for Posts and establish a relationship with Users.
  • Write a query to fetch users who have published posts.

References and Further Reading

Knowledge Check

Before we wrap up, let’s reinforce what we’ve learned with some questions and exercises.

Quiz: Working with Databases using Ecto

Loading quiz…

Remember, mastering Ecto is a journey. As you continue to explore its features and capabilities, you’ll find new ways to optimize and enhance your database interactions. Keep experimenting, stay curious, and enjoy the journey!

Revised on Thursday, April 23, 2026