I recently worked on building two personal projects that involve real-time communication between users. The first was a direct messaging application and the second was an online chess application. These are my key takeaways and lessons learnt from the process of building these two applications. Let's begin by taking a look at the architecture of these applications individually.
Application Overview
This was the first real-time web application that I was involved in building. I had recently learnt React.js and I was eager for an opportunity to put my theoretical knowledge to test and to prove to myself that I was in fact capable of developing responsive React applications.
I had chosen the MERN stack for this project since since it seemed like the go-to option to use a Javascript backend (Node.js + Express) alongside a React frontend. I had also never used a NoSQL database before, all my database experience was with relational databases so I was keen to give it a shot with nothing at stake but my self-esteem.
I knew that one of the most important things for a good user experience when using any messenger application is that messages should automatically show up on the conversation screen without the user having to refresh their browser window each time. I did a bit of Googling and decided that this would be a good use case for WebSockets.
User Stories
I decided that the Minimum Viable Product for this build should incorporate at least the following user stories:
1) Users should be able to sign-up using a username + password.
2) Users should be able to login using their credentials.
3) After successful login, Users should see a list of their ongoing conversations on the left pane.
3) Users should be able to start a new conversation using the "New Chat" button.
4) On clicking a conversation summary, the entire conversation should load on the right pane.
5) Users should be able to receive notifications when they receive a new message.
6) Users should be able to send messages. (I'll admit, this one was not that hard to think of)
I estimated that it would take about a week to complete if I was able to dedicate at least 2-3 hours per night. Furthermore, I wasn't too concerned about designing a beautiful User Interface (this was my first mistake) so I decided that Bootstrap was good enough for the task at hand. If I could do it all over again I would undoubtedly use TailwindCSS since their utility first approach (as opposed to Bootstrap's component first approach) leads to much more unique looking webpages. It's relatively trivial to instantly know when a web-app uses Bootstrap because all of them are practically identical, which is not the case with web-apps that use utility-based frameworks like Tailwind.
UI Components
The next step were to determine the UI components that I would need. These were the components that I ultimately decided on.
LoginComponent: Form where the User submits their username.
HomeComponent: Parent Component for all these components listed below.
ConversationSummaryList: Displayed after successful login, shows all the conversations the user has had.
ConversationSummary: Displays Conversation Name, Last Message. When clicked loads the respective Conversation component in the right pane
NewConversation: Button that when clicked loads ContactList
ContactList: List that displays all the user's contacts, each contact should be clickable to start a new conversation
Conversation: Left side pane displayed after user clicks on a conversation summary. Shows entire conversation.
UserProfile: Displays User's username, fullname, lastSeen / online status.
MessageList: All the messages in the conversation.
Message: Each individual message, displayed either on the right or left side of the window depending on whether it was sent or recieved.
AddMessage: Chatbox where the user types the message and clicks send / Enter button to send the message.
The design of the database collection would closely overlap with some of the components that are displayed on the UI i.e. Conversation, Message, ConversationSummary and an additional collection called Users (to handle authentication/authorization)
The Node.js backend consisted of APIs handler functions that would be required to perform basic CRUD functionality as well as user sign-up and login.
Once the development of the UI components, API handlers and the end-to-end integration between Client, Server and Database was complete. The only thing remaining was to implement real-time updates using WebSockets.
WebSockets and Azure App Service
Initially I had the WebSocket server code in the same Node.js application as the Webserver (although using different ports) this worked fine on my local environment during development but I soon learned when I tried deploying my app to Azure that I was unable to make calls to my WebSocket server since only port 80 and 443
are exposed by Azure App Service and these were used by the Express webserver. Thus, I ended up having to move all the WebSocket server code into a separate Node.js application so that it could run on the default ports.
Another key takeaway from this experience was that deploying the WebSocket server app to Azure App Service is a bit of an hassle to say the least.
For starters, at the time of this writing the Free Tier for Azure App Service on Linux does not support WebSocket connections. The Free Tier for Windows supports only up to 5 concurrent connections following which any client that tries to connect is greeted with a HTTP 503 error. The next App Service plan (Shared Tier) allows up to 35 concurrent connections.
Furthermore, apps deployed to Azure App service in the Free and Shared tiers have restrictions to the amount of CPU time they are allocated per day (60 and 240 mins respectively). Apps are deallocated from memory if they do not receive incoming requests and are brought back when required which leads to delays (known as cold starts) and an overall terrible user experience.
Node.js applications that are deployed on Azure App Service also sometimes face issues with WebSocket connections due to the way the handoff occurs between IIS and Node.js via iisnode
. The web.config
file plays an essential role in this and one must ensure that it is present and has the following configuration <webSocket enabled="false" />
I also faced similar issues for another application that I was developing that used Socket.IO instead of raw Websockets and ultimately concluded that it's simply not worth the hassle of using Azure App Service for this use case and found that Heroku was a much simpler and better option.
Handling Authentication
I was also handling the user sign-up and authentication logic myself in the server code and storing the credentials in users collection in the database (with hashed passwords). In hindsight I would not recommend this approach since this is not a secure way of going about it. I would absolutely recommend using Firebase for authentication instead due to the relative ease of integration and the improved security that comes with using OAuth / OpenIDConnect based authentication with an identity provider such as Google.
Next.js vs create-react-app
Another key lesson learnt was that it would have been much more efficient to have used Next.js
instead of create-react-app
while developing this application since Next.js allows backend code to be written in the application's /pages/api
directory. The pages that are placed in this directory are run as Serverless functions once the Next.js app is deployed on Vercel thereby eliminating the need for a backend server hosted on a separate cloud instance (in this case Azure App Service). Another point to remember is that Serverless functions are essentially stateless by nature therefore if your backend needs to maintain stateful sessions or anything of that nature then going the Serverless route is not recommended.
Top comments (0)