Building a Social Network With Elixir, Phoenix and Angular.js— Part 1

Léonard Hetsch
11 min readDec 29, 2016

--

As a backend engineer, so far I’ve been using mainly Ruby on Rails, PHP or Javascript, both on my daily work and personal projects. I’m always hungry to learn new technologies to expand my knowledge, and discover new ways of thinking and solving problems. So, I wanted to learn more about recent programming languages or frameworks. I’ve tried some of them, but the last one that really caught my attention was Elixir.

For those who don’t know yet about this language, I encourage you to give it a try. It simply brings the sexy syntax of Ruby to the power of the Erlang VM.

Phoenix is, as of today, the de facto framework when it comes to build web applications with Elixir. You can think of it as the “Rails” of Elixir, especially given the fact that it took a lot from the Rails architecture and philosophy.

In the process of learning, I decided this time not only to build a real-case application, but also to write at the same time this series of tutorials explaining how I built it. Doing so is actually a new challenge for me, as this is my first tutorial (and post, for that matter) here on Medium. I also think it’s a good way to take a step back from the coding itself and have a second look on how to learn and build new things.

We’ll be building a small social networking app, using Phoenix for the backend API, and Angular.js for the front-end web app, both parts communicating together using JSON endpoints and websockets. Our app will include all basic social networking features, including user signup and profile, friendship management, content sharing, and a real-time chat.

I’m quite bad when it comes to finding names, so let’s name it Slax, just because it sounds cool.

Small note: I chose Angular.js for the front-end because it’s a framework I’m already familiar with, and I wanted to focus more on learning about Phoenix. A good exercise would be to use a different front-end framework of your choice to build the web app, if you want to!

Bootstrapping the project

The first thing we need is to install Elixir, as well as PostgreSQL for the database.

$ apt-get update
$ apt-get install postgresql postgresql-contrib build-essential
$ wget http://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb
$ dpkg -i erlang-solutions_1.0_all.deb
$ apt-get update
$ apt-get install -y elixir erlang-dev erlang-parsetools && rm erlang-solutions_1.0_all.deb

Then we need to install Phoenix using Mix, the package manager of Elixir, and use themix phoenix.new command to generate a new Phoenix project inside the api subdirectory. We give the --no-html and --no-brunch options to tell the generator we won’t need any HTML boilerplate or asset build pipeline, since we don’t need it for our API.

$ mix archive.install https://github.com/phoenixframework/archives/raw/master/phoenix_new-1.2.1$ mix phoenix.new --no-html --no-brunch slax api

Now you have to install Phoenix dependencies using the mix deps.get command, and run the development server with mix phoenix.server. Connect to your local server on port 4000 and you should see the Phoenix error page, telling you that no routes are configured for now. We’ll soon deal with that, but before that we have to do some configuration.

I won’t go into the details of configuring PostgreSQL here, so I’ll assume you have a working connection and access that your app can use.

Configuration of the database is done in config/dev.exs file (we’re using the development environment). Since your local configuration could be different from other developers working on the same project, it is good practice to put developer-specific configuration in a separate file, checked out of your version control. You can notice this is already the case for the production environment, as we have a prod.secret.exs file that can contain sensitive production configuration. Let’s do the same with our development database configuration by creating a dev.secret.exs file and importing it in the main dev.exs file.

And voila! Because global configuration is compiled and loaded only when we start Phoenix and not on each request, you’ll need to restart $ mix phoenix.server after updating the configuration.

OK, now we can get to work.

Let’s have some users

The first thing we need is a User model and associated table in our database. We’ll need only basic information for now: email, password, name and a field to store an API token that will be used to authenticate one user requesting the backend.

Phoenix comes packed with the Ecto library that allows us to define, store, manipulate and query models against a database — in a similar way to any other ORM (though the term would be incorrect, since in Elixir we’re dealing with structs and not real objects). Ecto also comes with a variety of Mix commands to help interacting with database and models. The first one we have to run is to create the application database:

$ mix ecto.create

Let’s now create a User model with the following command:

$ mix phoenix.gen.model User users email:unique password first_name last_name api_token

A User model has been generated for us in the web/models/user.ex file.

A model is no more than an Elixir module using the Slax.Web, :model macro — which, if you look inside the web/web.ex file, is expanded so the calling module can use Ecto.Schema, and import three other modules from the Ecto library as helpers to query and manipulate data in our User module.

The generator wrote some boilerplate code for us, including the very definition of the database table structure needed to store the models data. It also generated a schema migration corresponding to the new model, in thepriv/repo/migrations directory. To reflect these changes on the database, we need to call the following command:

$ mix ecto.migrate

We now have our model setup and our database migrated. We’ll need to implement some API endpoints to manage and authenticate users. First thing is to define the routing of these endpoints:

Similarly to the Rails router resources definition, this single line will define all the REST routes to create, update, show and delete our users. We won’t need the new and edit ones, as we don’t need to present a user interface to perform these actions.

Our routes will map to the yet non-existing UsersController. Let’s create it now, along with the create/1 function that will be responsible of inserting a new user in the database.

So, quite straightforward here. The function definition takes advantage of Elixir’s clause matching to directly map the contents of the user params hash to a variable that we then pass to User.create/1 (this function doesn’t exist yet, but we’ll create it in a minute). If everything went OK, we “pipe” into the connection a created HTTP status, along with the associated JSON view. Otherwise (very likely, a fail on the model validation), we send back a HTTP Bad Request status code with some generic error message. The user.json view is itself defined as a function render/2 of the UserView module, returning a map that will be transformed into pure JSON.

We still need to implement User.create/1, where the following happens:

  • We create a changeset from an empty User struct, using the given user parameters
  • We then update the password with its hashed version, using a dedicated hashed_password/1 private function. I’ll leave the implementation of this one for later. We also set the API token of the new user to a randomly generated sequence of bytes.
  • Finally we insert the new user in the database by piping it into Repo.insert/1

For password hashing, we’ll use the comeonin library which provides handy methods to hash and check password using different algorithms. Let’s add it to our project dependencies:

Then run $ mix deps.get to install the new dependency (note that you’ll need the build-essentialsystem package to be installed if not already, so the library can compile). You’ll need to restart $ mix phoenix.server after the installation.

We can now implement the password hashing function, using for example the pbkdf2 algorithm:

Finally, we should have our first working endpoint. You can try it using any HTTP client or your choice:

curl -X POST -H "Content-Type: application/json" -d '{
"user": {
"email": "leo.hetsch@testapp.com",
"first_name": "Léo",
"last_name": "Hetsch",
"password": "notsosecure"
}
}' "http://localhost:4000/api/users"

Authentication

We know need an endpoint so users can login on the web app. When given the correct authentication information (email and password), it will respond with the JSON representation of the authenticated user, along with its unique API token, that the web app will then use in all subsequent requests.

Again, let’s add some convenience functions to the User model…

… and the associated action in the controller.

In a similar way to what we did before, we’ll match against the result of User.authenticate/2 to know if we need to respond with the JSON view of the user, or with an error status.

Then we have our login endpoint working. We’ll now create the endpoints to show a user and update a user. These two ones will be straightforward:

The only thing is, we want to make sure that a user is authenticated on our app to be able to call these endpoints. Moreover, we want some user to be able to only update his own profile. To authenticate a user when calling an endpoint, the web client will provide the API token of a user. There are many ways to achieve this, but for our case we’ll use the Authorization HTTP header to hold the user token. We then match this token against our user database to identify the user making the request.

We’ll use a Phoenix plug for that. Plugs are no more than middlewares (or filters) that can read and modify the original connection request before your controller is called. There are many ways to define plugs, for our case I chose to define it in a Helper module that will be imported in all controllers— so each one can enable the plug on any action that requires the user to be authenticated.

When Authorization header is empty, or when we couldn’t find any user matching the given token, we respond with an Unauthorized HTTP response. Otherwise, we call assign/3 on the current connection object, so it will carry a reference to the authenticated user, that we can retrieve once inside the controller.

User.authenticate_by_token/1 is another convenience function we use to retrieve the user object associated with the given token.

We now need to add the AuthHelper to controllers imported modules, by updating the web/web.ex file. By making this change, all controllers will automatically import the functions defined in our Helper module.

Now we can call the ensure_token/1 plug function in our controller, specifying we only want it to be called for the show/1 and update/1 actions. It will now act as a filter, requiring requests to provide an Authorization header containing a valid user token.

Then we need to make sure one authenticated user can only be able to update his own profile. Instead of making direct changes to the action, we can write another plug, this time specific to this controller, to act as a safeguard. By using a plug, we will be able to reuse it on any future action that would need such protection (for example, we could implement an action that would allow one user to update only his own profile picture).

Good! But we have one more concern: we probably don’t want users to be able to read the secret API token and the email of other users. So far, the only JSON view defined in the Slax.UserView module is including full user information. The solution is to define a second render/2 function in the same module, including only public user data, and called when the given view name is public_user.json.

And… we’re done with the API for now. Let’s move to the front now!

About dealing with CORS

Since your Phoenix server and the one serving your web app will be listening on different ports (and maybe even on different hosts), you’ll likely have to allow cross-origin requests on the backend. We can use the simple CORS plug for that.

Once again, you’ll need to install the new dependency and restart the server with $ mix deps.get && mix phoenix.server

The Front-end part

We’ll build a standalone Angular.js app using the basic router to present two views for now: the home page, where one user will be able to create an account or sign in with his existing credentials, and one view where once logged in, the user will be able to see and edit his profile.

Feel free to use whatever build system or tools you want for your front-end development environment. I’ll go with gulp (yes, I know it’s not hype anymore). I will also use Bootstrap for the app design, for the sake of simplicity, and the http-server NPM module to serve the app.

It will have this simple directory structure:

public/
views/
assets/
index.html
bundle.js
src/
controllers/
services/
app.js

Let’s start by building our index page, our main app module, and define the routes for the two views.

Login and sign-up forms

We need to create the controller for the homepage, and its associated template, where we bind the two fields of the sign-in form to the auth.email and auth.password properties of the controller scope, as well as the registration form fields to the user object. We also bind both the submission of the sign-in and registration forms the scope methods where we’ll be handling requests to the backend API.

To send requests to the backend, we’ll create a service to manage communication with the users endpoints, layering on top of the existing $http angular service.

We can now update our controller to use this service in the methods we bound to UI forms.

We need to save the API token so we can use it in any subsequent request made to the backend. Let’s create a service for that, too. We’ll simply store the token into the browser local storage, and update the $http service so it can send the token header in all requests by default.

Then back to the controller, we can use this new service to store information about the user that we just signed up (or signed in). We should also redirect the user to the profile view once he’s authenticated, using the $location service.

Done? Not entirely. Although user information is now saved into local storage once the user logs in, if he reloads the page at this point, he won’t be able to make authenticated requests to the API, simply because we don’t automatically set the default Authorization header on the $http service when we reload the app. Let’s fix that:

The user profile

The profile view is a really simple one now, since we just need to add the corresponding controller, view and API methods to our web app. We have to implement bothGET /users/:id and PUT /users/:id endpoints, providing a dead-simple UI for the user to read and edit his own information.

One last thing: even if these endpoints already require authentication in our backend, we should make sure that the profile view is not accessible when no user information is stored on the web app — and that we redirect the user to the login form instead, so we have a consistent user experience. We can do that by implementing a middleware in the app.run callback, listening on the route change event:

And we’re done for this first part! In the next one we’ll implement one of the most important aspect of social networks: creating friendship bonds between users. Stay tuned!

Thanks for reading, and if you liked this post, don’t hesitate to recommend or share. Any feedback will be appreciated :) I’m also on Twitter.

P.S: I’m french, so I apologize for the potential English mistakes!

Some links that helped me through the learning and writing of this first post:

http://www.phoenixframework.org/docs

http://nithinbekal.com/posts/phoenix-authentication/

https://github.com/h4cc/awesome-elixir

--

--

Léonard Hetsch

Software engineer based in London / Technical coach @makersacademy / Previously @stuart @dicefm & @oncetheapp / Studied @gobelins_paris / Hungry learner.