Building a Web App with Elixir & Phoenix — Part 2

Sun, Apr 22, 2018

Read in 4 minutes

This a continuation from the previous Part 1, where we learnt how to set up a basic Elixir project and also created a database link to lay the groundwork of data persisting application.

Building a Web App with Elixir & Phoenix — Part 2

In this section will set up your application to interact with the database.

While your database link has been created, there’s currently are 2 important pieces missing for your application to communicate with your database.

  1. Schema. Without some sort of data mapping, your Elixir app won’t know how to translate knowledge between 2 domains, your application domain and database domain.
  2. Tables. In our example, we used Postgres as a database. Postgres stores data in the form of “tables”, each row representing a data insertion and columns representing different fields.

With Elixir, we use Ecto to manage the database tables and schema.

Firstly let’s create the schema. Create item.ex in lib/ and then insert,

defmodule TodoApp.Item do
  use Ecto.Schema
  schema "item" do
    field :task, :string
    field :completed, :boolean
  end
end

use Ecto.Schema allows you to define a schema within a module. Notice that the fields are very similar to the defstruct you have done earlier in Part one of the guide. Since structs are only limited to the Elixir domain, it is insufficient to describe cross-domain concerns.

Next let’s create the table migrations. Migrations are a set of database operations that help set the tables up to reflect the structure of your schema. Migrations come in handy as a form of “transformation log” so you can see how your data struture changed over time.

To create a migration template, run mix ecto.create.migration item. This creates a file in priv/repo/migrations. Notice that it looks something like 20180421072342_item.exs , which is a timestamp concatenated with the migration title. Open the file and update it to,

defmodule TodoApp.Repo.Migrations.Item do
  use Ecto.Migration
    def change do
      create table("item") do
         add :task, :string
         add :completed, :boolean
      end
    end
end

Compare this file against lib/item.ex and notice that it reflects the struture of the Todo item model. This is what you want so the “communication” between your application domain is consistent with the database domain.

Now to update your table, run mix ecto.migrate. You should see,

00:27:35.457 [info]  == Running   
TodoApp.Repo.Migrations.Item.change/0 forward
00:27:35.457 [info]  create table item
00:27:35.467 [info]  == Migrated in 0.0s

Your table item is created. Now you can begin persisting data in the database! Open up iex -S mix ,

iex(1)> TodoApp.Repo.all(TodoApp.Item)

00:30:34.080 [debug] QUERY OK source="item" db=4.7ms
SELECT i0."id", i0."task", i0."completed" FROM "item" AS i0 []
[]

TodoApp.Repo which uses Ecto.Repo , ships with Ecto’s database query helpers. One of the helper functions is all/1 which accepts an Ecto.Schema and retrieves all instances of the schema you passed in. In this case we passed in TodoApp.Item which uses Ecto.Schema and it returned us nothing, which is correct since we haven’t inserted any data~

Let’s try inserting an item

iex(2)> item = %TodoApp.Item{task: "Try Elixir", completed: true}

%TodoApp.Item{
  __meta__: #Ecto.Schema.Metadata<:built, "item">,
  completed: true,
  id: nil,
  task: "Try Elixir"
}

iex(3)> TodoApp.Repo.insert(item)

00:35:29.863 [debug] QUERY OK db=4.7ms
INSERT INTO "item" ("completed","task") VALUES ($1,$2) RETURNING "id" [true, "Try Elixir"]
{:ok,
 %TodoApp.Item{
   __meta__: #Ecto.Schema.Metadata<:loaded, "item">,
   completed: true,
   id: 1,
   task: "Try Elixir"
}}

Using insert/1 we have successfully inserted an item. Notice that we used %TodoApp.Item{} which seems similar to the %Todo{} struct we user in Part one. %TodoApp.Item{} is special in that apart from having a map of data exactly like %Todo{} it also has metadata that allows Ecto to interpret it’s structure across databases. Now we can try to query again to see that indeed it has entered the database. We can use the all/1 query helper again:

iex(4)> TodoApp.Repo.all(TodoApp.Item)

00:40:32.811 [debug] QUERY OK source="item" db=3.9ms queue=0.1ms
SELECT i0."id", i0."task", i0."completed" FROM "item" AS i0 []
[
 %TodoApp.Item{
   __meta__: #Ecto.Schema.Metadata<:loaded, "item">,
   completed: true,
   id: 1,
   task: "Try Elixir"
 }
]

Success! We manage to get the item back from the database. Ecto.Query has many more database helpers since as update/2 and delete/1 which helps you manage changes in data.

Also notice that the new item created also has a new key called id. We didn’t define that in the migrations, where did that come from? Turns out Ecto assumes that you will need an easy reference for most cases to come back to either edit or delete the data you inserted, therefore id is included by default. It is auto-incremented to prevent duplicate references.

This concludes the second part of this guide. In the next part, we will link up your application into the outside world so you can update your todo list from anywhere.