DEV Community

Miguel Cobá
Miguel Cobá

Posted on • Updated on • Originally published at

Elixir API and Elm SPA - Part 1

Part 1: Elixir App creation

I am going to create a demo app with an Elixir API on the backend and a separated Elm SPA on the frontend. The app will be a simple CRUD app with what I have found to be the best practices so far. The app will be a simple market app where users can post announcements for things to sell and other users can see the offers and buy them.
The name of the app will be Toltec.


  1. Part 1 - Elixir App creation
  2. Part 2 - Adds Guardian Authentication
  3. Part 3: Elm App creation and Routing setup
  4. Part 4: Adding Login and Register pages
  5. Part 5: Persisting session data to localStorage


I assume you are using:

  • Elixir 1.6.5
  • Erlang 20
  • Elm 0.18
  • PostgreSQL 10.4

Create the App

Let's create a basic app for the API. We don't need html or brunch as we don't render html at all. We'll just expose a JSON REST API:

mix toltec-api --app toltec --no-brunch --no-html --binary-id
Enter fullscreen mode Exit fullscreen mode

Configure auto formatting

Before adding any code let's configure code auto formatting. Add a file in the root of the project called .formatter.exs:

# .formatter.exs

  inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"]
Enter fullscreen mode Exit fullscreen mode

And the run the format command:

mix format
Enter fullscreen mode Exit fullscreen mode

This should format all the code in your project.

Now you need to configure your editor to automatically run this command when you save a file. For VS Code, for example, you can use the vscode-elixir and vscode-elixir-formatter extensions.

Create Accounts

We are going to use the comeonin package to handle the password hashing in our app. Add this dependency in mix.exs

 # mix.exs

  defp deps do
      {:phoenix, "~> 1.3.0"},
      {:phoenix_pubsub, "~> 1.0"},
      {:phoenix_ecto, "~> 3.2"},
      {:postgrex, ">= 0.0.0"},
      {:gettext, "~> 0.11"},
      {:cowboy, "~> 1.0"},
      {:comeonin, "~> 4.0"},
      {:argon2_elixir, "~> 1.2"}
Enter fullscreen mode Exit fullscreen mode

And get the dependencies

mix deps.get
Enter fullscreen mode Exit fullscreen mode

Now let's add the User schema and an Accounts context to hold all this logic

mix phx.gen.context Accounts User users name:string email:string:unique password_hash:string
Enter fullscreen mode Exit fullscreen mode

This will create a user migration, a user schema and an accounts context. For the users I won't use UUID primary keys, so I am going to remove the binary_id config from the users migration, but you're free to keep it if you want.
Change the migration to:

# priv/repo/migrations/20180612062911_create_users.exs

  def change do
    execute("CREATE EXTENSION citext;")

    create table(:users) do
      add(:name, :string, null: false)
      add(:email, :citext, null: false)
      add(:password_hash, :string, null: false)


    create(unique_index(:users, [:email]))
Enter fullscreen mode Exit fullscreen mode

And lets add some basic methods to the user schema:

 # lib/toltec/accounts/user.ex

 defmodule Toltec.Accounts.User do
  use Ecto.Schema
  import Ecto.Changeset
  alias Toltec.Accounts.User

  schema "users" do
    field(:email, :string)
    field(:name, :string)
    field(:password, :string, virtual: true)
    field(:password_hash, :string)


  def changeset(%User{} = user, attrs) do
    |> cast(attrs, [:name, :email])
    |> validate_required([:name, :email])
    |> validate_length(:name, min: 2, max: 255)
    |> validate_length(:email, min: 5, max: 255)
    |> unique_constraint(:email)
    |> validate_format(:email, ~r/@/)

  def registration_changeset(%User{} = user, attrs) do
    |> changeset(attrs)
    |> cast(attrs, [:password])
    |> validate_required([:password])
    |> validate_length(:password, min: 8, max: 100)
    |> put_password_hash()

  defp put_password_hash(changeset) do
    case changeset do
      %Ecto.Changeset{valid?: true, changes: %{password: password}} ->
        put_change(changeset, :password_hash, Comeonin.Argon2.hashpwsalt(password))

      _ ->
Enter fullscreen mode Exit fullscreen mode

I have added a password virtual field, to temporarily hold the clear-text password. This will never be saved to the DB. I also added two changesets, one for changing the password and one for the other fields. The put_password_hash() function uses the comeonin library to get a hash of the password and put that on the password_hash field that will be stored in DB.

Now modify the Accounts context that holds all the logic for accounts to look like this:

# lib/toltec/accounts/accounts.ex

defmodule Toltec.Accounts do
  import Ecto.Query, warn: false
  alias Toltec.Repo

  alias Toltec.Accounts.User

  def list_users do

  def get_user!(id), do: Repo.get!(User, id)

  def create_user(attrs \\ %{}) do
    result =
      |> User.registration_changeset(attrs)
      |> Repo.insert()

    case result do
      {:ok, user} -> {:ok, %User{user | password: nil}}
      _ -> result

  def update_user(%User{} = user, attrs) do
    |> User.changeset(attrs)
    |> Repo.update()

  def delete_user(%User{} = user) do

  def change_user(%User{} = user) do
    User.changeset(user, %{})
Enter fullscreen mode Exit fullscreen mode

Next, let's add some seed data to the DB to make our app usable.

# priv/repo/seeds.exs

# users
user =
  Toltec.Accounts.User.registration_changeset(%Toltec.Accounts.User{}, %{
    name: "some user",
    email: "user@toltec",
    password: "user@toltec"


Enter fullscreen mode Exit fullscreen mode

Now is time to build the DB, create tables and insert the seed data.

mix ecto.reset
Enter fullscreen mode Exit fullscreen mode

This command will drop the DB if exists, create it again, run the migrations in order and finally run the seeds.exs file to insert initial data. You should see no errors in the output, and should be similar to this.

mix ecto.reset
The database for Toltec.Repo has already been dropped
The database for Toltec.Repo has been created
[info] == Running Toltec.Repo.Migrations.CreateUsers.change/0 forward
[info] execute "CREATE EXTENSION citext;"
[info] create table users
[info] create index users_email_index
[info] == Migrated in 0.0s
[debug] QUERY OK db=2.8ms
INSERT INTO "users" ("email","name","password_hash","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) RETURNING "id" ["user@toltec", "some user", "$argon2i$v=19$m=65536,t=6,p=1$jdZHJ4HdGSF34hP6iTiPeQ$+PWBp77RP8wxMasVj0wv1wWS33pponixKSEG109V/u8", {{2018, 6, 12}, {22, 35, 55, 411334}}, {{2018, 6, 12}, {22, 35, 55, 411345}}]
Enter fullscreen mode Exit fullscreen mode

You can find the source code, with tests, in the repo here in the branch part-01.

After cloning it, run the tests and verify that everything is alright:

mix test

Finished in 0.3 seconds
19 tests, 0 failures
Enter fullscreen mode Exit fullscreen mode

So far we have created the app, the users schema, and some initial user to play with our app.
That's it for now.

Top comments (3)

in4no profile image

mix ecto.reset throws error

== Compilation error in file lib/toltec/accounts/accounts.ex ==
** (CompileError) lib/toltec/accounts/accounts.ex:54: unknown key :password for struct Toltec.Accounts.User
(stdlib) lists.erl:1354: :lists.mapfoldl/3
(stdlib) lists.erl:1355: :lists.mapfoldl/3
(stdlib) lists.erl:1354: :lists.mapfoldl/3
(stdlib) lists.erl:1354: :lists.mapfoldl/3
lib/toltec/accounts/accounts.ex:48: (module)

mazz profile image

This is awesome, thanks.

darckblezzer profile image
Marco Antonio

Thanks for this, I was looking for something like this, great explanation, and example