Elixir represents relatively new programming language for wider audience. It was published back in 2011, and is in development ever since. His main trait is that adops functional pardigm because it is built on top of Erlang and runs on BEAM(Erlang VM). Elixir is designed for building fast, scalable and maintainable applications and with Phoenix these applications can be developed in web environment. Phoenix is web framework written in Elixir and it draws a lot of concepts from popular frameworks like Python's Django or Ruby on Rails. If you are familiar with those that is a nice starting point. Documentation Elixir/Phoenix is a great combination, but before starting writing an app, those who are not familiar with all concepts should first read following documentation. - Detail documentation, from Elixir basic types to advanced stuff like Mix and OTP, Elixir by Dave Thomas is recommendation, Programming Elixir - Built-in framework for testing, ExUnit - Phoenix framework documentation with all concepts explained with examples and Phoenix - Docs and API for Elixir's ORM. Ecto Setting up application Elixir ships with Mix which is built-in tool that helps compiling, generating and testing application, getting dependencies etc. We create our application by running mix phx company_api .new This tells mix to create new Phenix app named . After running this instruction mix will create application structure: company_api bash * creating company_api/config/config.exs * creating company_api/config/dev.exs * creating company_api/config/prod.exs * creating company_api/config/prod.secret.exs * creating company_api/config/test.exs * creating company_api/ * creating company_api/ * creating company_api/ * creating company_api/ * creating company_api/ * creating company_api/ * creating company_api/ * creating company_api/ * creating company_api/mix.exs * creating company_api/README.md * creating company_api/test/support/channel_case.ex * creating company_api/test/support/conn_case.ex * creating company_api/test/test_helper.exs * creating company_api/test/company_api_web/views/error_view_test.exs * creating company_api/ * creating company_api/priv/gettext/en/LC_MESSAGES/errors.po * creating company_api/priv/gettext/errors.pot * creating company_api/ * creating company_api/priv/repo/seeds.exs * creating company_api/test/support/data_case.ex * creating company_api/ * creating company_api/ * creating company_api/ * creating company_api/ * creating company_api/ * creating company_api/test/company_api_web/controllers/page_controller_test.exs * creating company_api/test/company_api_web/views/layout_view_test.exs * creating company_api/test/company_api_web/views/page_view_test.exs * creating company_api/.gitignore * creating company_api/assets/brunch-config.js * creating company_api/assets/css/app.css * creating company_api/assets/css/phoenix.css * creating company_api/assets/js/app.js * creating company_api/assets/js/socket.js * creating company_api/assets/package.json * creating company_api/assets/static/robots.txt * creating company_api/assets/static/images/phoenix.png * creating company_api/assets/static/favicon.ico / / . lib company_api application ex / . lib company_api ex / / / . lib company_api_web channels user_socket ex / / / . lib company_api_web views error_helpers ex / / / . lib company_api_web views error_view ex / / . lib company_api_web endpoint ex / / . lib company_api_web router ex / . lib company_api_web ex / / . lib company_api_web gettext ex / / . lib company_api repo ex / / / . lib company_api_web controllers page_controller ex / / / / . . lib company_api_web templates layout app html eex / / / / . . lib company_api_web templates page index html eex / / / . lib company_api_web views layout_view ex / / / . lib company_api_web views page_view ex Install additional dependencies if prompted. Next we need to configure our database. In this example we used PostgreSQL, and generally Phoenix has best integration with this DBMS. Open and and setup username, password and database name. After setting up database, run /config/dev.exs /config/test.exs mix ecto .create which will create development and test databases and after that mix phx. server That should start server (Cowboy) on default port 4000. Check it up in browser, if you see landing page that's it, setup is good. All configurations are placed in file. /config/config.exs Creating API Before coding there are several parts of development that are going to be explained: Writing tests using ExUnit, testing both models and controlers, Writing migrations, Writing models, Writing controllers, Routing, Writing views, Authentication using Guardian and Channels. Note that following parts won't be described for whole application, but you'll get the idea. Testing and writing models While developing we want to write clean code that works, also think about specification and what that code needs to do before implementing it. That's why we're using approach. TDD First in directory create models directory and then create user_test.exs. After that create a module: test/company_api_web/ CompanyApi.DataCase, defmodule CompanyApiWeb.UserTest do use async: true end On second line, we use macro to inject some external code, in this case data_case.exs script that is placed in directory among other scipts and to mark that this test will run asynchronous with other tests. But be careful, if test writes data to database or in some sense changes some data then it should not run asyc. use test/support/ `async: true` Think of what should be tested. In this case let's test creating user with valid and invalid data. Some mock up data can be set via module attributes as constants, for example: %{ , , , } @valid_attributes name: "John" subname: "Doe" email: "doe@gmail.com" job: "engineer" Ofcourse you don't have to use module attributes but it makes code cleaner. Next let's write test. test user = CompanyApiWeb.User.reg_changeset(%User{}, ) assert user.valid? "user with valid attributes" do @valid_attributes end In this test we try to create by calling method and then asserting for true value. changeset reg_changeset/2 If we run test with mix /company_api_web/models/user_test.exs test test test will fail ofcourse. First we dont even have User module, but we dont even have User table in database. Next we need to write a migration. mix ecto create_user .gen .migration generates migration in . There we define function for table creation in sugar elixir syntax which then translates into appropriate SQL query. priv/repo/migrations/ create table( ) add , add , add , add , add , timestamps() def change do :users do :name :varchar :subname :varchar :email :varchar :job :varchar :password :varchar end end Function creates database table from struct returned by function . For detail information about field type, options, and creating indexes read docs. By default surrogate key is generated for every table, with name id and type integer, if not defined otherwise. create/2 table/2 Now we run command mix ecto .migrate which runs migration. Next we need to create model, so create models directory in and create user.ex file. Our model is used to represent data from database tables as it maps that data into Elixir structs. lib/company_api_web/ CompanyApiWeb, schema field , field , field , field , field , (changeset, params \\ %{}) changeset |> cast(params, [ , , , , ]) |> validate_required([ , , , ]) |> validate_format( , ~r/\S+@\S+\.\S+ defmodule CompanyApiWeb.User do use :model "users" do :name :string :subname :string :email :string :password :string :job :string end def reg_changeset do :name :subname :email :job :password :name :subname :email :job :email /) end end On line 2, we use helper defined in which actually imports all necessary modules for creating models. If you open file you'll see that model is actually a function, same as controller, view, channel, router etc. (If there is no model function you can add it yourself). lib/company_api_web/company_api_web.ex Two important methods are schema (table <-> struct mapping) and . Changeset functions are not necessary, but are Elixir's way of creating structs that modify database. We can define one for registration, login etc. All validation and association checking can be done before we even try inserting data into database. changeset/2 For more details check docs. If we now run test again, it should pass. Add as many test cases as you want and try to cover all edge cases. Ecto.Changeset This should wrap creation of simple models. Adding association is going to be mention earlier. Testing and writing controllers Testing controllers is equally important as testing models. We are going to test registration of new user and getting all registered users in a system. Again we create test, this time in with name user_controller_test.exs. With controller testing we're going to use conn_case.exs script. Another important thing about test that wasn't mention while testing models (cause we didn't need it) is setup block. test/company_api_web/controllers/ setup user = %User{} |> User.reg_changeset( ) |> Repo.insert! conn = build_conn() |> put_req_header( , ) %{ conn, user} do @user "accept" "application/json" conn: user: end Setup block is called before invokation of each test case, and in this block we prepare data for tests. We can return data from block in a form of tuple or map. In this block we will insert one user into database and create connection struct which is mockup of connection. Again constants can be used to setup data. %{ , , , } %{ , , , } %{ , , , } @valid_data name: "Jim" subname: "Doe" email: "doe@gmail.com" job: "CEO" @user name: "John" subname: "Doe" email: "doe@gmail.com" job: "engineer" @user_jane name: "Jane" subname: "Doe" email: "jane@gmail.com" job: "architect" Now let's write test that sends request for creating new user. Server should process request, generate password, create new user, send email with generated password and return user as a json. That sounds like a lot, but we'll go slow. Pay attention that you should try to cover all 'paths' and edge cases. First let's test with valid data, and then with invalid data. describe test , %{ conn} response = post(conn, user_path(conn, ), ) |> json_response( ) assert Repo.get_by(User, ) assert_delivered_email Email.create_mail(response[ ], response[ ]) test , %{ conn} response = post(conn, user_path(conn, ), %{}) |> json_response( ) assert response[ ] != %{} "tries to create and render" do "user with valid data" conn: do :create user: @valid_data 201 name: "Jim" "password" "email" end "user with invalid data" conn: do :create user: 422 "errors" end end Each test sends post request to certain path and then we check json response and assert value. Running this test with mix /company_api_web/controller/user_controller_test.exs test test will result in errors. We don't have function which means that route isn't defined. Open . We'll add scope "/api" which will go through :api pipeline. We can define routes as resources, individually or as nested routes. Define new resource like this: user_path/3 lib/company_api_web/router.ex resources "/users", UserController, : [: , : ] only index create With this, Phoenix creates routes which are mapped to index and create functions and handled by UserController. If you open console and type mix phx .routes you can see list of routes and there are routes, one with verb GET and one with verb POST. Now if we run test again, this time we'll get another error, create function missing. Reason for this is that we don't have UserController. Add user_controller.ex in . user_path lib/company_api_web/controllers Now define new module: CompanyApiWeb, defmodule CompanyApiWeb.UserController do use :controller end Next we need to create that function. Create function must accept conn struct (and also return it) and params. Params is struct which carries all data supplied by browser. We can use one powerful feature of Elixir, pattern matching, to match just the data we need with our variables. create/2 (conn, %{ => user_data}) params = Map.put(user_data, , User.generate_password()) Repo.insert(User.reg_changeset(%User{}, params)) { , user} -> conn |> put_status( ) |> render( , user) { , user} -> conn |> put_status( ) |> render( , user) def create "user" do "password" case do :ok :created "create.json" user: :error :unprocessable_entity "error.json" user: end end In our tests we send through post method params , and that data is going to be matched with . In User model define function, so we can generate random passwords for every new user. user: @valid_data user_data generate_password .strong_rand_bytes( ) |> Base.encode64 |> binary_part( , ) def generate_password do :crypto @pass_length 0 @pass_length end Set the length of a password as you wish. Since the user_data is a map we are going to put new generated password inside that map with key "password". Although Elixir has try/rescue blocks they are rarely used. Usually combination of case and pattern matching is used for error handling. Function insert(note that we won't use insert! function cause it raises exception) returns one of two tuples: { , Ecto.Schema.t} { , Ecto.Changeset.t} :ok :error Based on returned tuple we send appropriate response. Since we're making JSON API, we should return data in json format. All data returned from controller is handled by appropriate view. If we run tests again, we are going to get another error. Last thing we need to do is to add view file. Create user_view.ex file in and inside define new module and render methods. lib/company_api_web/views/ CompanyApiWeb, ( , %{ user}) render_one(user, CompanyApiWeb.UserView, ) ( , %{ user}) %{ translate_errors(user)} ( , %{ user}) %{ user.id, user.name, user.subname, user.password, user.email, user.job} (user) Ecto.Changeset.traverse_errors(user, &translate_error/ ) defmodule CompanyApiWeb.UserView do use :view def render "create.json" user: do "user.json" end def render "error.json" user: do errors: end def render "user.json" user: do id: name: subname: password: email: job: end defp translate_errors do 1 end end First render method is being called from controller, and in that method we call to which we pass key, view module, and template name, so we can pattern match method. Now we return data which is going to be encoded into json. We didn't have to call method, we could return json right away, but this is more convinient. render_one/3 render_one/3 Second render method renders errors provided by changeset struct in form of json. Built-in method extracts error strings from changeset.errors struct. Ecto.Changeset.traverse_errors/2 If we remove that one line which asserts that email has been sent, our tests will pass. This rounds up how we test and write controllers. Now you can test and write index method and add more test cases that covers more code. Email sending example There are several email libraries in Elixir, but in this project we decided to use . After initial setup, its usage is fairly easy. Open file and under deps function add following line: Bamboo mix.exs { , } :bamboo "~> 0.8" and then run following command: mix deps. get which will download dependency. After that add bamboo as extra_application in function. application In global config file add configuration for Bamboo: config , CompanyApi.Mailer, Bamboo.LocalAdapter :company_api adapter: Here we're using Bamboo.LocalAdapter but there are other adapters also. Now, create module CompanyApi.Mailer and following line: Bamboo.Mailer, use otp_app: :company_api Before using mailer we should define email struct. Add into models directory Email.ex file(Note that you should first write test then add file but we'll skip that now). Bamboo.Email (password, email) new_email() |> to(email) |> from( ) |> subject( ) |> html_body( ) |> text_body( ) defmodule CompanyApiWeb.Email do import def create_mail do "company@gmail.com" "Generated password" "<h1>Welcome to Chat</h1>" "Welcome. This is your generated password . You can change it anytime." #{password} end end Function returns email struct which we will use for sending. Before running tests we need to add configuration in , same as before, only difference is in adapter which is now, Bamboo.TestAdapter. Adding this allows as to use function such as in our tests. Now in UserController after successfull insert add next line: create_mail/2 /config/test.exs `use Bamboo.Test` `assert_delivered_email` Email.create_mail(user.password, user.email) |> CompanyApi.Mailer.deliver_later This is going to create email struct and send it in the background. For asynchronous sending there is module. If you wish to see sent mails, in add following: Task router.exs if Mix.env == forward , Bamboo.EmailPreviewPlug :dev do "/send_mails" end Now we can see delivered mails at . localhost:4000/sent_mails Authentication via Guardian So far we've have shown how to write tests, migrations, models, controllers, views and routing. One more important thing is authenticating user. Library of choice here was . It uses JWT (Json Web Token) as a method of authentication and we can authenticate Phoenix services and also channels. Great stuff. Guardian First add dependency in mix.exs file and run `{:guardian, "~> 1.0-beta"}` mix deps. get In Guardian docs there is detail explanation how to setup basic configuration, but we're going to go step by step here. Open and add following: /config/config.exs config , CompanyApi.Guardian, , , :company_api issuer: "CompanyApi" secret_key: "QDG1lCBdCdjwF49UniOpbxgUINhdyvQDcFQUQam+65O4f9DgWRe09BYMEEDU1i9X" verify_issuer: true Note that CompanyApi.Guardian is going to be module that we're going to create. You don't have to call it Guardian, maybe it's little redundant. Anyway, next thing is secret_key that has to be generated. This is example of one secret key, and it can be generated by running mix guardian .gen .secret Create CompanyApi.Guardian module in . lib/company_api/ Guardian, CompanyApi.Repo CompanyApiWeb.User (user = %User{}, _claims) { , } ( ) { , } (claims) id = Enum.at(String.split(claims[ ], ), ) Repo.get(User, String.to_integer(id)) -> { , } user -> { , user} defmodule CompanyApi.Guardian do use otp_app: :company_api alias alias def subject_for_token do :ok "User: " #{user.id} end def subject_for_token _ do :error "Unknown type" end def resource_from_claims do "sub" ":" 1 case do nil :error "Unknown type" :ok end end end This module is going to be used when token is being created. We've put user id as a subject for token, in that way we can always get user from database. This may be the most convenient way, but it's not the only way. Next thing we're going to do is to set up guardian pipeline. Using Guardian with plugs is easy. Open and add new pipeline: lib/company_api_web/router.ex pipeline plug Guardian.Plug.Pipeline, CompanyApi.Guardian, CompanyApi.GuardianErrorHandler plug Guardian.Plug.VerifyHeader, plug Guardian.Plug.EnsureAuthenticated plug Guardian.Plug.LoadResource, :auth do module: error_handler: realm: "Bearer" ensure: true end This pipeline can be defined directly in router.ex file, or can be defined in separate module, but still needs to be referenced here. When user tries to call some service his request is going to pass through pipeline. Note that this pipeline is specifically for . JSON API Okey, first we define that we're using plug pipeline and reference implementation module and module that is going to handle auth errors (we're going to create it). Next plug verifies that token is in request header,plug EnsureAuthenticated ensures that valid JWT token was provided and last plug loads resource by calling function specified in CompanyApi.Guardian module. resource_from_claims/1 Since we're missing auth_error handling module add it in . lib/company_api/ (conn, {_type, reason}, _opts) conn |> Plug.Conn.put_resp_content_type( ) |> Plug.Conn.send_resp( , Poison.encode!(%{ to_string(reason)})) defmodule CompanyApi.GuardianErrorHandler do def auth_error do "application/json" 401 message: end end is Elixir JSON library. Just add dependency in mix.exs. Poison `{:poison, "~> 3.1"}` We've set up everything for Guardian and now it's time to write SessionController and handle login and logout. First we have to write tests. Create session_controller_test.exs. We're going to test user login and make it pass. We've already wrote tests for UserController so you know how to set up this one also. test , %{ conn, user} user_credentials = %{ user.email, user.password} response = post(conn, session_path(conn, ), user_credentials) |> json_response( ) expected = %{ => user.id, => user.name, => user.subname, => user.password, => user.email, => user.job } assert response[ ][ ] == expected refute response[ ][ ] == refute response[ ][ ] == "login as user" conn: user: do email: password: :create creds: 200 "id" "name" "subname" "password" "email" "job" "data" "user" "data" "token" nil "data" "expire" nil end We're going to try to login with valid credentials and we expect to get as a response user, token and expire value. If we run this test, it is going to fail. We don't have route. Open router.ex file and in our "/api" scope add new route: session_path post , SessionController, "/login" :create We've put this route in "/api" scope because our user doesn't need to get authenticated while he's trying to login. If we run test again, this time it is going to fail because there is no create function. Let's add SessionController now and write login function. (conn, %{ => params}) new_params = Map.new(params, {k, v} -> {String.to_atom(k), v} ) User.check_registration(new_params) { , user} -> new_conn = Guardian.Plug.sign_in(conn, CompanyApi.Guardian, user) token = Guardian.Plug.current_token(new_conn) claims = Guardian.Plug.current_claims(new_conn) expire = Map.get(claims, ) new_conn |> put_resp_header( , ) |> put_status( ) |> render( , user, token, expire) { , reason} -> conn |> put_status( ) |> render( , reason) def create "creds" do fn end case do :ok "exp" "authorization" "Bearer " #{token} :ok "login.json" user: token: exp: :error 401 "error.json" message: end end First line creates new map as a result with keys as atoms. Function checks if user with given credentials exist in database. If user exists we sign him in, create new token and expire date. After that we set response header, status and render user. For rendering we need to create session_view.ex in . check_registration/1 lib/company_api_web/views/ CompanyApiWeb, ( , %{ user, token, expire}) %{ %{ render_one(user, CompanyApiWeb.UserView, ), token, expire } } ( , %{ reason}) %{ reason} defmodule CompanyApiWeb.SessionView do use :view def render "login.json" user: token: exp: do data: user: "user.json" token: expire: end def render "error.json" message: do data: end end Now test should pass. Of course more tests should be added, but that's up to you. Logout is fairly simple, `Guardian.revoke(CompanyApi.Guardian, token)` deletes token from header and that is all we need to do. With APIs there is no really logout, but this will work. Before adding new route for logging out, we need to define "new scope". Actually this is going to be the same "/api" scope again, but it will go through two pipelines now: . `pipe_through [:api, :auth]` Why are we doing this? Every new route that needs to be authenticated will be places inside of this new scope. Also if we want to logout, we need to be authenticated first. With this we've covered authenticating with Guardian. Later socket authentication is going to be mentioned, and it's even easier. Associations example Since this is a chat app, message history has to be saved somehow. We're going to add two more entities that will represent conversation between two users and user's messages. This will be a good opportunity to show examples of associations in Ecto. First entity that we're going to add is conversation entity. Conversations will belong to both user involved in chat, and user is going to have many conversations. Also conversations will have many messages which is second entity. Messages will belong to user and certain conversation. In this case user represents a person who sends messages. Other attributes of message are date and content. In a few sentences we've described our data model. Each of these data models will have their own tests, controllers and views, but since we've explained all of these stuff already, in this part we're going to focus on associations between these entities. Note that the only thing you need to do is to write function for creation of conversation, creation of messages and getting message history. Conversations Firstly, lets add conversations migration. Run command mix ecto create_conversations .gen .migration Now we need to create table conversations with correct columns: create table( ) add , references( , ) add , references( , ) timestamps() create unique_index( , [ , ], ) def change do :conversations do :sender_id :users null: false :recipient_id :users null: false end :conversations :sender_id :recipient_id name: :sender end As you can see, we're adding foreign keys sender_id and recipient_id and we are referencing users table. This will represent our two users in conversation. Both keys can't be null. Last thing we want to do is to create unique_index on both columns which correspond to unique constraint. We're doing this because we don't want duplicate conversations with the same ids. Lets create model now: CompanyApiWeb, CompanyApiWeb.{User, Message} schema field , belongs_to , User, belongs_to , User, has_many , Message timestamps() (changeset, params \\ %{}) changeset |> cast(params, [ , , ]) |> validate_required([ , ]) |> unique_constraint( , ) |> foreign_key_constraint( ) |> foreign_key_constraint( ) defmodule CompanyApiWeb.Conversation do use :model alias "conversations" do :status :string :sender foreign_key: :sender_id :recipient foreign_key: :recipient_id :messages end def changeset do :sender_id :recipient_id :status :sender_id :recipient_id :sender_id name: :sender :sender_id :recipient_id end Observe new functions. Functions and represents associations. Usually function is defined with a name and a referenced module, but this time since we're have two references to the same module we have to add a correspond foreign key column. belongs_to/3 has_many/3 belongs_to/3 Same story goes for association, association name and module(we're going to create Message module soon). Now the change-set. We've added two functions, one for each foreign key and function (because of composite unique columns, only one has to specified). All of these constraints are checked on database level. has_many/3 foreign_key_contraint/3 unique_constraint/3 Mesages Second entity is messages. Run mix ecto create_messages .gen .migration Add create and table functions: create table( ) add , references( , ) add , references( , ) add , add , timestamps() create index( , [ ]) create index( , [ ]) def change do :messages do :sender_id :users null: false :conversation_id :conversations null: false :content :varchar :date :naive_datetime end :messages :sender_id :messages :conversation_id end Same story as before. Two foreign keys, messages belong to user (sender) and conversation. This time we don't need unique contraint, so we just index mentioned fields. One look at the model: CompanyApiWeb, CompanyApiWeb.{User, Conversation} schema field , field , belongs_to , Conversation belongs_to , User, timestamps() (changeset, params \\ %{}) changeset |> cast(params, [ , , , ]) |> validate_required([ , , , ]) |> foreign_key_constraint( ) |> foreign_key_constraint( ) defmodule CompanyApiWeb.Message do use :model alias "messages" do :content :string :date :naive_datetime :conversation :sender foreign_key: :sender_id end def changeset do :sender_id :conversation_id :content :date :sender_id :conversation_id :content :date :sender_id :conversation_id end Last thing we need to do is to add associations in User module: has_many , Conversation, has_many , Conversation, has_many , Message, :sender_conversations foreign_key: :sender_id :recipient_conversations foreign_key: :recipient_id :messages foreign_key: :sender_id With this we've set up our data model and you've seen brief example of Ecto associations. For many_to_many association read . docs Channels Essentially channels are Phoenix abstraction build on top of sockets. It is possible to have multiple channels over one socket connection. For detail explanations and understanding how channels recommendation is to read . official documentation Our goal is to send message via Websocket protocol, and we're going to start with writing channel tests. on channel testing is really helpful. Documentation Create chat_room_test.exs in directory. In setup block insert one user into database, create connection and sign in user. We're going to test message sending. /test/company_api_web/channels/ CompanyApiWeb.ChannelCase CompanyApi.Guardian, Guard CompanyApiWeb.{ChatRoom, UserSocket, Conversation} %{ , , , } %{ , , , } setup user = %User{} |> User.reg_changeset( ) |> Repo.insert! { , token, _claims} = Guard.encode_and_sign(user) { , soc} = connect(UserSocket, %{ => token}) { , , socket} = subscribe_and_join(soc, ChatRoom, ) { , socket, user} test , %{ socket, u} user = %User{} |> User.reg_changeset( ) |> Repo.insert! conv = %Conversation{} |> Conversation.changeset(%{ u.id, user.id}) |> Repo.insert! { , token, _claims} = Guard.encode_and_sign(user) { , soc} = connect(UserSocket, %{ => token}) { , , socketz} = subscribe_and_join(soc, ChatRoom, ) push socket, , %{ user.id, conv.id, } assert_push , %{ message} assert message.content == refute Repo.get!(CompanyApiWeb.Message, message.id) == push socketz, , %{ u.id, conv.id, } assert_push , %{ reply} assert reply.content == refute Repo.get!(CompanyApiWeb.Message, reply.id) == defmodule CompanyApiWeb.ChatRoomTest do use alias as: alias @first_user_data name: "John" subname: "Doe" email: "doe@gmail.com" job: "engineer" @second_user_data name: "Jane" subname: "Doe" email: "jane@gmail.com" job: "architect" do @first_user_data :ok :ok "token" :ok _ "room:chat" :ok socket: user: end "checks messaging" socket: user: do @second_user_data sender_id: recipient_id: :ok :ok "token" :ok _ "room:chat" "send_msg" user: conv: message: "Hi! This is message" "receive_msg" message: "Hi! This is message" nil "send_msg" user: conv: message: "This is a reply" "receive_msg" message: "This is a reply" nil end end Well, this seem like a lot, but lets go step by step. In setup block we connect to socket with generated token, and then function joins user to listed topic. After that in test, those steps are repeated for second user and then conversation is created. Function allows us to send messages directly through socket while or asserts for pushed or broadcasted messages. Running test is going to result in errors. subscribe_and_join/3 push/3 assert_push assert_broadcast Open and define new channel lib/company_api_web/channels/user_socket.ex , CompanyApiWeb.ChatRoom channel "room:*" While here modify and functions. We want to make that only authenticated users can connect to socket. connect/2 id/1 (%{ => token}, socket) Guardian.Phoenix.Socket.authenticate(socket, CompanyApi.Guardian, token) { , socket} -> { , socket} { , } -> (_params, _socket), (socket) user = Guardian.Phoenix.Socket.current_resource(socket) def connect "token" do case do :ok :ok :error _ :error end end def connect do: :error def id do "user_socket: " #{user.id} end Line provides authentication. Function returns socket id, and we set it as a user id. `Guardian.Phoenix.Socket.authenticate(socket, CompanyApi.Guardian, token)` id/1 Now lets create new channel. In the same directory create channel_room.ex file, but for now leave it be. Since we are making private chat we need to know socket we are sending messages to. There are some ways of achieving that. Decision here was to store opened socket connections in a map . `{user_id: socket}` Elixir provides two abstractions for storing state, and . For understanding concepts of GenServer or Agent documentation has to be read. GenServers Agent Open and create channel_sessions.ex, this will be our GenServer for storing sockets. lib/company_api/ GenServer (init_state) GenServer.start_link(__MODULE_ , init_state, __MODULE_ ) (user_id, socket) GenServer.call(__MODULE_ , { , user_id, socket}) (user_id) GenServer.call(__MODULE_ , { , user_id}) (user_id) GenServer.call(__MODULE_ , { , user_id}) () GenServer.call(__MODULE_ , ) ({ , user_id, socket}, _from, socket_map) Map.has_key?(socket_map, user_id) -> { , socket_map, socket_map} -> new_state = Map.put(socket_map, user_id, socket) { , new_state, new_state} ({ , user_id}, _from, socket_map) new_state = Map.delete(socket_map, user_id) { , new_state, new_state} ({ , user_id}, _from, socket_map) socket = Map.get(socket_map, user_id) { , socket, socket_map} ( , _from, state) { , %{}, %{}} defmodule CompanyApi.ChannelSessions do use #Client side def start_link do _ name: _ end def save_socket do _ :save_socket end def delete_socket do _ :delete_socket end def get_socket do _ :get_socket end def clear do _ :clear end #Server callbacks def handle_call :save_socket do case do true :reply false :reply end end def handle_call :delete_socket do :reply end def handle_call :get_socket do :reply end def handle_call :clear do :reply end end GenServer abstracts common client-server interaction. Client side calls server-side callbacks. These callbacks conduct operations over map. This module should start when application starts, so we'll add it in S . This is one of most beautiful things in Elixir. upervision tree Open application.ex file in the same directory and add this line in children list. This will start ChannelSessions at the start of the application with initial state . Now we can write ChatRoom channel. Every channel has to implement two callbacks and . `worker(CompanyApi.ChannelSessions, [%{}])` `%{}` join/3 handle_in/3 CompanyApiWeb, CompanyApi.{ChannelSessions, ChannelUsers} CompanyApiWeb.Message ( , _payload, socket) user = Guardian.Phoenix.Socket.current_resource(socket) send( (), { , user}) { , socket} ( , %{ => id, => conv_id, => content}, socket) ChannelSessions.get_socket id -> { , socket} socketz -> user = Guardian.Phoenix.Socket.current_resource(socket) Message.create_message(user.id, conv_id, content) -> { , socket} message -> push socketz, , %{ message} { , socket} ({ , user}, socket) ChannelSessions.save_socket(user.id, socket) { , socket} (_msg, socket) user = Guardian.Phoenix.Socket.current_resource(socket) ChannelSessions.delete_socket user.id defmodule CompanyApiWeb.ChatRoom do use :channel alias alias def join "room:chat" do self :after_join :ok end def handle_in "send_msg" "user" "conv" "message" do case do nil :error case do nil :noreply "receive_msg" message: :noreply end end end def handle_info :after_join do :noreply end def terminate do end end Since we need to save socket, it can be only done after socket is created which is at the end of callback. For that reason we send message to our self which is going to call callback method . There we add socket into the map. Callback creates a message and sends it to appropriate user. Function removes socket from map. join/3 handle_info/2 handle_in/3 teminate/2 With this being set, chat app API has been finished. This tutorial covers all listed parts from earlier with some advanced stuff from like GenServer. It aims to show workflow while developing one Elixir application, and for complete understanding requires documentation reading. After all, there are all information. OTP Recommended place for all Elixir enthusiasts, . Elixir Forum Previously published at https://kolosek.com/elixir-basic-api-guide/