Building a Chat Application with React, Node/Express Js. [Progress Update]
I should have written about this project earlier but I guess the idiom; ' better late than never ' still works. I have been building projects since some months ago. As an entry level full stack developer without experience, this is a way for me to learn new concepts. This chat application contains the basic features now but I am about to implement websocket. Before that, I want to talk through what I have done so far, and how I have implemented solutions to the challenges I encountered.
FRONT END
I didn't actually build out the whole of the front end first because there were some things I wasn't sure of. I was sure of the sign up and login page though and what user information would be required to effectively use the chat. I started developing the front end with the help of Tailwind Css.
How Tailwind works
I have used the plain old CSS, bootstrap, and, material UI. I think Tailwind does it the fastest. Every single CSS keyword being shortened and passed directly to classNames without the need of creating classes and styling separately. Right there in your markup, you can just define how you want the styling to go. Although, the downside being that you would have to restyle every page. This is my second project with Tailwind and I am loving it. In my next project, I might use something different
I think this is better than React Toastify for me -Highly subjective. I used React Toastify in my previous project and it was actually okay. It pops out from the side and does it's thing. I think this one is more clean although can be overwhelming for the user. It just feels good trying something new I guess.
- The Sign Up and Login
All that is required for the user to Sign up is the Username, Email and Password. I have come to understand that the end user is lazy and may back out of your website if you have a lot of unnecessary fields in your sign up. I am still yet to build something that would not necessarily have you sign up but I think my next project would be that, I have gotten just the right idea.
- Multi Avatar API
Link to the API
I just decided to go with Anonymity. May decide otherwise later. I am calling 6 random Avatars from this API for you to choose from. Actually, this API allows a maximum of 10 calls per min so refreshing the page immediately will cause an error. If there is a better suggestion , I am open to it.
- The Homepage
Honestly, this is still undergoing revamp. Contains the search bar and user messages. It still contains the users but I'm thinking of reducing the information on the homepage. I'd instead create a message button that will take you a page containing the list of registered users.
- Your Profile
I kind of changed the concept here a bit. Each input sends an API post request to the server to update the specific field.
- Chat Page
I still have a problem with this page. It doesn't fix to the bottom of the page when the chat opens. if any one could help with that. I used React Emoji Picker to handle Emojis. It is a little bit slow but does the job, if there is a better option, I'm also open to it.
const addEmoji = ( emoji)=> {
let msg = message
msg+=emoji.emoji
setMessage(msg)
}
<emojiPicker && <Picker onEmojiClick={addEmoji}/>
- Redux Toolkit
export const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
reset: (state)=> {
state.isError= false
state.isSuccess= false
state.isLoading= false
state.message= ''
}
},
extraReducers: (builder)=> {
builder.addCase(register.pending, (state)=> {
state.isLoading=true
})
.addCase(register.fulfilled, (state, action)=> {
state.isLoading = false
state.isSuccess = true
state.user = action.payload
})
.addCase(register.rejected,(state, action)=> {
state.isLoading = false
state.isError = true
state.message = action.payload
})
.addCase(login.pending, (state)=> {
state.isLoading=true
})
.addCase(login.fulfilled, (state, action)=> {
state.isLoading = false
state.isSuccess = true
state.user = action.payload
})
.addCase(login.rejected,(state, action)=> {
state.isLoading = false
state.isError = true
state.message = action.payload
})
.addCase(logout.pending, (state)=> {
state.isLoading=true
})
.addCase(logout.fulfilled, (state, action)=> {
state.isLoading = false
state.isSuccess = true
state.user = null
})
.addCase(logout.rejected,(state, action)=> {
state.isLoading = false
state.isError = true
state.message = action.payload
})
.addCase(setAvatar.pending, (state)=> {
state.isLoading= true
})
.addCase(setAvatar.fulfilled, (state, action)=> {
state.isLoading= false
state.isSuccess = true
state.message = action.payload
})
.addCase(setAvatar.rejected, (state, action)=> {
state.isLoading = false
state.isError = true
state.message= action.payload
})
.addCase(getAvatars.pending, (state)=> {
state.isLoading=true
})
.addCase(getAvatars.fulfilled, (state, action)=> {
state.isLoading = false
state.avatars = action.payload
})
.addCase(getAvatars.rejected, (state, action)=> {
state.isLoading = false
state.isError = true
state.message= action.payload
})
}
})
I'm getting more comfortable with Redux Toolkit. I have three reducers (Auth, Users, Messages). The first one manages the states related to authentication (Login, Logout, Userprofile), The second manages the states related to fetching registered a single/all users. The third one manages the states related to user messages and chats.
THE BACKEND
This is implemented with MongoDb and Node/Express Js. I have just two Models; One for the User and one for Messages. Those two have been doing the job so far. I was tempted to create another Model called 'Chat' due to an issue I faced but I eventually fixed it. If I am going to implement a group chat though, I will have to do that.
let token
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')){
try {
// Get token
token = req.headers.authorization.split(' ')[1]
const decoded = jwt.verify(token, `${process.env.JWT_KEY}`)
//Get user from token
req.user = await userModel.findById(decoded.id).select('-password')
next()
}
catch (err) {
return res.status(401).send({message: "Not Authorized"})
}
}
The Login and Sign up routes are protected by Jwt Authentication Middleware. It generates session tokens for successful logged in users and each route in the chat requires the presence of such token in the Header. I am currently exploring other means of protection.
- The Logic to the Chat
message: {type: String, required: true},
users: {type: Array, required:true},
sender: {type: Schema.Types.ObjectId, ref: "Users"},
to: {type: Schema.Types.ObjectId, ref: "Users"},
Each message contains three important fields, the users involved in the chat(This is used for chat grouping), and both the sender, and receiver. When you open a chat, of a user Id, it gets all the messages from the currentUserId to the SelectedUserId like so;
dispatch(getChat({from:user._id, to: selectedUser._id}))
The 'From' messages are styled to the left while the sender messages are styled to the right. That's all there is to the messages.
Chat Grouping
This is where I nearly twisted my brain nerves. I think I spent three days thinking ways to group a chat. I kept experimenting with different methods and I spent time reading a lot up on MongoDb Group aggregation and Query population and it did the trick for me.
const {currentUserId} = req.body
if (currentUserId) {
let currentUserMessages= await messageModel.aggregate([
//Check for any chat with the current User Id
{$match: {users: currentUserId}},
//Select the fields we want to retain
{
"$project": {
to: 1,
sender:1,
message:1,
createdAt: 1,
users: 1
}
},
//Destructure the users array and sort it so a/b or b/a returns a single array
{
$unwind: "$users"
},
{
$sort: {"users": 1}
},
{
$group: {
_id: "$_id",
"users": {
$push: "$users"
},
"sender": {
"$first": "$sender"
},
"to": {
"$first": "$to"
},
"message": {
"$first" : "$message"
},
"timeStamp": {
"$first" : "$createdAt"
}
}
},
{
"$sort": {
"timeStamp": -1
}
},
//Group by the sorted array
{
"$group": {
"_id": "$users",
"sender": {
"$first": "$sender"
},
"to": {
"$first": "$to"
},
"message": {
"$first": "$message"
},
"timeStamp": {
"$first": "$timeStamp"
}
}
}
])
currentUserMessages = await messageModel.populate(currentUserMessages, {path: "sender", select: 'nickname avatarImage'})
currentUserMessages = await messageModel.populate(currentUserMessages, {path: "to", select: 'nickname avatarImage'})
res.status(200).json(currentUserMessages)
}
I enjoyed the query population the more, it makes you populate a field with information from another model you had earlier referenced when setting the field. In this case, In my 'to' and 'sender' field, I specifically made them Mongoose object Id and referenced the Users Model
sender: {type: Schema.Types.ObjectId, ref: "Users"},
to: {type: Schema.Types.ObjectId, ref: "Users"},
So when I called the population query, it filled this field with data that matched the userID on this field from the Users Model. It also allows you to select some specific fields instead of just populating with irrelevant info. I selected the nickname and the avatar Image. Which I displayed on Chat.
{chat.to._id=== user._id? chat.sender.avatarImage : chat.to.avatarImage}
{chat.to._id=== user._id? chat.sender.nickname : chat.to.nickname}
I have to make sure I'm always displaying the image and the name of the other party on the home page.
What's Next now?
I want to implement the socket as soon as possible for real time messaging. What's a chat without it anyways? I also heard that it can implement voice and video calls, bit of a long stretch? If it does ill certainly explore it. I want to deploy it as soon as possible so I'll be fast tracking the development. I'd be providing more updates as time goes on.
**Current Issues
- Doesn't focus to the end of the chat on page load
- Multi Avatar 10 API calls/Min
- React Emoji Picker slow Loading
Follow for More♥️
https://twitter.com/marvelstalwart/status/1578316884826624000
The rewards earned on this comment will go directly to the people sharing the post on Twitter as long as they are registered with @poshtoken. Sign up at https://hiveposh.com.
Good code structure thanks!

!1UP
You have received a 1UP from @gwajnberg!
@stem-curator, @vyb-curator, @pob-curator
And they will bring !PIZZA 🍕.
Learn more about our delegation service to earn daily rewards. Join the Cartel on Discord.
PIZZA Holders sent $PIZZA tips in this post's comments:
@curation-cartel(18/20) tipped @marvel1206 (x1)
You can now send $PIZZA tips in Discord via tip.cc!
Thanks for your contribution to the STEMsocial community. Feel free to join us on discord to get to know the rest of us!
Please consider delegating to the @stemsocial account (85% of the curation rewards are returned).
You may also include @stemsocial as a beneficiary of the rewards of this post to get a stronger support.
Thank you for sharing this amazing post on HIVE!
Your content got selected by our fellow curator tibfox & you just received a little thank you upvote from our non-profit curation initiative!
You will be featured in one of our recurring curation compilations which is aiming to offer you a stage to widen your audience within the DIY scene of Hive.
Make sure to always post / cross-post your creations within the DIYHub community on HIVE so we never miss your content. We also have a discord server where you can connect with us and other DIYers. If you want to support our goal to motivate other DIY/art/music/gardening/... creators just delegate to us and earn 100% of your curation rewards!
Stay creative & hive on!