Let’s Build |> a Slack Clone with Elixir, Phoenix, and React (part 1 — Project setup)
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!
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.
If you don’t have Elixir or Phoenix installed, start here.
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:
Let’s get started!
Create the Phoenix application
Make a new folder for our repo
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
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
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:
At the bottom of dev.exs we need to import this file so it’s contents override the configuration above it.
Now we can create the database (make sure you are in the api directory when running mix tasks)
The database is created, and we can start Phoenix by running
If you open up http://localhost:4000 you should see the Phoenix welcome page.
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.
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 email@example.com 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.
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.
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.
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.
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.
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.
At this point, our Home page is a simple stateless component.
And our NotFound component is also a stateless component
That is all of our initial redux boilerplate. If you start the react app with npm start, you will see our Home page.
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.