RestAPI with Elixir and Phoenix: Part 1

Siddhant Singh
The Startup
Published in
6 min readOct 28, 2019
Photo by Franz Roos on Unsplash

Why Elixir and Phoenix?

In this section, I’m going to talk about RestAPI and how can you build one in elixir and phoenix. Coming from djangorestframework which is another way to build powerful APIs. In Django, Serializers allow complex data such as query sets and model instances to be converted to native Python datatypes that can then be easily rendered into JSON, XML or other content types. While python has some great functionality. I decided to learn something new which is not following OOPS concepts.

I’ve resolved to learn a new language which is of course elixir. So elixir is a dynamic, functional language designed for building scalable and maintainable applications. I had been reading and building RestAPI in elixir and phoenix for quite some time. This blog will give you the most basic introduction ever. This is for beginners only, of course, others can give their opinion about the blog.

Project Setup

So first you have to install and elixir and phoenix. For this project, I’m using these dependencies.

|> elixir: "~> 1.5"
|> {:phoenix, "~> 1.4.9"}

Now you can run these commands to set up your projects and dependencies.

mix phx.new --no-webpack --no-html <project-name>

After this, you need to run a mix ecto.create which will give create the repo for the project. We will talk about this in detail.

To run a server

mix phx.server

Now I’m going to create a banking API that will fetch bank details, given branch IFSC code. But first I’m going to explain to you the code and what it means.

defmodule Banking.Schema.Bank do
use Ecto.Schema
import Ecto.Changeset
alias Banking.Schema.Branch
schema "banks" do
field :name, :string
has_many :branches, Branch
timestamps()
end
def changeset(bank, attrs) do
bank
|> cast(attrs, [:name])
|> validate_required([:name])
end
end

defmodule gives you a struct and to manipulate struct we use a Map. You must be thinking why the Map is being used to manipulate struct it’s because struct only permits certain keys and those keys must be atoms. Structs need to be defined in modules with reasonable default values. They’re maps with rules.

Older versions of Elixir used to also include the HashDict to handle maps with more than a couple hundred values, but that module has been deprecated in favor of the good oldfashioned Map.

Ecto

I’m highlighting Ecto here because it has the most important functionalities in the elixir. Ecto is a domain-specific language for writing queries and interacting with databases in the Elixir language.

Ecto has 4 main components:

  • Ecto.Repo. Defines repositories that are wrappers around a data store. Using it, we can insert, create, delete, and query a repo. An adapter and credentials are required to communicate with the database.
  • Ecto.Schema. Schemas are used to map any data source into an Elixir struct.
  • Ecto.Changeset. Changesets provide a way for developers to filter and cast external parameters, as well as a mechanism to track and validate changes before they are applied to data.
  • Ecto.Query. It provides a DSL-like SQL query for retrieving information from a repository. Queries in Ecto are secure, avoiding common problems like SQL Injection, while still being composable, allowing developers to build queries piece by piece instead of all at once.

Context

Phoenix Contexts provide a “context” module as a way to group related functionality together in a unified API. Contexts encourage you to think about the design of your application, making it easier to design an application that has functionality with clearly defined boundaries. To explain this I have to show you how controllers call context and how it works.

defmodule BankingWeb.BankController do
use BankingWeb, :controller
alias Banking.Schema.Bank
alias Banking.Model.Bank, as: BankModel
def index(conn, _params) do
banks = BankModel.list_banks()
render(conn, "index.json", banks: banks)
end
def create(conn, %{"bank" => bank_params}) do
with {:ok, %Bank{} = bank} <- BankModel.create_bank(bank_params) do
conn
|> put_status(:created)
|> render("show.json", bank: bank)
end
end
def show(conn, %{"id" => id}) do
bank = BankModel.get_bank!(id)
render(conn, "show.json", bank: bank)
end
def update(conn, %{"id" => id, "bank" => bank_params}) do
bank = BankModel.get_bank!(id)
with {:ok, %Bank{} = bank} <- BankModel.update_bank(bank, bank_params) do
render(conn, "show.json", bank: bank)
end
end
def delete(conn, %{"id" => id}) do
bank = BankModel.get_bank!(id)
with {:ok, %Bank{}} <- BankModel.delete_bank(bank) do
send_resp(conn, :no_content, "")
end
end
end

Now let’s see our bank_controller.ex and if you used Phoenix prior to the release of 1.3, you may expect there to be called to the Repo module or the Bank module, but here when we’re interacting with our “banks” we’re doing so through our Bank context module. For example, to get our banks, we get them with the bank.list_banks function. Our “bank” functionality is now contained in our Bank context module, which exposes a public API that contains all of the functionality to interact with banks.

Now let me show you how the context model looks like.

defmodule Banking.Model.Bank do
import Ecto.Query
alias Banking.Repo
alias Banking.Schema.Bank
def list_banks() do
Repo.all(Bank)
end
def get_bank!(id), do: Repo.get!(Bank, id) def create_bank(attrs \\ %{}) do
%Bank{}
|> Bank.changeset(attrs)
|> Repo.insert()
end
def update_bank(%Bank{} = bank, attrs) do
bank
|> Bank.changeset(attrs)
|> Repo.update()
end

def delete_bank(%Bank{} = bank) do
Repo.delete(bank)
end
end

Now what I will do is I will call this API from my “postman”. So suppose I want to create a bank name so I will call this through post request. Let me show you.

Now, this gives me “id” and “name” but what if you hit the API again. So, in that case, it will create the same name but different “id”. If you want your name to be unique you have to add in the database unique index in your database.

create unique_index(:banks, [:name])

So what if you want a list of names which is created in the database. In that case, you have to call “get” API. That will give you a result of something like this.

List of Banks

Similarly, if you call “delete” request and pass the “id” which you want to delete then it will delete that item from the database. Also, if you want to update it, in that case, you have to hit“put” request and it will update that item.

Put Request

Now if you do get a request to list all the banks you will see the changes.

Update the Particular Item

But now, what if you don’t want to call the API and you want to directly insert the name in the database then you have to run your iex server. Let me show you

iex -S mix phx.server

Now you’re in elixir shell and to use a Bank module you have to alias the bank schema and repo.

alias Banking.Schema.Bank
alias Banking.Repo

If you call your Bank struct now it will give you something like this

iex(1) %Bank{}%Banking.Schema.Bank{
__meta__: #Ecto.Schema.Metadata<:built, "banks">,
branches: #Ecto.Association.NotLoaded<association :branches is not loaded>,
id: nil,
inserted_at: nil,
name: nil,
updated_at: nil
}

To insert into your database you have to create a changeset and compare your empty struct and then a hash of values to cast onto the struct.

iex> bank = Bank.changeset(%Bank{}, %{name: "siddhant"})#Ecto.Changeset<
action: nil,
changes: %{name: "siddhant"},
errors: [],
data: #Banking.Schema.Bank<>,
valid?: true
>

It’s just giving some validation if you want to insert then you have to take that variable and call this

iex> Repo.insert(bank){:ok,
%Banking.Schema.Bank{
__meta__: #Ecto.Schema.Metadata<:loaded, "banks">,
branches: #Ecto.Association.NotLoaded<association :branches is not loaded>,
id: 14,
inserted_at: ~N[2019-10-28 04:24:47],
name: "siddhant",
updated_at: ~N[2019-10-28 04:24:47]
}}

This will insert the item into the database. Now if you want to check in terminal how many item is inserted in your database.

Repo.all(Bank)

This will give you a list of items present in your database.

That’s it for this blog and I will explain in the second blog how you can add association between two models. Let me know if you need some more information about the code or some concept. I will try to explain it in a better way.

Thank you :)

--

--

Siddhant Singh
The Startup

Into Data Science, Machine Learning and Data-Driven Astronomy. If I’m not writing code, I might be reading some random stuff.