Web Apps using the Elixer Phoenix Framework
Updated: 08 May 2024
Notes based on Phoenix Framework REST API Crash Course
Prerequisites
Probably take a look at the Elixir Notes first
In order to get started the following are required:
It’s also handy to have the following installed if using VSCode:
Create the Application
Mix is a build tool for Elixir - To create the application we will use the mix
CLI along with the Phoenix template
In the above setup we’re going to use SQLite as the database. You can find the configuration for this in config/dev.exs
The databse is fairly abstracted from our application so the overall implementation shouldn’t differ too much other than in some configuration
Thereafter, you can start your application using:
In future, when updating dependencies the following command can be used to install dependencies:
Application Structure
The application code is laid out as follows:
Some of the important files we have are:
mix.ex
- dependencies and project metaconfig/dev.ex
- development config - particularly database configlib/elixirphoenix_web/router.ex
- application routes
Routing
We can take a look at the router.ex
file to view our initial app structure:
In the above we can se that we are using the PageController
, the PageController
is defined also as:
The above is rendering the :home
page which we can find by the lib/elixirphoenix_web/controllers/page_html/home.html.heex
which is just a template file. We can replace the contents with a simple hello world
Creating a Route
In general, we follow the following process when adding routes to our application:
- We go to the router file and define a reference to our routes
- We go to the controller file and define the routes which may render a template
- We go to the template file which renders our content
Let’s create a route in the PageController
called users
. Do this we need to asadd a reference to it in the router.ex
file:
Next, we can add a function in the PageController
called users
as we defined by :users
above:
And we can create a users.html.heex
file:
In the heex
file above, the elixir code that is embedded in the template is denoted by the <%= ... %>
.
HEEx
stands for HTML + Embedded Elixir
Working with Data
Phoenix
Defining a JSON resource
We can use phoenix and mix to generate and inialize a resource using the Mix CLI
We’re going to generate a simple entity called a Post
for our application
The above commands are in the structure of
Context Entity dbtable ...fields
The Context
is an elixir module that will be used to contain all the related functionality for a given entity. For example we may have a User
in multiple different Contexts
such as Accounts.user
and Payments.user
The above commands will generate the relvant modules and JSON code for interacting with application entities:
- Controllers for each resource
- Entity schemas for each resource
- Migrations for each resource
Overall, when working with phoenix, we use the Model-View-Controller architecture (MVC), when building an API, we can think of the JSON structure as the view for the sake of our API
Hooking up the Generated Code
After running the above command we will have some instructions telling us to add some content to the router.ex
file. We’re going to first create a new scope and add it into that scope:
Next, we need to run the following command as instructed:
Which will run the database migration that sets up the posts which was generated during the above command:
This sets up the database to work with the Post
entity that was generated which can be seen below:
Working with Resourecs
Which we then access from the controller that we exposed earlier in our router:
If we run our app now using mix phx.server
we can go to http://localhost:4000/api/posts
where we will see the date returned by our controller:
We can also do the same request using Nushell or any other HTTP Client you want
This is empty since we have nothing in our database as yet. We can create a POST request with the data for a user to the same endpoint which will create a new Post using the following:
The above is the format of our API though we can change this if we wanted. The method handling the above request can be seen below:
If we post invalid data we will return an error depending on the data we send as will be defined from the
We can also see in the above that we return data using the render(:show, post: post)
call, this renders the response using the following template:
Viewing Routes
We can get a view of the routes that our application has available using the following command:
The above shows us the routes that exist in our app. Phoenix is largely convention based and so our controllers will have a fairly standard structure
We can also see that the POST and PATCH for our resource point to the :update
method. This is because by default the POST and PATCH both work like a PATCH. If you want replace data you can create a separate method that would work as a normal POST method
Resource Relationships
We can create another resource for Users that can have posts associated with them, we can generate this using the following:
Next, we can follow the instructions to add the resource to our router:
We can also remove the users
that we had before:
And run the migration:
Now, we want to associate a user with a post. We’re going to do this to add a user
to a post:
To do this, we need to create new migration
This will generate the following empty migration:
In this file we will need to specify the change we want to make to our database table:
Next we can apply the migration with mix ecto.migrate
We need to also define that we have a user_id
in our schemas. We need to do this in both our resource types:
For the Post, we need to define the relationship as well as the validation information:
We can create a user by sending the following:
And then creating a Post with the associated user ID that we get back:
If we want the user_id
to be returned with the Post data we can modify the post_json.ex
file:
Getting Nested Data
If we want to get the posts when getting a user, we can make it such that we include the data. This is done from the context where we can add Repo.preload(:post)
into the get_user
as well as our list_users
functions:
Then we need to update our view to include posts. First, we can change the data
function from our post_json.ex
file to not be private:
And we can then use that from our user_json
file:
This will load the posts property when we query a user, so now if we do:
The above implementation however will not catch all cases where we can load the data since we try to convert this view to JSON in cases such as creation or updating where we may not want to preload the data. In these cases we can also use pattern matching to check if the data has not been loaded: