Building an Anonymous Messaging App With React and Phoenix

Building an Anonymous Messaging App with React and Phoenix

Introduction

What is an Anonymous Messaging App?

Prerequisites

  • Elixir, Phoenix, and PostgreSQL: Follow the installation guide on the Phoenix documentation: https://hexdocs.pm/phoenix/installation.html
  • Node.js and npm: If not installed, download Node.js from https://nodejs.org/en/download (this also installs npm).
  • React: A widely used JavaScript library renowned for crafting modular and interactive user interface components.
  • Phoenix: An Elixir-based framework acclaimed for its real-time communication capabilities and efficient handling of large volumes of concurrent connections.

App Requirements and Flow

  • Home Page: Introduces the app, displaying the “register” buttons.
  • Registration: Users create accounts with a unique email address and password.
  • User Page: Logged-in users see received messages (or a “no messages yet” notice). A shareable link will be displayed to the users that can be shared with others to send them anonymous messages.
  • Message Sending: Anonymous users visiting a shareable link can type messages in a text field and send the message.

Application Routes

  • / – Home page
  • /users/register – Registration page
  • /users/log_in – Login page
  • /users/messages – User’s messages page
  • /send_messages/:recipient_id – Page for sending messages anonymously to a specific user

Development Steps

mix phx.new anon_messages
cd anon_messages && mix ecto.create
mix phx.server

Understanding the Routes (router.ex)

get "/", PageController, :home

Customizing the Landing Page

image

Setting up the Authentication Flow

Generating an Authentication Layer

mix phx.gen.auth Accounts User users
  • It creates an Accounts context, which will house our authentication logic.
  • It generates a User schema to model our user data.
  • The pluralized users indicates the name of the corresponding database table.
mix deps.get

Running migrations

image
mix ecto.migrate

Exploring the Generated Authentication Views

  • live "/users/register", UserRegistrationLive, :new: This maps the "/users/register" route to the UserRegistrationLive LiveView, rendering the registration form.
  • live "/users/log_in", UserLoginLive, :new: Similarly, this maps the "/users/log_in" route to the UserLoginLive LiveView, rendering the login form.

Customizing Our Views

  • Logged in: We’ll see the user’s email, “Settings,” and “Log out” links.
  • Not logged in: We’ll see the “Register” and “Log in” links.

Linking the “Get started” Button to the Register Page

Creating the User’s Messages Page

Adding React Components to the Phoenix Application

  • mounted is invoked when the LiveView element containing the phx-hook="UserMessages" attribute is added to the DOM (i.e., is rendered on the page). We call the mount function. This function will handle the actual rendering of our <UserMessages/> React component within the designated container and return another function responsible for unmounting the React component. We store this unmount function in this.unmount for later use.
  • destroyedis executed when the LiveView element is removed from the DOM. Here, we use this.unmount, if it exists, to unmount the mounted React component.
  1. It starts by getting a reference to the DOM element where we want to render the React component. It does this using document.getElementById(id).
  2. The createRoot function from react-dom/client is used to create a React root. A React root is essentially a container within the DOM where React will manage the component tree and updates.
  3. root.render(...) is the key step where the provided React component is rendered inside the React root. The <React.StrictMode> wrapper is optional but helps with potential issue detection during development.
  4. The mount function returns another function. This returned function when called, removes the component and its associated updates from the DOM. This is the function we call in the destroyed callback of our UserMessages hook.
cd assets && npm i react react-dom

Creating the User Messages Interface

Fetching the Authenticated User

Passing the User ID to the Client

image

💡 You can style any of these pages as you like. You may notice that the Tailwind classes applied to the <UserMessages /> component are not reflected in the browser. If that’s the case, check the tailwind config (assets/tailwind.config.js) and update it to also search through .jsx files for utility classes. Update the content key as so:

content: ["./js/**/*.{js,jsx}", "../lib/*_web.ex", "../lib/*_web/**/*.*ex"]

What’s left for our app is sending anonymous messages and the user receiving those messages

The remaining features for our app are sending anonymous messages and allowing logged-in users to view them.

Sending Anonymous Messages

Creating the Message Sending Page

In this section, we’ll build the page where anyone can send anonymous messages without having to log in.

We’ll start by adding a new route, /send_messages/:recipient_id in lib/anon_messages_web/router.ex. Since we want this page to be accessible without logging in, we’ll not add this route to the scope or live session that requires the user to be authenticated. We’ll create a new scope in lib/anon_messages_web/router.ex and a live session we’ll call ensure_recipient_exists:

We’ll then implement the on_mount callback that matches ensure_recipient_exists in AnonMessagesWeb.UserAuth (lib/anon_messages_web/user_auth.ex):

Here, we retrieve the user_id from the route parameters. Then we call mount_user_by_id, which retrieves the user with that ID and assigns it to socket.assigns.user_by_id. If a valid user is found, we make it available via socket.assigns.recipient and continue the connection. Otherwise, we halt the connection, display an error message to the user, and redirect them to the homepage. Any route defined within this ensure_recipient_exists live session will automatically trigger this callback.

Next, we’ll create the SendMessagesLive module which we mapped the /send_messages/:recipient_id route to. Create the file at lib/anon_messages_web/live/send_messages_live.ex:

Here, we pass the user’s email and ID as attributes to the SendMessages container which we’ll retrieve in the hook and pass to the <SendMessages /> component we’ll create.

Create the SendMessages hook in assets/js/app.jsx:

Generating the Messaging Context

mix phx.gen.context Messaging Message messages content:string recipient_id:integer
  • Messaging is the name of the context module.
  • Message is the name of the schema module.
  • messages is the pluralized table name in the database.
  • content:string: A field to store the message text.
  • recipient_id:integer: A field to store the user ID of the message’s intended recipient

The generator creates several files, including:

  • lib/anon_messages/messaging/message.ex: This contains the Message schema and associated functions.
  • lib/anon_messages/messaging.ex: This is the Messaging context module containing functions to perform operations on messages.

Finally, we’ll run migrations to inform our database of this new schema. Run:

mix ecto.migrate

Saving Anonymous Messages

Now that we have our context, let’s save the messages when the send form is submitted. In lib/anon_messages_web/live/send_messages_live.ex, we’ll add a handle_event function:

This calls the create_message function from the Messaging context to create the message and provides feedback to the user.

Next, we’ll push this "send_message" event from our form to the LiveView when the form is submitted. We’ll start by passing a handleSendMessage function to the <SendMessages /> component:

This function uses the pushEvent method to push the "send_message" event to the containing LiveView, alongside the message parameters as payload.

Finally, we’ll update the <SendMessages /> component to call this handleSendMessage on form submission:

Displaying User’s Messages

To display a user’s received messages, we begin by creating a function in the Messaging context (lib/anon_messages/messaging.ex) to retrieve messages by recipient_id:

This query fetches a user’s messages and sorts them with the newest messages displayed first.

This fetches messages using our new context function and prepares them in a format suitable for the React component.

This fetches the messages sent to the logged in user using our new context function, and since they are a list of structs, we convert them to a list of maps that React will understand.

Finally, we’ll modify the UserMessages Hook in assets/js/app.jsx:

With these changes, when a user logs in and accesses their messages, they should see the anonymous messages they’ve received!

For the complete codebase of this project, please refer to the GitHub repository: https://github.com/Steph-crown/anon_messages.

Conclusion

In this tutorial, we’ve built a solid foundation for an anonymous messaging application within a Phoenix LiveView project. Here’s a quick recap of what we’ve accomplished:

  • Authentication: Set up a secure user authentication system.
  • Message Routing: Created routes for sending and receiving messages.
  • Context and Schema: Implemented a Messaging context and Message schema to work with message data.
  • User Interface: Built React components to display messages and handle sending new ones.

Here are a few ideas for taking this project further:

  • Advanced Features: Consider options like message deletion, replies, or even image attachments.
  • Read Indicators: Add a way to mark messages as “read”.
  • Custom Styling: Make the message components more visually appealing.

Leave a Reply

Your email address will not be published. Required fields are marked *