SlideShare a Scribd company logo
1 of 75
Download to read offline
Ecto and Phoenix: Doing Web With Elixir
Elixir Club 9, Kharkiv 2017
• Elixir Applications

• Umbrella Projects

• Phoenix: Application Structure

• Phoenix: Controllers, Templates and Views

• Phoenix: JSON

• Phoenix: Channels

• ECTO
Elixir (Erlang.OTP) Applications
• In Elixir (Erlang/OTP), an application is a component implementing some
specific functionality, that can be started and stopped as a unit, and which
can be re-used in other systems.

• Applications are defined with an application file named
application_name.app in the same ebin directory as the compiled
modules of the application.

• In Elixir, the Mix build tool is responsible for compiling your source code
and generating your application .app file. 
Creating Elixir Application with Supervision Tree
$ mix new hello_world --sup
$ mix compile
_build/dev/lib/hello_world/ebin/hello_world.app
(Erlang/OTP)
{application,hello_world,
[{applications,[kernel,stdlib,elixir,logger]},
{description,"hello_world"},
{modules,
['Elixir.HelloWorld','Elixir.HelloWorld.Application']},
{registered,[]},
{vsn,"0.1.0"},
{extra_applications,[logger]},
{mod,{'Elixir.HelloWorld.Application',[]}}]}.
mix.exs
def project do
[ app: :hello_world,
version: "0.1.0",
elixir: "~> 1.6-dev",
start_permanent: Mix.env() == :prod,
deps: deps() ]
end
def application do
[ extra_applications: [:logger],
mod: {HelloWorld.Application, []} ]
end
defp deps do
[]
end
Starting Elixir Application
• You start one or more applications, each with their own initialization and
termination logic.

• Elixir does not have a main procedure that is responsible for starting your
system. 

• Starting an application is done via the “application module callback”,
which is a module that defines the start/2 function.
lib/hello_world/application.ex
use Application
def start(_type, _args) do
# List all child processes to be supervised
children = [
# Starts a worker by calling: HelloWorld.Worker.start_link(arg)
# {HelloWorld.Worker, arg},
]
opts = [strategy: :one_for_one, name: HelloWorld.Supervisor]
Supervisor.start_link(children, opts)
end
Application Supervision Tree
The start/2 function should start a supervisor, which is often called
as the top-level supervisor, since it sits at the root of a potentially
long supervision tree.

lib/hello_phoenix/application.ex
def start(_type, _args) do
import Supervisor.Spec
children = [
# Start the Ecto repository
supervisor(HelloPhoenix.Repo, []),
# Start the endpoint when the application starts
supervisor(HelloPhoenixWeb.Endpoint, []),
]
opts = [strategy: :one_for_one,
name: HelloPhoenix.Supervisor]
Supervisor.start_link(children, opts)
end
WW
S
S S
W
Umbrella projects
$ mix new hello_umbrella --umbrella
Umbrella projects
hello_umbrella/mix.exs
def project do
[
apps_path: "apps",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
Applications in umbrella project
$ cd hello_umbrella/apps
$ mix new hello
$ mix new world
In umbrella dependencies
hello_umbrella/apps/hello/mix.exs
def project do
[
app: :hello,
version: "0.1.0",
build_path: "../../_build",
config_path: "../../config/config.exs",
deps_path: "../../deps",
lockfile: "../../mix.lock",
elixir: "~> 1.6-dev",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
...
defp deps do
[
{:world, in_umbrella: true}
]
end
Phoenix Framework: Productive. Reliable. Fast.
• Phoenix is a web development framework
written in Elixir which implements the server-
side MVC pattern. 

• Current version: 1.3.0
• http://phoenixframework.org/
 Phoenix Layers
• Phoenix itself is actually the top layer of a multi-layer system

• Cowboy: the web server used by Phoenix (and Plug) is Cowboy (Erlang).

• Plug is a specification for constructing composable modules to build web
applications. Plugs are reusable modules or functions built to that
specification.

• Ecto is a language integrated query composition tool and database
wrapper for Elixir.
Phoenix Application
$ mix phx.new hello_phoenix
lib/hello_phoenix/application.ex
defmodule HelloPhoenix.Application do
use Application
...
lib/hello_phoenix_web/endpoint.ex
defmodule HelloPhoenixWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :hello_phoenix
...
Phoenix Application
mix.exs
...
def application do
[
mod: {HelloPhoenix.Application, []},
extra_applications: [:logger, :runtime_tools]
]
end
...
lib/hello_phoenix/application.ex
...
def start(_type, _args) do
import Supervisor.Spec
children = [
# Start the Ecto repository
supervisor(HelloPhoenix.Repo, []),
# Start the endpoint when the application starts
supervisor(HelloPhoenixWeb.Endpoint, []),
]
opts = [strategy: :one_for_one, name: HelloPhoenix.Supervisor]
Supervisor.start_link(children, opts)
end
...
Starting Phoenix Application
$ mix phx.new hello_phoenix
$ cd hello_phoenix
$ mix deps.get
$ mix ecto.create
$ mix ecto.migrate
$ mix phx.server
($ iex -S mix phx.server)
Phoenix Umbrella Project
mix phx.new phoenix --umbrella
Plug
• Core Phoenix components like Endpoints, Routers, and Controllers are all
just Plugs internally. 

• Plug is a specification for composable modules in web application.

• Plugs are reusable modules or functions built to that specification. 

• They provide discrete behaviors - like request header parsing or logging. 

• Because the Plug API is small and consistent, plugs can be defined and
executed in a set order, like a pipeline.
Plugs in Phoenix’s HTTP layer
HTTP Request
%Plug.Conn Plug Plug Plug
Plug Plug Plug %Plug.Conn
HTTP Response
Plug.Conn
devhints.io/phoenix-conn
Request
conn.host # → "example.com"
conn.method # → "GET"
conn.path_info # → ["posts", "1"]
conn.request_path # → "/posts/1"
conn.query_string # → "utm_source=twitter"
conn.port # → 80
conn.scheme # → :http
conn.peer # → { {127, 0, 0, 1}, 12345 }
conn.remote_ip # → { 151, 236, 219, 228 }
conn.req_headers # → [{"content-type", "text/plain"}]
Response
conn.resp_body # → "..."
conn.resp_charset # → "utf-8"
conn.resp_cookies # → ...
conn.resp_headers # → ...
conn.status # → …
Assigns
conn |> assign(:user_id, 100)
conn.assigns[:user_id]
Module Plug Example
defmodule Example.HelloWorldPlug do
import Plug.Conn
def init(options), do: options
def call(conn, _opts) do
conn
|> put_resp_content_type("text/plain")
|> send_resp(200, "Hello World!")
end
end
%Plug.Conn{…}
Plug pipelines
…
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
…
Phoenix Parts
• Endpoint

• Router

• Controllers

• Views

• Templates

• Channels
Endpoint
• Provides a wrapper for starting and stopping the endpoint as part of a
supervision tree

• Handles all aspects of requests up until the point where the router takes
over

• Defines an initial plug pipeline where requests are sent through

• Hosts web specific configuration for your application

• Dispatches requests into a designated router
Endpoint
…
use Phoenix.Endpoint, otp_app: :hello_phoenix
socket "/socket", HelloPhoenixWeb.UserSocket
plug Plug.Static,
...
if code_reloading? do
...
end
plug Plug.RequestId
plug Plug.Logger
plug Plug.Parsers,
...
plug Plug.MethodOverride
plug Plug.Head
plug Plug.Session,
...
plug HelloPhoenixWeb.Router
…
Starting Endpoint
...
def start(_type, _args) do
import Supervisor.Spec
children = [
# Start the Ecto repository
supervisor(HelloPhoenix.Repo, []),
# Start the endpoint when the application starts
supervisor(HelloPhoenixWeb.Endpoint, []),
]
opts = [strategy: :one_for_one, name:
HelloPhoenix.Supervisor]
Supervisor.start_link(children, opts)
end
...
Router
• Parses incoming requests and dispatches them to the correct controller/
action, passing parameters as needed

• Provides helpers to generate route paths or urls to resources

• Defines named pipelines through which we may pass our requests
Router
...
pipeline :api_client_id do
plug :header_required, "x-consumer-metadata"
plug :client_id_exists
end
...
scope "/api", EHealth.Web do
pipe_through [:api, :api_client_id]
# Legal Entities
get "/legal_entities/:id", LegalEntityController, :show
patch "/legal_entities/:id/actions/mis_verify", LegalEntityController, :mis_verify
patch "/legal_entities/:id/actions/nhs_verify", LegalEntityController, :nhs_verify
patch "/legal_entities/:id/actions/deactivate", LegalEntityController, :deactivate
# Employees
get "/employees/:id", EmployeeController, :show
scope "/employees" do
pipe_through [:client_context_list]
patch "/:id/actions/deactivate", EmployeeController, :deactivate
end
...
Controllers & Actions
• Controllers provide functions, called actions, to handle requests

• Actions:

• prepare data and pass it into views

• invoke rendering via views

• perform redirects
Controllers & Actions
defmodule EHealth.Web.LegalEntityController do
use EHealth.Web, :controller
...
action_fallback EHealth.Web.FallbackController
def show(%Plug.Conn{req_headers: req_headers} = conn, %{"id" => id}) do
with {:ok, legal_entity, security} <- API.get_legal_entity_by_id(id, req_headers) do
conn
|> assign_security(security)
|> render("show.json", legal_entity: legal_entity)
end
end
def mis_verify(%Plug.Conn{req_headers: req_headers} = conn, %{"id" => id}) do
with {:ok, legal_entity} <- API.mis_verify(id, get_consumer_id(req_headers)) do
render(conn, "show.json", legal_entity: legal_entity)
end
end
...
Web Interface Entrypoint
defmodule HelloPhoenixWeb do
def controller do
quote do
use Phoenix.Controller, namespace: HelloPhoenixWeb
import Plug.Conn
import HelloPhoenixWeb.Router.Helpers
import HelloPhoenixWeb.Gettext
end
end
def view do
...
end
def router do
...
end
def channel do
...
end
...
Fallback Controller
action_fallback(plug)
• Registers the plug to call as a
fallback to the controller
action.

• If the controller action fails to
return a %Plug.Conn{}, the
provided plug will be called
and receive the
controller’s %Plug.Conn{} as it
was before the action was
invoked along with the value
returned from the controller
action.

web/controllers/fallback_controller.ex
defmodule EHealth.Web.FallbackController do
...
use EHealth.Web, :controller
require Logger
def call(conn, {:error, json_schema_errors}) when is_list(json_schema_errors) do
conn
|> put_status(422)
|> render(EView.Views.ValidationError, "422.json", %{schema: json_schema_errors})
end
def call(conn, {:error, errors, :query_parameter}) when is_list(errors) do
conn
|> put_status(422)
|> render(EView.Views.ValidationError, "422.query.json", %{schema: errors})
end
def call(conn, {:error, {:"422", error}}) do
conn
|> put_status(422)
|> render(EView.Views.Error, :"400", %{message: error})
end
...
Views
• Defines the view layer of a Phoenix application

• Render templates

• Define helper functions, available in templates, to decorate data for
presentation
• Phoenix assumes a strong naming convention
from controllers to views to the templates they
render. 

• The PageController requires a PageView to
render templates in the lib/hello_phoenix_web/
templates/page directory.
Templates
Web Interface Entrypoint
defmodule HelloPhoenixWeb do
def controller do
...
end
def view do
quote do
use Phoenix.View, root: "lib/hello_phoenix_web/templates",
namespace: HelloPhoenixWeb
# Import convenience functions from controllers
import Phoenix.Controller, only: [get_flash: 2, view_module: 1]
# Use all HTML functionality (forms, tags, etc)
use Phoenix.HTML
import HelloPhoenixWeb.Router.Helpers
import HelloPhoenixWeb.ErrorHelpers
import HelloPhoenixWeb.Gettext
end
end
...
View Module
defmodule HelloPhoenixWeb.PageView do
use HelloPhoenixWeb, :view
end
Because we have defined the template root to be "lib/hello_phoenix_web/templates",
Phoenix.View will automatically load all templates at “lib/hello_phoenix_web/
templates/page” and include them in the HelloPhoenixWeb.PageView.
lib/hello_phoenix_web/controllers/page_controller.ex
...
def index(conn, _params) do
render conn, "index.html"
end
...
EEx Templates
• EEx is the default template system in Phoenix

• EEx module receives a template path and transforms its source code into
Elixir quoted expressions

• Templates are precompiled and fast

• Template name - is the name of the template as given by the user, without
the template engine extension, for example: “foo.html”
HTML EEx Template
Hello <%= @name %>
<h3>Keys for the conn Struct</h3>
<%= for key <- connection_keys @conn do %>
<p><%= key %></p>
<% end %>
JSON - Controller
defmodule EHealth.Web.DictionaryController do
…
alias EHealth.Dictionaries
alias EHealth.Dictionaries.Dictionary
action_fallback EHealth.Web.FallbackController
def index(conn, params) do
with {:ok, dictionaries} <- Dictionaries.list_dictionaries(params)
do
render(conn, "index.json", dictionaries: dictionaries)
end
end
def update(conn, %{"name" => name} = dictionary_params) do
with {:ok, %Dictionary{} = dictionary} <- Dictionaries.create_or_update_dictionary(name, dictionary_params) do
render(conn, "show.json", dictionary: dictionary)
end
end
End
JSON - View
defmodule EHealth.Web.DictionaryView do
use EHealth.Web, :view
alias EHealth.Web.DictionaryView
def render("index.json", %{dictionaries: dictionaries}) do
render_many(dictionaries, DictionaryView, "dictionary.json")
end
def render("show.json", %{dictionary: dictionary}) do
render_one(dictionary, DictionaryView, "dictionary.json")
end
def render("dictionary.json", %{dictionary: dictionary}) do
%{
name: dictionary.name,
values: dictionary.values,
labels: dictionary.labels,
is_active: dictionary.is_active
}
end
end
Channels
• Manage sockets for easy real-time communication

• Allow bi-directional communication with persistent connections

• Every time you join a channel, you need to choose which particular topic
you want to listen to. 

• The topic is just an identifier, but by convention it is often made of two
parts: "topic:subtopic".
Channels
hello_phoenix/lib/hello_phoenix_web/endpoint.ex
...
socket "/socket", HelloPhoenixWeb.UserSocket
...
hello_phoenix/lib/hello_phoenix_web/channels/user_socket.ex
defmodule HelloPhoenixWeb.UserSocket do
use Phoenix.Socket
## Channels
channel "room:*", HelloPhoenixWeb.RoomChannel
...
Any topic coming into the router with the "room:" prefix
would dispatch to HelloPhoenixWeb.RoomChannel
Channels - Socket
hello_phoenix/lib/hello_phoenix_web/channels/user_socket.ex
defmodule HelloPhoenixWeb.UserSocket do
use Phoenix.Socket
## Channels
channel "room:*", HelloPhoenixWeb.RoomChannel
## Transports
transport :websocket, Phoenix.Transports.WebSocket
# Socket params are passed from the client and can
# be used to verify and authenticate a user. After
# verification, you can put default assigns into
# the socket that will be set for all channels, ie
#
# {:ok, assign(socket, :user_id, verified_user_id)}
#
# To deny connection, return `:error`.
def connect(_params, socket) do
{:ok, socket}
end
...
Joining Channels
defmodule HelloPhoenixWeb.RoomChannel do
use Phoenix.Channel
def join("room:lobby", _message, socket) do
{:ok, socket}
end
def join("room:" <> _private_room_id, _params, _socket) do
{:error, %{reason: "unauthorized"}}
end
end
Channels: phoenix.js
hello_phoenix/assets/js/socket.js
import {Socket} from "phoenix"
let socket = new Socket("/socket", {params: {token: window.userToken}})
socket.connect()
// Now that you are connected, you can join channels with a topic:
let channel = socket.channel("topic:subtopic", {})
channel.join()
.receive("ok", resp => { console.log("Joined successfully", resp) })
.receive("error", resp => { console.log("Unable to join", resp) })
...
Channels: phoenix.js
…
channel.push("new_msg", {body: “Hello world!”})
…
…
channel.on("new_msg", payload => { … })
…
Channels: Incoming Events
defmodule HelloPhoenixWeb.RoomChannel do
use Phoenix.Channel
…
def handle_in("new_msg", %{"body" => body}, socket) do
broadcast! socket, "new_msg", %{body: body}
{:noreply, socket}
end
end
We can pattern match on the event names, like "new_msg", and then grab
the payload that the client passed over the channel.
Channels: Intercepting Outgoing Events
...
intercept ["user_joined"]
def handle_out("user_joined", msg, socket) do
if Accounts.ignoring_user?(socket.assigns[:user], msg.user_id) do
{:noreply, socket}
else
push socket, "user_joined", msg
{:noreply, socket}
end
end
...
This callback will be called for every recipient of a message, so more
expensive operations like hitting the database should be considered carefully
before being included in handle_out/3
Channels: Socket Assigns
Similar to connection structs, %Plug.Conn{}, it is possible to assign values
to a channel socket.

socket = assign(socket, :user, msg[“user”])
Sockets store assigned values as a map in socket.assigns.

user = socket.assigns[:user]
• Ecto is a domain specific language for writing queries and interacting with
databases in Elixir. 

• What's new in Ecto 2.1: pages.plataformatec.com.br/ebook-whats-new-
in-ecto-2-0
Ecto
• Ecto.Repo - repositories are wrappers around the data store.

• Ecto.Schema - schemas are used to map any data source into an Elixir
struct.

• Ecto.Changeset - allows developers to filter, cast, and validate changes
before we apply them to the data. 

• Ecto.Query - written in Elixir syntax, queries are used to retrieve
information from a given repository.
Repositories
Via the repository, we can create, update, destroy and query existing
database entries.
Repositories
hello_phoenix/lib/hello_phoenix/application.ex
…
def start(_type, _args) do
import Supervisor.Spec
children = [
# Start the Ecto repository
supervisor(HelloPhoenix.Repo, []),
…
hello_phoenix/lib/hello_phoenix/repo.ex
defmodule HelloPhoenix.Repo do
use Ecto.Repo, otp_app: :hello_phoenix
end
Repositories: config
hello_phoenix/config/dev.exs
...
# Configure your database
config :hello_phoenix, HelloPhoenix.Repo,
adapter: Ecto.Adapters.Postgres,
username: "postgres",
password: "postgres",
database: "hello_phoenix_dev",
hostname: "localhost",
pool_size: 10
...
A repository needs an adapter and credentials to communicate to the
database. Configuration for the Repo usually defined in your app config.
Schema
Schemas allows developers to define the shape of their data.
Schema
defmodule Weather do
use Ecto.Schema
# weather is the DB table
schema "weather" do
field :city, :string
field :temp_lo, :integer
field :temp_hi, :integer
field :prcp, :float, default: 0.0
end
end
An :id field with type :id (:id means :integer) is generated by default, which is
the primary key of the Schema.
Schema
By defining a schema, Ecto automatically defines a struct with the schema
fields:

iex> weather = %Weather{temp_lo: 30}
iex> weather.temp_lo
30
By defining a schema, Ecto automatically defines a struct with the schema
fields:

iex> weather = %Weather{temp_lo: 0, temp_hi: 23}
iex> weather = Repo.insert!(weather)
%Weather{...}
iex> weather.id
1
Schema
After persisting weather to the database, it will return a new copy of
%Weather{} with the primary key (the id) set. We can use this value to
interact with the repository:

# Get the struct back
iex> weather = Repo.get Weather, 1
%Weather{id: 1, ...}
# Delete it
iex> Repo.delete!(weather)
%Weather{...}
Changesets
Changesets allow developers to filter, cast, and validate changes before we
apply them to the data. 

Changesets are also capable of transforming database constraints, like
unique indexes and foreign key checks, into errors.
Changesets
defmodule User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :name
field :email
field :age, :integer
end
def changeset(user, params  %{}) do
user
|> cast(params, [:name, :email, :age])
|> validate_required([:name, :email])
|> validate_format(:email, ~r/@/)
|> validate_inclusion(:age, 18..100)
end
end
Changesets
Once a changeset is built, it can be given to functions like insert and update
in the repository that will return an :ok or :error tuple

changeset = User.changeset(%User{}, %{name: "Ivan", age: 30})
case Repo.update(changeset) do
{:ok, user} ->
# user updated
{:error, changeset} ->
# an error occurred
end
Changesets
We can easily provide different changesets for different use cases

def registration_changeset(user, params) do
# Changeset on create
end
def update_changeset(user, params) do
# Changeset on update
end
Query
Ecto allows you to write queries in Elixir and send them to the repository,
which translates them to the underlying database.
Query: predefined Schema
import Ecto.Query, only: [from: 2]
query = from u in User,
where: u.age > 18 or is_nil(u.email),
select: u
# Returns %User{} structs matching the query
Repo.all(query)
Query: directly against a table
query = from u in "users",
where: u.age > 18 or is_nil(u.email),
select: %{name: u.name, age: u.age}
# Returns maps as defined in select
Repo.all(query)
Query: accessing params values
# min = 33
def min_age(min) do
from u in User, where: u.age > ^min
end
# min = "35"
Repo.all(from u in "users",
where: u.age > type(^age, :integer),
select: u.name)
Query: fragments
def unpublished_by_title(title) do
from p in Post,
where: is_nil(p.published_at) and
fragment("lower(?)", p.title) == ^title
end
# PostgreSQL’s JSON/JSONB data type with fragments
fragment("?->>? ILIKE ?", p.map, "key_name", ^some_value)
Ecto.Multi
• Ecto.Multi is a data structure for grouping multiple Repo operations.

• Ecto.Multi makes it possible to pack operations that should be performed
in a single database transaction and gives a way to introspect the queued
operations without actually performing them. 

• Each operation is given a name that is unique and will identify its result in
case of success or failure.

• All operations will be executed in the order they were added.
Ecto.Multi
defmodule PasswordManager do
alias Ecto.Multi
def reset(account, params) do
Multi.new
|> Multi.update(:account, Account.password_reset_changeset(account, params))
|> Multi.insert(:log, Log.password_reset_changeset(account, params))
|> Multi.delete_all(:sessions, Ecto.assoc(account, :sessions))
end
end
Ecto.Multi
result = Repo.transaction(PasswordManager.reset(account, params))
case result do
{:ok, %{account: account, log: log, sessions: sessions}} ->
# Operation was successful, we can access results under keys
# we used for naming the operations.
{:error, failed_operation, failed_value, changes_so_far} ->
# One of the operations failed. We can access the operation's failure
# value (like changeset for operations on changesets) to prepare a
# proper response. We also get access to the results of any operations
# that succeeded before the indicated operation failed. However, any
# successful operations would have been rolled back.
end
Phoenix-Ecto Contexts
$ mix phx.gen.html Accounts User users name:string 
username:string:unique

$ mix phx.gen.context Accounts Credential credentials 
email:string:unique user_id:references:users
Accounts Context
defmodule HelloPhoenix.Accounts do
@moduledoc """
The Accounts context.
"""
import Ecto.Query, warn: false
alias HelloPhoenix.Repo
alias HelloPhoenix.Accounts.User
def list_users do
Repo.all(User)
end
def get_user!(id), do: Repo.get!(User, id)
def create_user(attrs  %{}) do
%User{}
|> User.changeset(attrs)
|> Repo.insert()
end
def update_user(%User{} = user, attrs) do
user
|> User.changeset(attrs)
|> Repo.update()
end
def delete_user(%User{} = user) do
Repo.delete(user)
end
def change_user(%User{} = user) do
User.changeset(user, %{})
end
alias HelloPhoenix.Accounts.Credential
def list_credentials do
Repo.all(Credential)
end
def get_credential!(id), do: Repo.get!
(Credential, id)
def create_credential(attrs  %{}) do
%Credential{}
|> Credential.changeset(attrs)
|> Repo.insert()
end
def update_credential(%Credential{} =
credential, attrs) do
credential
|> Credential.changeset(attrs)
|> Repo.update()
end
def delete_credential(%Credential{} =
credential) do
Repo.delete(credential)
end
def change_credential(%Credential{} =
credential) do
Credential.changeset(credential, %{})
end
end
THANK YOU!
Yurii Bodarev

@bodarev_yurii

github.com/ybod

More Related Content

What's hot

Golang 101
Golang 101Golang 101
Golang 101宇 傅
 
DevOps Meetup ansible
DevOps Meetup   ansibleDevOps Meetup   ansible
DevOps Meetup ansiblesriram_rajan
 
Docker Introduction.pdf
Docker Introduction.pdfDocker Introduction.pdf
Docker Introduction.pdfOKLABS
 
Building Bizweb Microservices with Docker
Building Bizweb Microservices with DockerBuilding Bizweb Microservices with Docker
Building Bizweb Microservices with DockerKhôi Nguyễn Minh
 
Monitoring Kubernetes with Prometheus
Monitoring Kubernetes with PrometheusMonitoring Kubernetes with Prometheus
Monitoring Kubernetes with PrometheusGrafana Labs
 
Designing Apps for Runtime Fabric: Logging, Monitoring & Object Store Persist...
Designing Apps for Runtime Fabric: Logging, Monitoring & Object Store Persist...Designing Apps for Runtime Fabric: Logging, Monitoring & Object Store Persist...
Designing Apps for Runtime Fabric: Logging, Monitoring & Object Store Persist...Eva Mave Ng
 
Prometheus in Practice: High Availability with Thanos (DevOpsDays Edinburgh 2...
Prometheus in Practice: High Availability with Thanos (DevOpsDays Edinburgh 2...Prometheus in Practice: High Availability with Thanos (DevOpsDays Edinburgh 2...
Prometheus in Practice: High Availability with Thanos (DevOpsDays Edinburgh 2...Thomas Riley
 
Monitoring kubernetes with prometheus
Monitoring kubernetes with prometheusMonitoring kubernetes with prometheus
Monitoring kubernetes with prometheusBrice Fernandes
 
An Overview of Web Services: SOAP and REST
An Overview of Web Services: SOAP and REST An Overview of Web Services: SOAP and REST
An Overview of Web Services: SOAP and REST Ram Awadh Prasad, PMP
 
Autoscale a self-healing cluster in OpenStack with Heat
Autoscale a self-healing cluster in OpenStack with HeatAutoscale a self-healing cluster in OpenStack with Heat
Autoscale a self-healing cluster in OpenStack with HeatRico Lin
 
NGINX High-performance Caching
NGINX High-performance CachingNGINX High-performance Caching
NGINX High-performance CachingNGINX, Inc.
 
Prometheus - basics
Prometheus - basicsPrometheus - basics
Prometheus - basicsJuraj Hantak
 
What is Node.js | Node.js Tutorial for Beginners | Node.js Modules | Node.js ...
What is Node.js | Node.js Tutorial for Beginners | Node.js Modules | Node.js ...What is Node.js | Node.js Tutorial for Beginners | Node.js Modules | Node.js ...
What is Node.js | Node.js Tutorial for Beginners | Node.js Modules | Node.js ...Edureka!
 
Angular 16 – the rise of Signals
Angular 16 – the rise of SignalsAngular 16 – the rise of Signals
Angular 16 – the rise of SignalsCoding Academy
 
Single page applications
Single page applicationsSingle page applications
Single page applicationsDiego Cardozo
 
CI/CD with Jenkins and Docker - DevOps Meetup Day Thailand
CI/CD with Jenkins and Docker - DevOps Meetup Day ThailandCI/CD with Jenkins and Docker - DevOps Meetup Day Thailand
CI/CD with Jenkins and Docker - DevOps Meetup Day ThailandTroublemaker Khunpech
 
Testando API REST - Parte 1
Testando API REST - Parte 1Testando API REST - Parte 1
Testando API REST - Parte 1alinebiath
 

What's hot (20)

Golang 101
Golang 101Golang 101
Golang 101
 
Nginx
NginxNginx
Nginx
 
DevOps Meetup ansible
DevOps Meetup   ansibleDevOps Meetup   ansible
DevOps Meetup ansible
 
Docker Introduction.pdf
Docker Introduction.pdfDocker Introduction.pdf
Docker Introduction.pdf
 
Building Bizweb Microservices with Docker
Building Bizweb Microservices with DockerBuilding Bizweb Microservices with Docker
Building Bizweb Microservices with Docker
 
Monitoring Kubernetes with Prometheus
Monitoring Kubernetes with PrometheusMonitoring Kubernetes with Prometheus
Monitoring Kubernetes with Prometheus
 
Designing Apps for Runtime Fabric: Logging, Monitoring & Object Store Persist...
Designing Apps for Runtime Fabric: Logging, Monitoring & Object Store Persist...Designing Apps for Runtime Fabric: Logging, Monitoring & Object Store Persist...
Designing Apps for Runtime Fabric: Logging, Monitoring & Object Store Persist...
 
Prometheus in Practice: High Availability with Thanos (DevOpsDays Edinburgh 2...
Prometheus in Practice: High Availability with Thanos (DevOpsDays Edinburgh 2...Prometheus in Practice: High Availability with Thanos (DevOpsDays Edinburgh 2...
Prometheus in Practice: High Availability with Thanos (DevOpsDays Edinburgh 2...
 
Monitoring kubernetes with prometheus
Monitoring kubernetes with prometheusMonitoring kubernetes with prometheus
Monitoring kubernetes with prometheus
 
An Overview of Web Services: SOAP and REST
An Overview of Web Services: SOAP and REST An Overview of Web Services: SOAP and REST
An Overview of Web Services: SOAP and REST
 
Autoscale a self-healing cluster in OpenStack with Heat
Autoscale a self-healing cluster in OpenStack with HeatAutoscale a self-healing cluster in OpenStack with Heat
Autoscale a self-healing cluster in OpenStack with Heat
 
NGINX High-performance Caching
NGINX High-performance CachingNGINX High-performance Caching
NGINX High-performance Caching
 
Prometheus - basics
Prometheus - basicsPrometheus - basics
Prometheus - basics
 
What is Node.js | Node.js Tutorial for Beginners | Node.js Modules | Node.js ...
What is Node.js | Node.js Tutorial for Beginners | Node.js Modules | Node.js ...What is Node.js | Node.js Tutorial for Beginners | Node.js Modules | Node.js ...
What is Node.js | Node.js Tutorial for Beginners | Node.js Modules | Node.js ...
 
Angular 16 – the rise of Signals
Angular 16 – the rise of SignalsAngular 16 – the rise of Signals
Angular 16 – the rise of Signals
 
Single page applications
Single page applicationsSingle page applications
Single page applications
 
Grafana
GrafanaGrafana
Grafana
 
CI/CD with Jenkins and Docker - DevOps Meetup Day Thailand
CI/CD with Jenkins and Docker - DevOps Meetup Day ThailandCI/CD with Jenkins and Docker - DevOps Meetup Day Thailand
CI/CD with Jenkins and Docker - DevOps Meetup Day Thailand
 
Alfresco Certificates
Alfresco Certificates Alfresco Certificates
Alfresco Certificates
 
Testando API REST - Parte 1
Testando API REST - Parte 1Testando API REST - Parte 1
Testando API REST - Parte 1
 

Similar to Ecto and Phoenix: Doing Web With Elixir

Phoenix for Rails Devs
Phoenix for Rails DevsPhoenix for Rails Devs
Phoenix for Rails DevsDiacode
 
Cocoapods and Most common used library in Swift
Cocoapods and Most common used library in SwiftCocoapods and Most common used library in Swift
Cocoapods and Most common used library in SwiftWan Muzaffar Wan Hashim
 
Hands-on with the Symfony2 Framework
Hands-on with the Symfony2 FrameworkHands-on with the Symfony2 Framework
Hands-on with the Symfony2 FrameworkRyan Weaver
 
Baruco 2014 - Rubymotion Workshop
Baruco 2014 - Rubymotion WorkshopBaruco 2014 - Rubymotion Workshop
Baruco 2014 - Rubymotion WorkshopBrian Sam-Bodden
 
Building Cloud Castles
Building Cloud CastlesBuilding Cloud Castles
Building Cloud CastlesBen Scofield
 
Creating your own framework on top of Symfony2 Components
Creating your own framework on top of Symfony2 ComponentsCreating your own framework on top of Symfony2 Components
Creating your own framework on top of Symfony2 ComponentsDeepak Chandani
 
OpenERP Technical Memento
OpenERP Technical MementoOpenERP Technical Memento
OpenERP Technical MementoOdoo
 
How to Build & Develop Responsive Open Learning Environments with the ROLE SDK
How to Build & Develop Responsive Open Learning Environments with the ROLE SDKHow to Build & Develop Responsive Open Learning Environments with the ROLE SDK
How to Build & Develop Responsive Open Learning Environments with the ROLE SDKDominik Renzel
 
How to Build & Develop Responsive Open Learning Environments with the ROLE SDK
How to Build & Develop Responsive Open Learning Environments with the ROLE SDKHow to Build & Develop Responsive Open Learning Environments with the ROLE SDK
How to Build & Develop Responsive Open Learning Environments with the ROLE SDKDominik Renzel
 
ElixirConf 2019 - Your Guide to Understand the Initial Commit of a Phoenix Pr...
ElixirConf 2019 - Your Guide to Understand the Initial Commit of a Phoenix Pr...ElixirConf 2019 - Your Guide to Understand the Initial Commit of a Phoenix Pr...
ElixirConf 2019 - Your Guide to Understand the Initial Commit of a Phoenix Pr...Jorge Bejar
 
Web Server and how we can design app in C#
Web Server and how we can design app  in C#Web Server and how we can design app  in C#
Web Server and how we can design app in C#caohansnnuedu
 
Introduction to Laravel Framework (5.2)
Introduction to Laravel Framework (5.2)Introduction to Laravel Framework (5.2)
Introduction to Laravel Framework (5.2)Viral Solani
 
walkmod: An open source tool for coding conventions
walkmod: An open source tool for coding conventionswalkmod: An open source tool for coding conventions
walkmod: An open source tool for coding conventionswalkmod
 
Yet Another Fog Simulator (YAFS) - user guide
Yet Another Fog Simulator (YAFS) - user guideYet Another Fog Simulator (YAFS) - user guide
Yet Another Fog Simulator (YAFS) - user guidewisaaco
 
Selenium & PHPUnit made easy with Steward (Berlin, April 2017)
Selenium & PHPUnit made easy with Steward (Berlin, April 2017)Selenium & PHPUnit made easy with Steward (Berlin, April 2017)
Selenium & PHPUnit made easy with Steward (Berlin, April 2017)Ondřej Machulda
 
Building web framework with Rack
Building web framework with RackBuilding web framework with Rack
Building web framework with Racksickill
 
Phoenix and the Plug-life
Phoenix and the Plug-lifePhoenix and the Plug-life
Phoenix and the Plug-lifedevmyndmichael
 
China Science Challenge
China Science ChallengeChina Science Challenge
China Science Challengeremko caprio
 
SgCodeJam24 Workshop
SgCodeJam24 WorkshopSgCodeJam24 Workshop
SgCodeJam24 Workshopremko caprio
 

Similar to Ecto and Phoenix: Doing Web With Elixir (20)

Phoenix for Rails Devs
Phoenix for Rails DevsPhoenix for Rails Devs
Phoenix for Rails Devs
 
Cocoapods and Most common used library in Swift
Cocoapods and Most common used library in SwiftCocoapods and Most common used library in Swift
Cocoapods and Most common used library in Swift
 
Hands-on with the Symfony2 Framework
Hands-on with the Symfony2 FrameworkHands-on with the Symfony2 Framework
Hands-on with the Symfony2 Framework
 
Baruco 2014 - Rubymotion Workshop
Baruco 2014 - Rubymotion WorkshopBaruco 2014 - Rubymotion Workshop
Baruco 2014 - Rubymotion Workshop
 
Building Cloud Castles
Building Cloud CastlesBuilding Cloud Castles
Building Cloud Castles
 
Creating your own framework on top of Symfony2 Components
Creating your own framework on top of Symfony2 ComponentsCreating your own framework on top of Symfony2 Components
Creating your own framework on top of Symfony2 Components
 
OpenERP Technical Memento
OpenERP Technical MementoOpenERP Technical Memento
OpenERP Technical Memento
 
How to Build & Develop Responsive Open Learning Environments with the ROLE SDK
How to Build & Develop Responsive Open Learning Environments with the ROLE SDKHow to Build & Develop Responsive Open Learning Environments with the ROLE SDK
How to Build & Develop Responsive Open Learning Environments with the ROLE SDK
 
How to Build & Develop Responsive Open Learning Environments with the ROLE SDK
How to Build & Develop Responsive Open Learning Environments with the ROLE SDKHow to Build & Develop Responsive Open Learning Environments with the ROLE SDK
How to Build & Develop Responsive Open Learning Environments with the ROLE SDK
 
Hexagonal architecture in PHP
Hexagonal architecture in PHPHexagonal architecture in PHP
Hexagonal architecture in PHP
 
ElixirConf 2019 - Your Guide to Understand the Initial Commit of a Phoenix Pr...
ElixirConf 2019 - Your Guide to Understand the Initial Commit of a Phoenix Pr...ElixirConf 2019 - Your Guide to Understand the Initial Commit of a Phoenix Pr...
ElixirConf 2019 - Your Guide to Understand the Initial Commit of a Phoenix Pr...
 
Web Server and how we can design app in C#
Web Server and how we can design app  in C#Web Server and how we can design app  in C#
Web Server and how we can design app in C#
 
Introduction to Laravel Framework (5.2)
Introduction to Laravel Framework (5.2)Introduction to Laravel Framework (5.2)
Introduction to Laravel Framework (5.2)
 
walkmod: An open source tool for coding conventions
walkmod: An open source tool for coding conventionswalkmod: An open source tool for coding conventions
walkmod: An open source tool for coding conventions
 
Yet Another Fog Simulator (YAFS) - user guide
Yet Another Fog Simulator (YAFS) - user guideYet Another Fog Simulator (YAFS) - user guide
Yet Another Fog Simulator (YAFS) - user guide
 
Selenium & PHPUnit made easy with Steward (Berlin, April 2017)
Selenium & PHPUnit made easy with Steward (Berlin, April 2017)Selenium & PHPUnit made easy with Steward (Berlin, April 2017)
Selenium & PHPUnit made easy with Steward (Berlin, April 2017)
 
Building web framework with Rack
Building web framework with RackBuilding web framework with Rack
Building web framework with Rack
 
Phoenix and the Plug-life
Phoenix and the Plug-lifePhoenix and the Plug-life
Phoenix and the Plug-life
 
China Science Challenge
China Science ChallengeChina Science Challenge
China Science Challenge
 
SgCodeJam24 Workshop
SgCodeJam24 WorkshopSgCodeJam24 Workshop
SgCodeJam24 Workshop
 

Recently uploaded

Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticscarlostorres15106
 
Maximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptxMaximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptxOnBoard
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Mattias Andersson
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesSinan KOZAK
 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...shyamraj55
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 3652toLead Limited
 
Build your next Gen AI Breakthrough - April 2024
Build your next Gen AI Breakthrough - April 2024Build your next Gen AI Breakthrough - April 2024
Build your next Gen AI Breakthrough - April 2024Neo4j
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Enterprise Knowledge
 
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024BookNet Canada
 
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr LapshynFwdays
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Scott Keck-Warren
 
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphSIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphNeo4j
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsRizwan Syed
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsMark Billinghurst
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):comworks
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationSlibray Presentation
 
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | DelhiFULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhisoniya singh
 
Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupFlorian Wilhelm
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonetsnaman860154
 

Recently uploaded (20)

Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
 
Maximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptxMaximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptx
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen Frames
 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
 
Build your next Gen AI Breakthrough - April 2024
Build your next Gen AI Breakthrough - April 2024Build your next Gen AI Breakthrough - April 2024
Build your next Gen AI Breakthrough - April 2024
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024
 
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
 
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024
 
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphSIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL Certs
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR Systems
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):
 
The transition to renewables in India.pdf
The transition to renewables in India.pdfThe transition to renewables in India.pdf
The transition to renewables in India.pdf
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck Presentation
 
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | DelhiFULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
 
Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project Setup
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 

Ecto and Phoenix: Doing Web With Elixir

  • 1. Ecto and Phoenix: Doing Web With Elixir Elixir Club 9, Kharkiv 2017
  • 2. • Elixir Applications • Umbrella Projects • Phoenix: Application Structure • Phoenix: Controllers, Templates and Views • Phoenix: JSON • Phoenix: Channels • ECTO
  • 3. Elixir (Erlang.OTP) Applications • In Elixir (Erlang/OTP), an application is a component implementing some specific functionality, that can be started and stopped as a unit, and which can be re-used in other systems. • Applications are defined with an application file named application_name.app in the same ebin directory as the compiled modules of the application. • In Elixir, the Mix build tool is responsible for compiling your source code and generating your application .app file. 
  • 4. Creating Elixir Application with Supervision Tree $ mix new hello_world --sup $ mix compile
  • 6. mix.exs def project do [ app: :hello_world, version: "0.1.0", elixir: "~> 1.6-dev", start_permanent: Mix.env() == :prod, deps: deps() ] end def application do [ extra_applications: [:logger], mod: {HelloWorld.Application, []} ] end defp deps do [] end
  • 7. Starting Elixir Application • You start one or more applications, each with their own initialization and termination logic. • Elixir does not have a main procedure that is responsible for starting your system. • Starting an application is done via the “application module callback”, which is a module that defines the start/2 function.
  • 8. lib/hello_world/application.ex use Application def start(_type, _args) do # List all child processes to be supervised children = [ # Starts a worker by calling: HelloWorld.Worker.start_link(arg) # {HelloWorld.Worker, arg}, ] opts = [strategy: :one_for_one, name: HelloWorld.Supervisor] Supervisor.start_link(children, opts) end
  • 9. Application Supervision Tree The start/2 function should start a supervisor, which is often called as the top-level supervisor, since it sits at the root of a potentially long supervision tree. lib/hello_phoenix/application.ex def start(_type, _args) do import Supervisor.Spec children = [ # Start the Ecto repository supervisor(HelloPhoenix.Repo, []), # Start the endpoint when the application starts supervisor(HelloPhoenixWeb.Endpoint, []), ] opts = [strategy: :one_for_one, name: HelloPhoenix.Supervisor] Supervisor.start_link(children, opts) end WW S S S W
  • 10. Umbrella projects $ mix new hello_umbrella --umbrella
  • 11. Umbrella projects hello_umbrella/mix.exs def project do [ apps_path: "apps", start_permanent: Mix.env() == :prod, deps: deps() ] end
  • 12. Applications in umbrella project $ cd hello_umbrella/apps $ mix new hello $ mix new world
  • 13. In umbrella dependencies hello_umbrella/apps/hello/mix.exs def project do [ app: :hello, version: "0.1.0", build_path: "../../_build", config_path: "../../config/config.exs", deps_path: "../../deps", lockfile: "../../mix.lock", elixir: "~> 1.6-dev", start_permanent: Mix.env() == :prod, deps: deps() ] end ... defp deps do [ {:world, in_umbrella: true} ] end
  • 14. Phoenix Framework: Productive. Reliable. Fast. • Phoenix is a web development framework written in Elixir which implements the server- side MVC pattern.  • Current version: 1.3.0 • http://phoenixframework.org/
  • 15.  Phoenix Layers • Phoenix itself is actually the top layer of a multi-layer system • Cowboy: the web server used by Phoenix (and Plug) is Cowboy (Erlang). • Plug is a specification for constructing composable modules to build web applications. Plugs are reusable modules or functions built to that specification. • Ecto is a language integrated query composition tool and database wrapper for Elixir.
  • 16. Phoenix Application $ mix phx.new hello_phoenix lib/hello_phoenix/application.ex defmodule HelloPhoenix.Application do use Application ... lib/hello_phoenix_web/endpoint.ex defmodule HelloPhoenixWeb.Endpoint do use Phoenix.Endpoint, otp_app: :hello_phoenix ...
  • 17. Phoenix Application mix.exs ... def application do [ mod: {HelloPhoenix.Application, []}, extra_applications: [:logger, :runtime_tools] ] end ... lib/hello_phoenix/application.ex ... def start(_type, _args) do import Supervisor.Spec children = [ # Start the Ecto repository supervisor(HelloPhoenix.Repo, []), # Start the endpoint when the application starts supervisor(HelloPhoenixWeb.Endpoint, []), ] opts = [strategy: :one_for_one, name: HelloPhoenix.Supervisor] Supervisor.start_link(children, opts) end ...
  • 18. Starting Phoenix Application $ mix phx.new hello_phoenix $ cd hello_phoenix $ mix deps.get $ mix ecto.create $ mix ecto.migrate $ mix phx.server ($ iex -S mix phx.server)
  • 19. Phoenix Umbrella Project mix phx.new phoenix --umbrella
  • 20. Plug • Core Phoenix components like Endpoints, Routers, and Controllers are all just Plugs internally. • Plug is a specification for composable modules in web application. • Plugs are reusable modules or functions built to that specification. • They provide discrete behaviors - like request header parsing or logging. • Because the Plug API is small and consistent, plugs can be defined and executed in a set order, like a pipeline.
  • 21. Plugs in Phoenix’s HTTP layer HTTP Request %Plug.Conn Plug Plug Plug Plug Plug Plug %Plug.Conn HTTP Response
  • 22. Plug.Conn devhints.io/phoenix-conn Request conn.host # → "example.com" conn.method # → "GET" conn.path_info # → ["posts", "1"] conn.request_path # → "/posts/1" conn.query_string # → "utm_source=twitter" conn.port # → 80 conn.scheme # → :http conn.peer # → { {127, 0, 0, 1}, 12345 } conn.remote_ip # → { 151, 236, 219, 228 } conn.req_headers # → [{"content-type", "text/plain"}] Response conn.resp_body # → "..." conn.resp_charset # → "utf-8" conn.resp_cookies # → ... conn.resp_headers # → ... conn.status # → … Assigns conn |> assign(:user_id, 100) conn.assigns[:user_id]
  • 23. Module Plug Example defmodule Example.HelloWorldPlug do import Plug.Conn def init(options), do: options def call(conn, _opts) do conn |> put_resp_content_type("text/plain") |> send_resp(200, "Hello World!") end end %Plug.Conn{…}
  • 24. Plug pipelines … pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers end pipeline :api do plug :accepts, ["json"] end …
  • 25. Phoenix Parts • Endpoint • Router • Controllers • Views • Templates • Channels
  • 26. Endpoint • Provides a wrapper for starting and stopping the endpoint as part of a supervision tree • Handles all aspects of requests up until the point where the router takes over • Defines an initial plug pipeline where requests are sent through • Hosts web specific configuration for your application • Dispatches requests into a designated router
  • 27. Endpoint … use Phoenix.Endpoint, otp_app: :hello_phoenix socket "/socket", HelloPhoenixWeb.UserSocket plug Plug.Static, ... if code_reloading? do ... end plug Plug.RequestId plug Plug.Logger plug Plug.Parsers, ... plug Plug.MethodOverride plug Plug.Head plug Plug.Session, ... plug HelloPhoenixWeb.Router …
  • 28. Starting Endpoint ... def start(_type, _args) do import Supervisor.Spec children = [ # Start the Ecto repository supervisor(HelloPhoenix.Repo, []), # Start the endpoint when the application starts supervisor(HelloPhoenixWeb.Endpoint, []), ] opts = [strategy: :one_for_one, name: HelloPhoenix.Supervisor] Supervisor.start_link(children, opts) end ...
  • 29. Router • Parses incoming requests and dispatches them to the correct controller/ action, passing parameters as needed • Provides helpers to generate route paths or urls to resources • Defines named pipelines through which we may pass our requests
  • 30. Router ... pipeline :api_client_id do plug :header_required, "x-consumer-metadata" plug :client_id_exists end ... scope "/api", EHealth.Web do pipe_through [:api, :api_client_id] # Legal Entities get "/legal_entities/:id", LegalEntityController, :show patch "/legal_entities/:id/actions/mis_verify", LegalEntityController, :mis_verify patch "/legal_entities/:id/actions/nhs_verify", LegalEntityController, :nhs_verify patch "/legal_entities/:id/actions/deactivate", LegalEntityController, :deactivate # Employees get "/employees/:id", EmployeeController, :show scope "/employees" do pipe_through [:client_context_list] patch "/:id/actions/deactivate", EmployeeController, :deactivate end ...
  • 31. Controllers & Actions • Controllers provide functions, called actions, to handle requests • Actions: • prepare data and pass it into views • invoke rendering via views • perform redirects
  • 32. Controllers & Actions defmodule EHealth.Web.LegalEntityController do use EHealth.Web, :controller ... action_fallback EHealth.Web.FallbackController def show(%Plug.Conn{req_headers: req_headers} = conn, %{"id" => id}) do with {:ok, legal_entity, security} <- API.get_legal_entity_by_id(id, req_headers) do conn |> assign_security(security) |> render("show.json", legal_entity: legal_entity) end end def mis_verify(%Plug.Conn{req_headers: req_headers} = conn, %{"id" => id}) do with {:ok, legal_entity} <- API.mis_verify(id, get_consumer_id(req_headers)) do render(conn, "show.json", legal_entity: legal_entity) end end ...
  • 33. Web Interface Entrypoint defmodule HelloPhoenixWeb do def controller do quote do use Phoenix.Controller, namespace: HelloPhoenixWeb import Plug.Conn import HelloPhoenixWeb.Router.Helpers import HelloPhoenixWeb.Gettext end end def view do ... end def router do ... end def channel do ... end ...
  • 34. Fallback Controller action_fallback(plug) • Registers the plug to call as a fallback to the controller action. • If the controller action fails to return a %Plug.Conn{}, the provided plug will be called and receive the controller’s %Plug.Conn{} as it was before the action was invoked along with the value returned from the controller action. web/controllers/fallback_controller.ex defmodule EHealth.Web.FallbackController do ... use EHealth.Web, :controller require Logger def call(conn, {:error, json_schema_errors}) when is_list(json_schema_errors) do conn |> put_status(422) |> render(EView.Views.ValidationError, "422.json", %{schema: json_schema_errors}) end def call(conn, {:error, errors, :query_parameter}) when is_list(errors) do conn |> put_status(422) |> render(EView.Views.ValidationError, "422.query.json", %{schema: errors}) end def call(conn, {:error, {:"422", error}}) do conn |> put_status(422) |> render(EView.Views.Error, :"400", %{message: error}) end ...
  • 35. Views • Defines the view layer of a Phoenix application • Render templates • Define helper functions, available in templates, to decorate data for presentation
  • 36. • Phoenix assumes a strong naming convention from controllers to views to the templates they render. • The PageController requires a PageView to render templates in the lib/hello_phoenix_web/ templates/page directory. Templates
  • 37. Web Interface Entrypoint defmodule HelloPhoenixWeb do def controller do ... end def view do quote do use Phoenix.View, root: "lib/hello_phoenix_web/templates", namespace: HelloPhoenixWeb # Import convenience functions from controllers import Phoenix.Controller, only: [get_flash: 2, view_module: 1] # Use all HTML functionality (forms, tags, etc) use Phoenix.HTML import HelloPhoenixWeb.Router.Helpers import HelloPhoenixWeb.ErrorHelpers import HelloPhoenixWeb.Gettext end end ...
  • 38. View Module defmodule HelloPhoenixWeb.PageView do use HelloPhoenixWeb, :view end Because we have defined the template root to be "lib/hello_phoenix_web/templates", Phoenix.View will automatically load all templates at “lib/hello_phoenix_web/ templates/page” and include them in the HelloPhoenixWeb.PageView. lib/hello_phoenix_web/controllers/page_controller.ex ... def index(conn, _params) do render conn, "index.html" end ...
  • 39. EEx Templates • EEx is the default template system in Phoenix • EEx module receives a template path and transforms its source code into Elixir quoted expressions • Templates are precompiled and fast • Template name - is the name of the template as given by the user, without the template engine extension, for example: “foo.html”
  • 40. HTML EEx Template Hello <%= @name %> <h3>Keys for the conn Struct</h3> <%= for key <- connection_keys @conn do %> <p><%= key %></p> <% end %>
  • 41. JSON - Controller defmodule EHealth.Web.DictionaryController do … alias EHealth.Dictionaries alias EHealth.Dictionaries.Dictionary action_fallback EHealth.Web.FallbackController def index(conn, params) do with {:ok, dictionaries} <- Dictionaries.list_dictionaries(params) do render(conn, "index.json", dictionaries: dictionaries) end end def update(conn, %{"name" => name} = dictionary_params) do with {:ok, %Dictionary{} = dictionary} <- Dictionaries.create_or_update_dictionary(name, dictionary_params) do render(conn, "show.json", dictionary: dictionary) end end End
  • 42. JSON - View defmodule EHealth.Web.DictionaryView do use EHealth.Web, :view alias EHealth.Web.DictionaryView def render("index.json", %{dictionaries: dictionaries}) do render_many(dictionaries, DictionaryView, "dictionary.json") end def render("show.json", %{dictionary: dictionary}) do render_one(dictionary, DictionaryView, "dictionary.json") end def render("dictionary.json", %{dictionary: dictionary}) do %{ name: dictionary.name, values: dictionary.values, labels: dictionary.labels, is_active: dictionary.is_active } end end
  • 43. Channels • Manage sockets for easy real-time communication • Allow bi-directional communication with persistent connections • Every time you join a channel, you need to choose which particular topic you want to listen to. • The topic is just an identifier, but by convention it is often made of two parts: "topic:subtopic".
  • 44. Channels hello_phoenix/lib/hello_phoenix_web/endpoint.ex ... socket "/socket", HelloPhoenixWeb.UserSocket ... hello_phoenix/lib/hello_phoenix_web/channels/user_socket.ex defmodule HelloPhoenixWeb.UserSocket do use Phoenix.Socket ## Channels channel "room:*", HelloPhoenixWeb.RoomChannel ... Any topic coming into the router with the "room:" prefix would dispatch to HelloPhoenixWeb.RoomChannel
  • 45. Channels - Socket hello_phoenix/lib/hello_phoenix_web/channels/user_socket.ex defmodule HelloPhoenixWeb.UserSocket do use Phoenix.Socket ## Channels channel "room:*", HelloPhoenixWeb.RoomChannel ## Transports transport :websocket, Phoenix.Transports.WebSocket # Socket params are passed from the client and can # be used to verify and authenticate a user. After # verification, you can put default assigns into # the socket that will be set for all channels, ie # # {:ok, assign(socket, :user_id, verified_user_id)} # # To deny connection, return `:error`. def connect(_params, socket) do {:ok, socket} end ...
  • 46. Joining Channels defmodule HelloPhoenixWeb.RoomChannel do use Phoenix.Channel def join("room:lobby", _message, socket) do {:ok, socket} end def join("room:" <> _private_room_id, _params, _socket) do {:error, %{reason: "unauthorized"}} end end
  • 47. Channels: phoenix.js hello_phoenix/assets/js/socket.js import {Socket} from "phoenix" let socket = new Socket("/socket", {params: {token: window.userToken}}) socket.connect() // Now that you are connected, you can join channels with a topic: let channel = socket.channel("topic:subtopic", {}) channel.join() .receive("ok", resp => { console.log("Joined successfully", resp) }) .receive("error", resp => { console.log("Unable to join", resp) }) ...
  • 48. Channels: phoenix.js … channel.push("new_msg", {body: “Hello world!”}) … … channel.on("new_msg", payload => { … }) …
  • 49. Channels: Incoming Events defmodule HelloPhoenixWeb.RoomChannel do use Phoenix.Channel … def handle_in("new_msg", %{"body" => body}, socket) do broadcast! socket, "new_msg", %{body: body} {:noreply, socket} end end We can pattern match on the event names, like "new_msg", and then grab the payload that the client passed over the channel.
  • 50. Channels: Intercepting Outgoing Events ... intercept ["user_joined"] def handle_out("user_joined", msg, socket) do if Accounts.ignoring_user?(socket.assigns[:user], msg.user_id) do {:noreply, socket} else push socket, "user_joined", msg {:noreply, socket} end end ... This callback will be called for every recipient of a message, so more expensive operations like hitting the database should be considered carefully before being included in handle_out/3
  • 51. Channels: Socket Assigns Similar to connection structs, %Plug.Conn{}, it is possible to assign values to a channel socket. socket = assign(socket, :user, msg[“user”]) Sockets store assigned values as a map in socket.assigns. user = socket.assigns[:user]
  • 52. • Ecto is a domain specific language for writing queries and interacting with databases in Elixir. • What's new in Ecto 2.1: pages.plataformatec.com.br/ebook-whats-new- in-ecto-2-0
  • 53. Ecto • Ecto.Repo - repositories are wrappers around the data store. • Ecto.Schema - schemas are used to map any data source into an Elixir struct. • Ecto.Changeset - allows developers to filter, cast, and validate changes before we apply them to the data.  • Ecto.Query - written in Elixir syntax, queries are used to retrieve information from a given repository.
  • 54. Repositories Via the repository, we can create, update, destroy and query existing database entries.
  • 55. Repositories hello_phoenix/lib/hello_phoenix/application.ex … def start(_type, _args) do import Supervisor.Spec children = [ # Start the Ecto repository supervisor(HelloPhoenix.Repo, []), … hello_phoenix/lib/hello_phoenix/repo.ex defmodule HelloPhoenix.Repo do use Ecto.Repo, otp_app: :hello_phoenix end
  • 56. Repositories: config hello_phoenix/config/dev.exs ... # Configure your database config :hello_phoenix, HelloPhoenix.Repo, adapter: Ecto.Adapters.Postgres, username: "postgres", password: "postgres", database: "hello_phoenix_dev", hostname: "localhost", pool_size: 10 ... A repository needs an adapter and credentials to communicate to the database. Configuration for the Repo usually defined in your app config.
  • 57. Schema Schemas allows developers to define the shape of their data.
  • 58. Schema defmodule Weather do use Ecto.Schema # weather is the DB table schema "weather" do field :city, :string field :temp_lo, :integer field :temp_hi, :integer field :prcp, :float, default: 0.0 end end An :id field with type :id (:id means :integer) is generated by default, which is the primary key of the Schema.
  • 59. Schema By defining a schema, Ecto automatically defines a struct with the schema fields: iex> weather = %Weather{temp_lo: 30} iex> weather.temp_lo 30 By defining a schema, Ecto automatically defines a struct with the schema fields: iex> weather = %Weather{temp_lo: 0, temp_hi: 23} iex> weather = Repo.insert!(weather) %Weather{...} iex> weather.id 1
  • 60. Schema After persisting weather to the database, it will return a new copy of %Weather{} with the primary key (the id) set. We can use this value to interact with the repository: # Get the struct back iex> weather = Repo.get Weather, 1 %Weather{id: 1, ...} # Delete it iex> Repo.delete!(weather) %Weather{...}
  • 61. Changesets Changesets allow developers to filter, cast, and validate changes before we apply them to the data. Changesets are also capable of transforming database constraints, like unique indexes and foreign key checks, into errors.
  • 62. Changesets defmodule User do use Ecto.Schema import Ecto.Changeset schema "users" do field :name field :email field :age, :integer end def changeset(user, params %{}) do user |> cast(params, [:name, :email, :age]) |> validate_required([:name, :email]) |> validate_format(:email, ~r/@/) |> validate_inclusion(:age, 18..100) end end
  • 63. Changesets Once a changeset is built, it can be given to functions like insert and update in the repository that will return an :ok or :error tuple changeset = User.changeset(%User{}, %{name: "Ivan", age: 30}) case Repo.update(changeset) do {:ok, user} -> # user updated {:error, changeset} -> # an error occurred end
  • 64. Changesets We can easily provide different changesets for different use cases def registration_changeset(user, params) do # Changeset on create end def update_changeset(user, params) do # Changeset on update end
  • 65. Query Ecto allows you to write queries in Elixir and send them to the repository, which translates them to the underlying database.
  • 66. Query: predefined Schema import Ecto.Query, only: [from: 2] query = from u in User, where: u.age > 18 or is_nil(u.email), select: u # Returns %User{} structs matching the query Repo.all(query)
  • 67. Query: directly against a table query = from u in "users", where: u.age > 18 or is_nil(u.email), select: %{name: u.name, age: u.age} # Returns maps as defined in select Repo.all(query)
  • 68. Query: accessing params values # min = 33 def min_age(min) do from u in User, where: u.age > ^min end # min = "35" Repo.all(from u in "users", where: u.age > type(^age, :integer), select: u.name)
  • 69. Query: fragments def unpublished_by_title(title) do from p in Post, where: is_nil(p.published_at) and fragment("lower(?)", p.title) == ^title end # PostgreSQL’s JSON/JSONB data type with fragments fragment("?->>? ILIKE ?", p.map, "key_name", ^some_value)
  • 70. Ecto.Multi • Ecto.Multi is a data structure for grouping multiple Repo operations. • Ecto.Multi makes it possible to pack operations that should be performed in a single database transaction and gives a way to introspect the queued operations without actually performing them. • Each operation is given a name that is unique and will identify its result in case of success or failure. • All operations will be executed in the order they were added.
  • 71. Ecto.Multi defmodule PasswordManager do alias Ecto.Multi def reset(account, params) do Multi.new |> Multi.update(:account, Account.password_reset_changeset(account, params)) |> Multi.insert(:log, Log.password_reset_changeset(account, params)) |> Multi.delete_all(:sessions, Ecto.assoc(account, :sessions)) end end
  • 72. Ecto.Multi result = Repo.transaction(PasswordManager.reset(account, params)) case result do {:ok, %{account: account, log: log, sessions: sessions}} -> # Operation was successful, we can access results under keys # we used for naming the operations. {:error, failed_operation, failed_value, changes_so_far} -> # One of the operations failed. We can access the operation's failure # value (like changeset for operations on changesets) to prepare a # proper response. We also get access to the results of any operations # that succeeded before the indicated operation failed. However, any # successful operations would have been rolled back. end
  • 73. Phoenix-Ecto Contexts $ mix phx.gen.html Accounts User users name:string username:string:unique $ mix phx.gen.context Accounts Credential credentials email:string:unique user_id:references:users
  • 74. Accounts Context defmodule HelloPhoenix.Accounts do @moduledoc """ The Accounts context. """ import Ecto.Query, warn: false alias HelloPhoenix.Repo alias HelloPhoenix.Accounts.User def list_users do Repo.all(User) end def get_user!(id), do: Repo.get!(User, id) def create_user(attrs %{}) do %User{} |> User.changeset(attrs) |> Repo.insert() end def update_user(%User{} = user, attrs) do user |> User.changeset(attrs) |> Repo.update() end def delete_user(%User{} = user) do Repo.delete(user) end def change_user(%User{} = user) do User.changeset(user, %{}) end alias HelloPhoenix.Accounts.Credential def list_credentials do Repo.all(Credential) end def get_credential!(id), do: Repo.get! (Credential, id) def create_credential(attrs %{}) do %Credential{} |> Credential.changeset(attrs) |> Repo.insert() end def update_credential(%Credential{} = credential, attrs) do credential |> Credential.changeset(attrs) |> Repo.update() end def delete_credential(%Credential{} = credential) do Repo.delete(credential) end def change_credential(%Credential{} = credential) do Credential.changeset(credential, %{}) end end