DEV Community

Cover image for Connecting Ember.js with Phoenix
Kevin Expósito
Kevin Expósito

Posted on • Edited on

8 1 1

Connecting Ember.js with Phoenix

Introduction

The aim of this article is not only to show how to connect a backend application using Phoenix and a frontend application using Ember, but also to know all the different issues that may be encountered when trying to connect any frontend or backend application.

Versions used in this guide:

Elixir: 1.11.3
Erlang/OTP: 21
Phoenix: 1.5.7

Node: 12.13.0
Ember CLI: 3.24.0
Enter fullscreen mode Exit fullscreen mode

Creating Phoenix backend

Let's start by creating our project in Phoenix by running the following command:

$ mix phx.new pizza_corner_api
(...)
$ cd pizza_corner_api
$ mix ecto.create
$ iex -S mix phx.server
Enter fullscreen mode Exit fullscreen mode

Then, let's create a migration in our application:

$ mix phx.gen.schema Api.Pizza pizzas name:string description:text image:string
Enter fullscreen mode Exit fullscreen mode

This will generate the following files:

defmodule PizzaCornerApi.Api.Pizza do
use Ecto.Schema
import Ecto.Changeset
schema "pizzas" do
field :description, :string
field :image, :string
field :name, :string
timestamps()
end
@doc false
def changeset(pizza, attrs) do
pizza
|> cast(attrs, [:name, :description, :image])
|> validate_required([:name, :description, :image])
end
end
view raw pizza.ex hosted with ❤ by GitHub
defmodule PizzaCornerApi.Repo.Migrations.CreatePizzas do
use Ecto.Migration
def change do
create table(:pizzas) do
add :name, :string
add :description, :text
add :image, :string
timestamps()
end
end
end

Now, we are going to apply the migration by running:

$ mix ecto.migrate
Enter fullscreen mode Exit fullscreen mode

Nice! we have the Pizza table created, let's add some seeds:

alias PizzaCornerApi.Repo
alias PizzaCornerApi.Api.Pizza
[
%{
name: "Neapolitan Pizza",
description:
"Neapolitan is the original pizza. This delicious pie dates all the way back to 18th century in Naples, Italy. During this time, the poorer citizens of this seaside city frequently purchased food that was cheap and could be eaten quickly. Luckily for them, Neapolitan pizza was affordable and readily available through numerous street vendors",
image:
"https://cdnimg.webstaurantstore.com/uploads/buying_guide/2014/11/pizzatypes-margherita-.jpg"
},
%{
name: "Chicago Pizza",
description:
"Chicago pizza, also commonly referred to as deep-dish pizza, gets its name from the city it was invented in. During the early 1900’s, Italian immigrants in the windy city were searching for something similar to the Neapolitan pizza that they knew and loved. Instead of imitating the notoriously thin pie, Ike Sewell had something else in mind. He created a pizza with a thick crust that had raised edges, similar to a pie, and ingredients in reverse, with slices of mozzarella lining the dough followed by meat, vegetables, and then topped with a can of crushed tomatoes. This original creation led Sewell to create the now famous chain restaurant, Pizzeria Uno.",
image:
"https://cdnimg.webstaurantstore.com/uploads/buying_guide/2014/11/pizzatypes-deepdish.jpg"
},
%{
name: "New York-Style Pizza",
description:
"With its characteristic large, foldable slices and crispy outer crust, New York-style pizza is one of America’s most famous regional pizza types. Originally a variation of Neapolitan-style pizza, the New York slice has taken on a fame all its own, with some saying its unique flavor has to do with the minerals present in New York’s tap water supply",
image: "https://cdnimg.webstaurantstore.com/uploads/blog/2016/8/flat.jpg"
},
%{
name: "Sicilian Pizza",
description:
"Sicilian pizza, also known as sfincione, provides a thick cut of pizza with pillowy dough, a crunchy crust, and robust tomato sauce. This square-cut pizza is served with or without cheese, and often with the cheese underneath the sauce to prevent the pie from becoming soggy. Sicilian pizza was brought to America in the 19th century by Sicilian immigrants and became popular in the United States after the Second World War.",
image: "https://cdnimg.webstaurantstore.com/uploads/blog/2016/8/rectangle.jpg"
},
%{
name: "Greek Pizza",
description:
"Greek pizza was created by Greek immigrants who came to America and were introduced to Italian pizza. Greek-style pizza, especially popular in the New England states, features a thick and chewy crust cooked in shallow, oiled pans, resulting in a nearly deep-fried bottom. While this style has a crust that is puffier and chewier than thin crust pizzas, it’s not quite as thick as a deep-dish or Sicilian crust.",
image: "https://cdnimg.webstaurantstore.com/uploads/blog/2016/8/onions.jpg"
},
%{
name: "California Pizza",
description:
"California pizza, or gourmet pizza, is known for its unusual ingredients. This pizza got its start back in the late 1970’s when Chef Ed LaDou began experimenting with pizza recipes in the classic Italian restaurant, Prego. He created a pizza with mustard, ricotta, pate, and red pepper, and by chance, served it to Wolfgang Puck. Impressed with LaDou’s innovative pie, Puck invited him to be a head pizza chef at his restaurant. It was here that LaDou came up with over 250 unique pizza recipes that eventually formed the menu of the chain restaurant California Pizza Kitchen.",
image:
"https://cdnimg.webstaurantstore.com/uploads/buying_guide/2014/11/pizzatypes-gourmet.jpg"
},
%{
name: "Detroit Pizza",
description:
"Reflecting the city’s deep ties to the auto industry, Detroit-style pizza was originally baked in a square automotive parts pan in the 1940’s. Detroit pizza is first topped with pepperoni, followed by brick cheese which is spread to the very edges of the pan, yielding a caramelized cheese perimeter. Sauce is then spooned over the pizza, an order similar to Chicago-style pizza. This pizza features a thick, extra crispy crust that is tender and airy on the inside.",
image:
"https://cdnimg.webstaurantstore.com/uploads/blog/2019/3/blog-types-pizza_in-blog-7.jpg"
},
%{
name: "St. Louis Pizza",
description:
"Looking for a light slice? St. Louis pizza features a thin crust with a cracker-like consistency that is made without yeast. Due to the crispy crust, St. Louis pizza is usually cut into three- or four-inch rectangles, known as party or tavern cut. This pizza features Provel processed cheese, which is a gooey combination of cheddar, Swiss, and provolone cheeses. St. Louis received an influx of Italian immigrants in the 19th century who were looking for employment opportunities. The Italian community, largely from Milan and Sicily, created the St. Louis-style pizza. Its sweet sauce is reminiscent of the Sicilian influence.",
image:
"https://cdnimg.webstaurantstore.com/uploads/blog/2019/3/blog-types-pizza_in-blog-8.jpg"
}
]
|> Enum.each(fn pizza ->
%Pizza{} |> Pizza.changeset(pizza) |> Repo.insert()
end)
view raw seeds.exs hosted with ❤ by GitHub



After this, run:

$ mix run priv/repo/seeds.exs
Enter fullscreen mode Exit fullscreen mode

Returning JSON API Responses with JaSerializer

Now we are almost ready to create the PizzaController and router, but before doing that, let's think about what we are doing. We are building the frontend in Ember and the backend in Phoenix. These technologies must communicate with each other. To do this we are going to use JSON API. This means that Phoenix and Ember apps must send and receive JSONAPI responses.

To do this in Phoenix we are going to use JaSerializer (https://github.com/vt-elixir/ja_serializer).

Installing JaSerializer

# mix.exs
defp deps do
    ...
    {:ja_serializer, "~> 0.16.0"},
    {:poison, "~> 3.1"}
    ...
end
Enter fullscreen mode Exit fullscreen mode
$ mix deps.get
Enter fullscreen mode Exit fullscreen mode
# config.exs
config :phoenix, :format_encoders,
  "json-api": Poison

config :mime, :types, %{
  "application/vnd.api+json" => ["json-api"]
}
Enter fullscreen mode Exit fullscreen mode

And then:

$ mix deps.clean mime --build
$ mix deps.get
Enter fullscreen mode Exit fullscreen mode
# router.ex
pipeline :api do
  plug :accepts, ["json-api"]
  plug JaSerializer.ContentTypeNegotiation
  plug JaSerializer.Deserializer
end
Enter fullscreen mode Exit fullscreen mode

Finally, we are going to create the Pizza View and Controller:

defmodule PizzaCornerApiWeb.PizzaView do
use PizzaCornerApiWeb, :view
use JaSerializer.PhoenixView
attributes [:name, :description, :image]
end
view raw pizza_view.ex hosted with ❤ by GitHub
defmodule PizzaCornerApiWeb.PizzaController do
use PizzaCornerApiWeb, :controller
alias PizzaCornerApi.Repo
alias PizzaCornerApi.Api.Pizza
def index(conn, _params) do
render conn, "index.json-api", data: Repo.all(Pizza)
end
end

Don't forget to add the endpoint to the router!

# router.ex
scope "/api", PizzaCornerApiWeb do
  pipe_through :api

  get "/pizzas", PizzaController, :index
end
Enter fullscreen mode Exit fullscreen mode

Creating the Ember frontend

$ ember new pizza_corner --no-welcome
Enter fullscreen mode Exit fullscreen mode

Adding the Pizza Model

import Model, { attr } from '@ember-data/model';
export default class PizzaModel extends Model {
@attr('string') name;
@attr('string') description;
@attr('string') image;
}
view raw pizza.js hosted with ❤ by GitHub
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
export default class ApplicationRoute extends Route {
@service store
model() {
return this.store.findAll('pizza');
}
}
view raw application.js hosted with ❤ by GitHub
{{page-title "ThePizzaCorner"}}
{{#each @model as |pizza|}}
<div class="card">
<img src={{pizza.image}} alt="pizza-image" class="pizza-image">
<div class="container">
<h3><b>{{pizza.name}}</b></h3>
<p class="pizza-description">{{pizza.description}}</p>
</div>
</div>
{{/each}}
view raw application.hbs hosted with ❤ by GitHub
body {
background: #FB7659;
}
.card {
border-radius: 15px;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
transition: transform 0.3s;
display: flex;
flex-direction: row;
background: white;
margin: 50px 20px;
}
.card:hover {
box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
transform: scale(1.02);
}
.container {
padding: 2px 16px;
}
.pizza-image {
height: 200px;
width: 200px;
}
.pizza-description {
font-size: 18px;
}
view raw app.css hosted with ❤ by GitHub



Now, lets run the following code to see if everything is working fine:

$ ember s
Enter fullscreen mode Exit fullscreen mode

and check it http://localhost:4200

What is happening?

If we open the console, we will see something like this:
Alt Text
What does this mean? By default Ember will request the same URL it is running on, so we must specify that we want to hit our Phoenix backend. To do this, we have to override the adapter as follows:

import JSONAPIAdapter from '@ember-data/adapter/json-api';
export default class ApplicationAdapter extends JSONAPIAdapter {
namespace = 'api'
host = 'http://localhost:4000';
}
view raw application.js hosted with ❤ by GitHub

host is the url that we want to hit, and namespace is the path after the host. This is because, in the Phoenix router we specified that pizzas are inside api scope. By doing this and thanks to Ember JSONAPIAdapter, store.findAll('pizza') will hit http://localhost:4000/api/pizzas . To see more about this you can see: https://api.emberjs.com/ember-data/release/classes/JSONAPIAdapter

Update your app/serializers/application.js:

import JSONAPISerializer from '@ember-data/serializer/json-api';
export default class ApplicationSerializer extends JSONAPISerializer {
}
view raw appication.js hosted with ❤ by GitHub

Now, let's try again, hopefully we will see some magic.
And no magic happens, So.. lets see the inspector again 😡
Alt Text

We are having a CORS issue here. Why does this happens?

This happens because of security. CORS doesn't allow communication between our Ember web app (http://localhost:4200) and our Phoenix api (http://localhost:4000) because they are not from the same path and this is not safe.

What can we do to fix this?

In order to fix this, we should tell our Phoenix API to accept incoming requests from Ember. To do this, we will use CORS Plug https://github.com/mschae/cors_plug, this plugin will allow us to configure that.

Installing CORS Plug

# mix.exs
defp deps do
    ...
    {:cors_plug, "~> 2.0"},
    ...
end
Enter fullscreen mode Exit fullscreen mode

And then run:

$ mix deps.get
Enter fullscreen mode Exit fullscreen mode

Also add:

#lib/pizza_corner_api_web/endpoint.ex
defmodule PizzaCornerApiWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :pizza_corner_api
    ...
    ...
    plug CORSPlug, origin: ["http://localhost:4200"] # <- Add this
    plug PizzaCornerApiWeb.Router
end
Enter fullscreen mode Exit fullscreen mode

And after this, restart the server:

$ mix phx.server
Enter fullscreen mode Exit fullscreen mode

Now visit http://localhost:4200 one more time. You will see the pizzas from our backend being rendered! 🎉🎉🍕

Alt Text
Phoenix Documentation:

Ember Documentation:

Things we used here:

Image of Datadog

The Future of AI, LLMs, and Observability on Google Cloud

Datadog sat down with Google’s Director of AI to discuss the current and future states of AI, ML, and LLMs on Google Cloud. Discover 7 key insights for technical leaders, covering everything from upskilling teams to observability best practices

Learn More

Top comments (3)

Collapse
 
charlesfries profile image
Charles Fries

TIL --no-welcome is a thing, thank you sir

Collapse
 
madza profile image
Madza

Phoenix!? Time to search it up 😄😄

Collapse
 
santiago_veiga_53b2edaf0e profile image
Santiago Veiga

very helpful!

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay