This is the second part of the Building Real time private chatting app using React. In this part, you will implement below-listed features in your application,
- Registration.
- Checking the uniqueness of the username before registration.
- Login.
- Checking the Session of a user (when the user navigates to the home page).
In the First part of the Series, we did the setup of React and Nodejs application. Since I have created boilerplates for Both React and Nodejs, therefore you don’t have to do the monotonous work for your self.
I assume you have cloned or downloaded the React boilerplate and Nodejs boilerplate. I highly recommend you to clone/download, If you haven’t cloned/downloaded these boilerplates already. If you use Boilerplates, your application will be less prone to mistakes.
Okay, Now install and run both the boilerplates that you have for React application and Nodejs server. I hope till this point, you are with me. And somehow if you are having any trouble let me know.
Setting Up the Authentication Page
In this section first, we will set up the authentication page. In the below Image as you can see we have Tabs for Login and Registration. If you click of Login Tab then you will see the Login form and If you click on Registration page then you will see the Registration Form.
Here we will react-bootstrap to implement the Tabs. In the container of those Tabs, we will render the Login
and Registration
components. The below code will do the same, open the Authentication.js file and add the below code.
Authentication.js:
/* * Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ import React, { Component } from 'react'; import {Tabs, Tab} from 'react-bootstrap' import Login from './login/Login'; import Registration from './registration/Registration'; import './Authentication.css'; class Authentication extends Component { state = { loadingState: false } setRenderLoadingState = (loadingState) => { this.setState({ loadingState: loadingState }); } render() { return ( <div className="container"> <div className = {`overlay auth-loading ${this.state.loadingState ? '' : 'visibility-hidden'}`}> <h1>Loading</h1> </div> <div className="authentication-screen"> <Tabs variant="pills" defaultActiveKey = "login" > <Tab eventKey="login" title="Login"> <Login loadingState={this.setRenderLoadingState}/> </Tab> <Tab eventKey="registration" title="Registration"> <Registration loadingState={this.setRenderLoadingState}/> </Tab> </Tabs> </div> </div> ); } } export default Authentication;
Explanation:
- First, we imported
Tabs
andTab
for obvious reasons. - Then we imported
Login
andRegistration
components to display them inside the Tab container. - In the state object of Authentication component, we have only one property i.e.
loadingState
. This property will be used to display the overlay loading if we make an ajax call fromLogin
andRegistration
component. setRenderLoadingState()
method will be called from the child component with the help of ReactProps
.- Lastly, we have two tabs container to show both the
Login
andRegistration
component.
Implementing Registration Feature
Let’s start by implementing Registration feature. I don’t think I have much to talk about the importance of the registration feature. First, we’ll write the client side code. Later we will write node APIs and all code related to it.
=> So the first step is writing MarkUp, so open the Registration.js
and write down the below code markup in the render()
method.
Registration.js:
/* * Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ render() { return ( <Form className="auth-form"> <Form.Group controlId="formUsername"> <DebounceInput className="form-control" placeholder = "Enter username" minLength={2} debounceTimeout={300} onChange={ this.checkUsernameAvailability } /> </Form.Group> <Form.Group controlId="formPassword"> <Form.Control type = "password" name = "password" placeholder = "Enter Password" onChange = { this.handleInputChange } /> </Form.Group> <Button variant="primary" type="submit" onClick={this.handleRegistration}> Registration </Button> </Form> ); }
Explanation:
Form
,Form.Group
,From.Control
andButton
components are part of the react-bootstrap library.- The
DebounceInput
component will be used for a specific purpose, we will talk about this component in the next section. - The
checkUsernameAvailability()
andhandleInputChange()
methods will update the state with respective details. - The
handleRegistration()
will call the Make an Ajax call in order to register the new user.
To make your markup work, you will have to write some code inside your component class. Open theRegistration.jsand write down the below code.
=>In the below code, we will use ChatHttpServer class to register the new User.
=>The application will redirect the user to the Home Page as soon as the user finishes the registration process.
Resgistration.js:
/* * Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ import React, { Component } from 'react'; import { Alert, Form, Button } from 'react-bootstrap'; import { withRouter } from 'react-router-dom'; import { DebounceInput } from 'react-debounce-input'; import ChatHttpServer from '../../../utils/ChatHttpServer'; import './Registration.css'; class Registration extends Component { constructor(props) { super(props); this.state = { username: '', password: '' }; } handleRegistration = async (event) => { event.preventDefault(); this.props.loadingState(true); try { const response = await ChatHttpServer.register(this.state); this.props.loadingState(false); if (response.error) { alert('Unable to register, try after some time.') } else { chatHttpServer.setLS('userid', response.userId); this.props.history.push(`/home`); } } catch (error) { this.props.loadingState(false); alert('Unable to register, try after some time.') } } checkUsernameAvailability = async (event) => { this.setState({ username: event.target.value }); } handleInputChange = (event) => { this.setState({ [event.target.name]: event.target.value }); } }
Explanation:
- Let’s start from the top first, we have imported all the necessary components from third-party libraries.
- Then we have imported the
ChatHttpServer
class object. - In the state object, we have two properties username and password. We will directly send this state object to the server while registration.
- In
handleRegistration()
method, first, we will call theloadingState()
method using Reactprops
. ChatHttpServer.register()
method will register the user by making an HTTP call to the server, here we are directly passing the state object. The state object has theusername
andpassword
inside it with the updated value.- Once the Registration is successful, then we will redirect the user to the
/home
page. Also, we will store the userId in local storage so that we can use it later in the application. - The
checkUsernameAvailability()
and thehandleInputChange()
methods will update the keys of state object respectively. - If anything goes wrong, we are showing an alert with an error message.
Forgive me for showing ugly messages. For the sake of simplicity, I have used the usual alerts, instead of fancy POPUPs.
To complete the registration process you will have to write code in Nodejs as well. Basically, writing Nodejs code will piece of a cake for you, because you don’t have much to write. Let’s get to it, Open the routes.js and add the below the route.
routes.js:
this.app.post('/register', routeHandler.registerRouteHandler);
After that, you have to write a few lines of code inside theregisterRouteHandler()
, just to handle the request/response and call the method which just inserts the data into Database.
Add the below code, inside the route.handler.jsfile. TheregisterRouteHandler()
will take care of the /register
route.
route.handler.js:
/* * Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */async registerRouteHandler(request, response){ const data = { username : (request.body.username).toLowerCase(), password : request.body.password }; if(data.username === '') { response.status(CONSTANTS.SERVER_ERROR_HTTP_CODE).json({ error : true, message : CONSTANTS.USERNAME_NOT_FOUND }); }else if(data.password === ''){ response.status(CONSTANTS.SERVER_ERROR_HTTP_CODE).json({ error : true, message : CONSTANTS.PASSWORD_NOT_FOUND }); }else{ try { data.online = 'Y' ; data.socketId = '' ; data.password = passwordHash.createHash(data.password); const result = await helper.registerUser(data); if (result === null || result === undefined) { response.status(CONSTANTS.SERVER_OK_HTTP_CODE).json({ error : false, message : CONSTANTS.USER_REGISTRATION_FAILED }); } else { response.status(CONSTANTS.SERVER_OK_HTTP_CODE).json({ error : false, userId : result.insertedId, message : CONSTANTS.USER_REGISTRATION_OK }); } } catch ( error ) { response.status(CONSTANTS.SERVER_NOT_FOUND_HTTP_CODE).json({ error : true, message : CONSTANTS.SERVER_ERROR_MESSAGE }); } } }
Explanation:
- In the above code, you will first check the validation. If anything wrong with that, we will send the proper error code with an Error message.
- Then we will generate the Password Hash for user entered password.
- And the end if everything is valid or in more precise words if the request is valid than you will Register a user by calling
registerUser()
. - The
registerUser()
is defined inside the query-handler.js.
Open thequery-handler.jsfile and write down the below code. As you have figured it by now, You will just have to insert the records in MongoDB in the below code.
query-handler.js:
/* * Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari * * Name of the Method : registerUser * Description : register the User * Parameter : * 1) data query object for MongoDB * Return : Promise */registerUser(data){ return new Promise( async (resolve, reject) => { try { const [DB,ObjectID] = await this.Mongodb.onConnect(); DB.collection('users').insertOne(data, (err, result) =>{ DB.close(); if( err ){ reject(err); } resolve(result); }); } catch (error) { reject(error) } }); }
Explanation:
- In the above code, First thing you will do is return promise. By returning promise you can save yourself from callback hell, and later you can apply Async/Await on this function.
- After that, you will connect the MongoDB database and insert the record into the database.
- And that’s it. You have created a new user.
Checking for a unique username
It is very important that before registration, you check the uniqueness of username. It’s obvious that you can’t have two users with the same username.
First thing first, let’s add the Markup in Registration.js. Here you don’t have much to write, just an indicator that shows the particular username is taken.
In the below Markup, we have used the React state to hide and show the Message by using the usernameAvailable
state property of Registration Component class.
<!-- Real time private chatting app using React, Nodejs, MongoDB, and Socket.io @author Shashank Tiwari --> <Alert className={{ 'username-availability-warning' : true, 'visibility-hidden': this.state.usernameAvailable }} variant="danger"> <strong>{this.state.username}</strong> is already taken, try another username. </Alert>
The above markup will hide/show only if you write something in your input tag.
First of all, add the below property in your state object ofRegistration Componentclass,
this.state = { username: '', password: '', usernameAvailable: true };
After that add the below code in Registration Component class, The below code will check the username uniqueness by Making an HTTP request to your Nodejs server. Open theRegistration.jsand update the code ofcheckUsernameAvailability()
method,
Registration.js:
/* * Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ checkUsernameAvailability = async (event) => { if(event.target.value !== '' && event.target.value !== undefined) { this.setState({ username: event.target.value }); this.props.loadingState(true); try { const response = await ChatHttpServer.checkUsernameAvailability(this.state.username); this.props.loadingState(false); if(response.error) { this.setState({ usernameAvailable: false }); } else { this.setState({ usernameAvailable: true }); } } catch (error) { this.props.loadingState(false); this.setState({ usernameAvailable: false }); } } else if (event.target.value === '') { this.setState({ usernameAvailable: true }); } }
Explanation:
- First, we will update the state property
username
, then we are calling theloadingState()
method using React props. - Then we will call the
ChatHttpServer.checkUsernameAvailability()
, which eventually make an HTTP request to check the uniqueness of username. - And based on the response of the HTTP server we will set the value of the
usernameAvailable
property of the state.
This is not complete yet, you still have to code for your Nodejs part. Writing Nodejs routes and its helper will be the same as it was in the last section, In fact, it’s going to be identical for Nodejs routes. So first as usual open the routes.js and add the below the route.
routes.js:
this.app.post('/usernameAvailable', routeHandler.userNameCheckHandler);
Now our express route is added, let’s write a method to handle that request in the route-handler.js file. Open that file and add the below code,
route-handler.js:
/* * Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */async userNameCheckHandler(request, response){ const username = request.body.username; if (username === "") { response.status(CONSTANTS.SERVER_ERROR_HTTP_CODE).json({ error : true, message : CONSTANTS.USERNAME_NOT_FOUND }); } else { try { const count = await helper.userNameCheck( { username : username.toLowerCase() }); if (count > 0) { response.status(200).json({ error : true, message : CONSTANTS.USERNAME_AVAILABLE_FAILED }); } else { response.status(200).json({ error : false, message : CONSTANTS.USERNAME_AVAILABLE_OK }); } } catch ( error ){ response.status(CONSTANTS.SERVER_ERROR_HTTP_CODE).json({ error : true, message : CONSTANTS.SERVER_ERROR_MESSAGE }); } } }
If you notice we have calleduserNameCheck()
method to check the user count of same usernames. This method will basically check the counts of the given username and returns that count to the callee.
Open the query-handler.js and add the below code, in the below code you just a run a MongoDB query and return the response to caller method.
query-handler.js:
/* * Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari * * Name of the Method : userNameCheck * Description : To check if the username is available or not. * Parameter : * 1) data query object for MongDB * Return : Promise */userNameCheck(data){ return new Promise( async (resolve, reject) => { try { const [DB,ObjectID] = await this.Mongodb.onConnect(); DB.collection('users').find(data).count( (error, result) => { DB.close(); if( error ){ reject(error); } resolve(result); }); } catch (error) { reject(error) } }); }
Now, this completes your Checking for a unique username features. Congrats, you have added one more feature to your application.
Implementing Login Feature:
Implementation of Login feature is going to pretty easy and almost identical to above-implemented features. First, we will start with Markup, then component and in the end, we will write some NodeJs just like we did in the last two sections.
The below markup will render the Login form, which will have two fields username and password. Open theLogin.jsand add the below code in render()
method,
Login.js:
/* * Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ render() { return ( <Form className="auth-form"> <Form.Group controlId="loginUsername"> <Form.Control type = "text" name = "username" placeholder = "Enter username" onChange = { this.handleInputChange } /> </Form.Group> <Form.Group controlId="loginPassword"> <Form.Control type = "password" name = "password" placeholder = "Password" onChange = { this.handleInputChange } /> </Form.Group> <Button variant="primary" type="submit" onClick={this.handleLogin}> Login </Button> </Form> ); }
Explanation:
Form
,Form.Group
,From.Control
andButton
components are part of the react-bootstrap library.handleInputChange()
methods will update the React state with respective details.handleLogin()
methods will make an HTTP request to/login
endpoint usingChatHTTPServer
class.
Now we will complete the rest if code i.e. left in the Login component. The below code will handle the Login form submission. Open the Login.js and write down the below code,
Login.js:
/* * Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ import React, { Component } from 'react'; import { Form, Button } from 'react-bootstrap'; import { withRouter } from 'react-router-dom'; import ChatHttpServer from '../../../utils/ChatHttpServer'; import './Login.css'; class Login extends Component { constructor(props) { super(props); this.state = { username: '', password: '' }; } handleLogin = async (event) => { event.preventDefault(); this.props.loadingState(true); try { const response = await ChatHttpServer.login(this.state); this.props.loadingState(false); if(response.error) { alert('Invalid login details') } else { ChatHttpServer.setLS('userid', response.userId); this.props.history.push(`/home`) } } catch (error) { this.props.loadingState(false); alert('Invalid login details') } } handleInputChange = (event) => { this.setState({ [event.target.name]: event.target.value }); } // render() method should come here } export default withRouter(Login)
Explanation:
- Let’s start from the top first, we have imported all the necessary components from third-party libraries.
- Then we have imported the
ChatHttpServer
class object. - In the state object, we have two properties username and password. We will directly send this state object to the server while Login.
- In
handleLogin()
method, first, we will call theloadingState()
method using Reactprops
. ChatHttpServer.login()
method will register the user by making an HTTP call to the server, here we are directly passing the state object. The state object has theusername
andpassword
inside it with the updated value.- Once the Login is successful, then we will redirect the user to the
/home
page. Also, we will store the userId in local storage so that we can use it later in the application. - The
handleInputChange()
methods will update the keys of state object respectively.
Let’s write the Nodejs part, Here we will do three things First we will add the route. Second, we will add a method to handle the request/response of the route and at the end method to perform the Login.
So let’s just add the route, open the routes.js. and add the below route.
routes.js:
this.app.post('/login', routeHandler.loginRouteHandler);
The/login
route is added to its respective file, now let’s write the method to handle the response to the/login
route. Open the route-handler.js and add the below code,
route-handler.js:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */async loginRouteHandler(request, response){ const data = { username : (request.body.username).toLowerCase(), password : request.body.password }; if(data.username === '' || data.username === null) { response.status(CONSTANTS.SERVER_ERROR_HTTP_CODE).json({ error : true, message : CONSTANTS.USERNAME_NOT_FOUND }); }else if(data.password === '' || data.password === null){ response.status(CONSTANTS.SERVER_ERROR_HTTP_CODE).json({ error : true, message : CONSTANTS.PASSWORD_NOT_FOUND }); }else{ try { const result = await queryHandler.getUserByUsername(data.username); if(result === null || result === undefined) { response.status(CONSTANTS.SERVER_NOT_FOUND_HTTP_CODE).json({ error : true, message : CONSTANTS.USER_LOGIN_FAILED }); } else { if( passwordHash.compareHash(data.password, result.password) ){ await queryHandler.makeUserOnline(result._id); response.status(CONSTANTS.SERVER_OK_HTTP_CODE).json({ error : false, userId : result._id, message : CONSTANTS.USER_LOGIN_OK }); } else { response.status(CONSTANTS.SERVER_NOT_FOUND_HTTP_CODE).json({ error : true, message : CONSTANTS.USER_LOGIN_FAILED }); } } } catch (error) { response.status(CONSTANTS.SERVER_NOT_FOUND_HTTP_CODE).json({ error : true, message : CONSTANTS.USER_LOGIN_FAILED }); } } }
Explanation:
- There is a lot of things going on here, First, we will do the validation if everything goes fine, then we will actually fetch the result from the database and verify it with the data sent from the Angular application.
- After validation and stuff, we will fetch the record of the user by using his/her username.
- If you get a result from the database using the username, then go ahead compare the password of a user by using
compareHash()
method. - After comparing the password, based on the result you will make the status of user online.
Checking User’s Session
It is very critical to check the session of the user before allowing the users to chat and read messages. To check the session of the user, we will make an HTTP call. Whenever a user visits the home page of the application we will check the session.
=> Here we will use React’s lifecycle method i.e. componentDidMount()
. In this method, we will make an HTTP call to check the session of the user. Open theHome.jsand write down the below code,
Home.js:
/* * Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ async componentDidMount() { try { this.setRenderLoadingState(true); this.userId = await ChatHttpServer.getUserId(); const response = await ChatHttpServer.userSessionCheck(this.userId); if (response.error) { this.props.history.push(`/`) } else { this.setState({ username: response.username }); } this.setRenderLoadingState(false); } catch (error) { this.setRenderLoadingState(false); this.props.history.push(`/`) } }
Explanation:
- In this method, first, we will call the
setRenderLoadingState()
method to show a loading screen while we make check the session of the user by making an HTTP call. - To check the session of the user, we need the user id of that user. We can get the user ID using
getUserId()
method, which is defined inside theChatHttpServer
service. - Once we will get the user ID then we can call the
userSessionCheck()
method, this method we will make an HTTP call to check if the user is logged in or not. - If the response says, User is logged in then we will make set the username in our component state.
- If the user is not logged in then we will redirect the user to the Home page.
Now let’s add the route for session check service in the routes.js file. Here we will check the online status of the user, and based on the online status we will determine the session of that user. So add the below route in the routes.js file.
routes.js:
this.app.post('/userSessionCheck', routeHandler.userSessionCheckRouteHandler);
As always, we have to write method inside theroute-handler.js file to handle the response for the /userSessionCheck
route. As you can see, in this case, we are callinguserSessionCheckRouteHandler()
to handle the response for the /userSessionCheck
.
Open the route-handler.js and add the below code,
route-handler.js :
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */async userSessionCheckRouteHandler(request, response){ let userId = request.body.userId; if (userId === '') { response.status(CONSTANTS.SERVER_ERROR_HTTP_CODE).json({ error : true, message : CONSTANTS.USERID_NOT_FOUND }); }else{ try { const result = await queryHandler.userSessionCheck({ userId : userId }); response.status(CONSTANTS.SERVER_OK_HTTP_CODE).json({ error : false, username : result.username, message : CONSTANTS.USER_LOGIN_OK }); } catch ( error ) { response.status(CONSTANTS.SERVER_NOT_ALLOWED_HTTP_CODE).json({ error : true, message : CONSTANTS.USER_NOT_LOGGED_IN }); } } }
Explanation:
- In the above code, First, we will check for the userId validation. If the validation fails, we will send the user not found a response.
- If validation passes, then we will check the online status of the user in the Database by using userId.
- And depending on the online status we will send the response to Angular Application.
Now to check the online status in the Database you will have to writeuserSessionCheck()
method in QueryHandler
class. This method will basically check the online status by executing a MongoDB Query.
Open the query-handler.js file and add the below method,
query-handler.js:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari * * Name of the Method : userSessionCheck * Description : to check if user is online or not. * Parameter : * 1) data query object for MongDB * Return : Promise */userSessionCheck(data){ return new Promise( async (resolve, reject) => { try { const [DB,ObjectID] = await this.Mongodb.onConnect(); DB.collection('users').findOne( { _id : ObjectID(data.userId) , online : 'Y'}, (err, result) => { DB.close(); if( err ){ reject(err); } resolve(result); }); } catch (error) { reject(error) } }); }
And I assume above code needs no explanation, since you are just executing a MongoDB query and returning a Promise.
That’s it.
Conclusion
Now let’s conclude this part by recapping what we did in this part.
- We downloaded theReact boilerplateandNodejs boilerplateand using these created our application.
- We implemented a Registration feature.
- We implemented a feature to check uniqueness of the username before registration.
- We added Login functionality in our application.
- In the end, we added a feature to check the session of a User.
In the next part of the series, we will connect our React application to the socket and implement the Realtime chat list for the application.
So stay tuned it’s going to be more interesting.
This tutorial has been pretty helpful and detailed so far.
Wanted to know when the next part will be available, I’m really looking forward to it !
Looks great, I’m waiting 3rd part of this interesting tutorial.
Hey! I searched a lot and went through tons of articles before I found yours. This has been really helpful to build a chat application and I look forward to next part to be able to complete the chat app. When will the next part be available?
great work…looking 3rd part …please upload it as soon as possible
waiting for 3rd part????
Looking forward to 3rd part for more than 4 months now…
Hi Harsh, I was very busy for the last few months. But now I am getting some time so this week you can expect the 3rd part.
I really appreciate reading this Blog Thanks.
Hey Harsh, here are the rest of tha parts,
Part 3: https://www.codershood.info/2019/10/20/real-time-private-chatting-app-using-react-nodejs-mongodb-and-socket-io-part-3/
Part 4: https://www.codershood.info/2019/10/31/real-time-private-chatting-app-using-react-nodejs-mongodb-and-socket-io-part-4/
If you have any question please get in touch.
Thanks again,