Let’s Build |> a Slack Clone with Elixir, Phoenix, and React (part 1 — Project setup)

Live Demo — GitHub Repo
Part 1 — Part 2 — Part 3 — Part 4 — Part 5 — Part 6 — Part 7

Background: I have been a Ruby developer for a few years, and I recently began learning Elixir. I am quickly falling in love with Elixir and the Phoenix framework, and am excited about the possibilities presented by these technologies. Much like what Rails did for Ruby, Phoenix is bringing Elixir into popularity because it enables developers to build fast, stable applications very efficiently, and makes it easy to work with real time data.

At the time of writing, I have ~1 week experience with Phoenix. Similar to Rubber Ducky Debugging, I am writing this blog post to force myself to think differently about the problems I am solving and therefore gain a better understanding of the language and framework. If you see any errors in the code or have ideas for improvements, please drop me a message below or submit a pull request!

If you are totally new to Elixir, I recommend Programming Elixir, and Programming Phoenix, as they will describe the fundamentals much better that I can.

What we’re building

As a tribute to my favorite chat application Slack, we will be building a clone called Sling. I will be limiting the number of features for the brevity of this post, but we’ll build enough to learn about the fundamentals of Phoenix.

Slack has a concept of teams, and each team can have any number of channels. Members of the team can join channels, and the channels are where chats take place. To keep a simple, flat structure, we will not be creating teams, instead we will have rooms which any registered user can join, and chats will take place in the room instead of child channels.

We will also be implementing the cool new Phoenix Presence module so we can see a list of all users currently present in each chat room.

I want to be as transparent as possible about how I approach building applications, so I will attempt to break up the work into small, incremental steps and link to the full git diffs so you can see how the code evolves.

The linked git diffs may be outdated, check here for the current code.

Tech Stack

Backend

Frontend

Since most of my experience building web applications is with Ruby, I will be writing in the context of someone who is familiar with Rails. I will also assume some familiarity with JavaScript & ES6. Instead of diving deep into a React tutorial, I will try my best to explain the logic in piecing together the React components but will not be discussing the framework in depth.

If you don’t have Elixir or Phoenix installed, start here.

Project Structure

For a real application, it might make sense to create two completely separate code repositories for the Phoenix API and the React app, but for the sake of this blog post I will keep them in the same repo. Therefore the project will look like this:

sling/
|--- api/
(phoenix app)
|--- web/
(react app)

Let’s get started!

Create the Phoenix application

Make a new folder for our repo

mkdir sling
cd sling

Generate a new Phoenix app (fetch dependencies when requested). We will be using Phoenix strictly as a JSON API. Therefore we will skip installing Phoenix’s default asset manager Brunch with the--no-brunch option, and skip creating the html templates and browser routes with--no-html.

mix phoenix.new sling --no-html --no-brunch
mv sling api

Create the React application

I will be scaffolding the React app with create-react-app. This awesome tool will allow us to setup our frontend without having to deal with any Webpack configuration.

Install the create-react-app cli if needed

npm i -g create-react-app

Create the React app

create-react-app sling
mv sling web

Awesome. Now we have two barebones applications scaffolded for us.

This is where I will be making my initial commit.

Phoenix Project Setup

We have to create our database to get the Phoenix app up and running. The development database config is located in sling/api/config/dev.exs. The default postgres user and password values are both “postgres”, which is not what I have setup on my computer. For security and to make it easy for multiple developers to work on this project, let’s override these values by creating a new file dev.secret.exs. This file will also need to be added to your .gitignore so it does not get committed. The new file will look something like this:

sling/api/config/dev.secret.exs

At the bottom of dev.exs we need to import this file so it’s contents override the configuration above it.

sling/api/config/dev.exs

Now we can create the database (make sure you are in the api directory when running mix tasks)

mix ecto.create

The database is created, and we can start Phoenix by running

mix phoenix.server

If you open up http://localhost:4000 you should see the Phoenix welcome page.

Git commit

We know Phoenix is working, so let’s switch gears and get the React app up and running.

React Project Setup

Our create-react-app scaffold gave us a working application. So if you run npm start, you will see the site running at http://localhost:3000. But I want to scrap that boilerplate and set up our own with redux and react-router. I will start by deleting every file in web/src.

Instead of managing our dependencies with npm, I will be using the new yarn package manager. Check out the installation guide to get started with yarn.

I know there are a handful of packages that we will be using in this project, so let’s install all of them at once. (Make sure you are in the web directory when running our npm/yarn tasks).

yarn add aphrodite lodash md5 moment phoenix react-redux react-router@4.0.0-alpha.5 redux redux-form redux-thunk

You’ll notice I’m using the v4-alpha version of react-router, it’s a big change from react-router v2, but I wanted to take this chance learn the new router api. I expect some big updates to be released soon, so I will be updating this blog post when that time comes.

I will also be using flow & eslint with Airbnb’s styleguide. You can install these dev dependencies with yarn.

yarn add babel-eslint eslint eslint-config-airbnb eslint-plugin-flowtype eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react --dev

Linting rules can be a personal preference, but for reference, here is the .eslintrc I will be using. The react/no-unused-prop-types rule is disabled because of some conflicts with flowtype, so hopefully I will be able to enable that in the future.

sling/web/.eslintrc
Quick git commit to show installed packages

React/Redux Boilerplate

There are many ways to structure a React project, each offering tradeoffs. For this project, I think it makes sense to have a containers directory which will hold our components connected to the redux store, a components directory for all other components, an actions and reducers folder which will contain different files separated by concerns like a user session, or current room, and a store directory to configure the redux store. Let’s start putting these pieces together.

Create our application entry point at sling/web/src/index.js. This file imports the redux store configuration we will create in a moment, imports our main App container, and mounts it to <div id=”root” /> in the index.html file.

sling/web/src/index.js

Create the store at sling/web/src/store/index.js. This file is importing the redux reducers we will create in a moment, and applying the redux-thunk middleware which will help us handle asynchronous actions and Promises.

sling/web/src/store/index.js

Create our root reducer at sling/web/src/reducers/index.js. This file will be importing other reducers soon, but for now we are just wiring it up with redux-form. Also, instead of exporting our main combineReducers function, we export a reducer which reacts to a dispatched ‘LOGOUT’ action. When it receives a logout, it returns our appReducer with undefined as the first argument, which will force each reducer to return it’s initial state. In effect, this cleans up all redux state when a user logs out, so no data leaks through to the next user’s session.

sling/web/src/reducers/index.js

Now we need to create our main App container. This file is using the new react-router v4 setup, rendering routes as components instead of using a central route config file. Right now we only have two routes, a Home page and the NotFound component for unmatched routes.

sling/web/src/containers/App/index.js

At this point, our Home page is a simple stateless component.

sling/web/src/containers/Home/index.js

And our NotFound component is also a stateless component

sling/web/src/components/NotFound/index.js

That is all of our initial redux boilerplate. If you start the react app with npm start, you will see our Home page.

Let’s check in with a git commit

At this point, our backend and frontend still don’t know anything about each other. This is a good time for a break, in part 2 we will begin hooking these together and setting up user accounts and authentication.

Read part 2 or view the live demo