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
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
Then, let's create a migration in our application:
$ mix phx.gen.schema Api.Pizza pizzas name:string description:text image:string
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 |
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
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) |
After this, run:
$ mix run priv/repo/seeds.exs
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
$ mix deps.get
# config.exs
config :phoenix, :format_encoders,
"json-api": Poison
config :mime, :types, %{
"application/vnd.api+json" => ["json-api"]
}
And then:
$ mix deps.clean mime --build
$ mix deps.get
# router.ex
pipeline :api do
plug :accepts, ["json-api"]
plug JaSerializer.ContentTypeNegotiation
plug JaSerializer.Deserializer
end
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 |
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
Creating the Ember frontend
$ ember new pizza_corner --no-welcome
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; | |
} |
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'); | |
} | |
} |
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; | |
} |
Now, lets run the following code to see if everything is working fine:
$ ember s
and check it http://localhost:4200
What is happening?
If we open the console, we will see something like this:
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'; | |
} |
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 { | |
} |
Now, let's try again, hopefully we will see some magic.
And no magic happens, So.. lets see the inspector again 😡
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
And then run:
$ mix deps.get
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
And after this, restart the server:
$ mix phx.server
Now visit http://localhost:4200 one more time. You will see the pizzas from our backend being rendered! 🎉🎉🍕
- https://hexdocs.pm/phoenix/up_and_running.html
- https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Schema.html
- https://hexdocs.pm/phoenix/routing.html
Ember Documentation:
- https://cli.emberjs.com/release/basic-use/cli-commands/
- https://guides.emberjs.com/release/getting-started/quick-start/
- https://guides.emberjs.com/release/routing/specifying-a-routes-model/
- https://guides.emberjs.com/release/models/#toc_models
- https://guides.emberjs.com/release/models/customizing-adapters/
- https://api.emberjs.com/ember-data/release/classes/JSONAPIAdapter
Things we used here:
- JaSerializer (https://github.com/vt-elixir/ja_serializer)
- Poison (https://github.com/devinus/poison)
- Cors Plug (https://github.com/mschae/cors_plug)
Top comments (3)
TIL
--no-welcome
is a thing, thank you sirPhoenix!? Time to search it up 😄😄
very helpful!