Friend Request System using node js.

harshit pandey
Frontend Weekly
Published in
13 min readOct 12, 2018

Hello folks. So today we are building a node js application where you can create your profile, search for friends and connect with them.

We will divide our main application into small modules into -

1- Login/ Registration system

2- User Profile

3- Searching friends

4- Sending and Accepting Requests(includes notification system)

Github:

https://github.com/harshittpandey/friendreq-system

1- LOGIN/ REGISTRATION SYSTEM

Our first step will be creating a basic login/registration system. I am using this amazing application Node.js login, registration and access control using Express and Passport which allows the user to register and login .

So, clone this application in your system and we will be further advancing this application according to our needs.(open the above link, clone the repository using Git Bash and don’t forget to star this repository).

now open node js command line prompt to install all the packages and run the application .

  • npm install
  • node app.js

so install all the packages and run your application. Try to create an account and login to your new account.

now in the next part we have to create a user profile it should look like this:

2- USER PROFILE

Our next step is to create User Profile so that user can upload profile picture. and then to redesign the navigation bar.

redesigned navbar

okay, so focusing on navbar.

You can download panda image from http://rwby.wikia.com/wiki/File:Panda.png (not necessary) and create a folder(Upload) in public folder and store our panda there.

now to make any change in the navbar open layout.

panda login -> views -> layouts -> layout.handlebars

  • in layout.handlebars search for class= “text-muted” and change Login to panda DOTS.
  • Include img tag before Login. <img src=”../uploads/panda.png” style=”width: 100px; position: absolute;left: 200px”>
  • Now we have to replace Dashboard with search button and notification drop down before logout button.
  • <li role=”presentation”><a href=”/search”>Search <i class=”fa fa-search”></i> </a></li>
    <li>
    <div class=”dropdown”>
    <button class=”btn btn-primary dropdown-toggle” type=”button” data-toggle=”dropdown”><i class=”fa fa-user-plus”></i>
    <span class=”caret”></span></button>
    <ul class=”dropdown-menu” style=”width: 300px”>
    <li style=”border-bottom: 1px solid lightgray”>
    <a href=”#”>
    <div>
    <h4>Harshit Pandey</h4>
    </div>
    <div>
    <button class=”btn btn-primary” style=”width: 50%”>Accept</button>
    <button class=”btn btn-primary” style=”width: 50%”>Reject</button>
    </div>
    </a>
    </li>
    </ul>
    </div>
    </li>

AND DON’T FORGET TO ADD LINKS THESE LINKS IN layout.handlebars:

current
desired

Now our application looks like this and we have to change

DASHBOARD

to

NAME (USERNAME).

for this open index.handlebars and as we want to print name and username so change Dashboard to {{user.name}} <span style=”color: blue”>(@{{user.username}})</span>

now UPLOADING PROFILE PICTURE:

  • we will store the profile image in our upload folder so and the name of image in the database.
  • open user.js in models and add this to your mongo schema.
    userImage : {
    type:String,
    default:’default.png’
    }
  • now add a default image in your upload folder with name default.png . so that whenever a new user visits the dashboard he should have a default profile.
  • remove WELCOME TO YOUR DASHBORAD and add our new html there.
  • we will start by adding html code to index.handlebars
    <div style=”margin-left: 30%”>
    <img class=”profilestyle” src=”uploads/{{user.userImage}}” alt=”{{user.userImage}}” >
    <form action=”/” enctype=”multipart/form-data” method=”post” style=”margin-top: 10px”>
    <label class=”custombutton” id=”custombutton” style=”width:200px;border: 1px solid #ccc;display: inline-block; padding: 6px 12px;cursor: pointer;”>
    <input type=”file” name=”upload” accept=”image/*” style=” display: none;” onchange=”document.getElementById(‘custombutton’).style.border = ‘2px solid lightgreen’”>
    Select new file
    </label>
    <input type=”submit” value=”Upload” style=”border: 1px solid #2d2d2d ; padding: 6px 12px; cursor: pointer; background-color: #2d2d2d; color: #ccc”>
    </form>
    </div>
  • We will use formidable to store new images in upload folder.
  • now open style.css in public/css/style.css
    .profilestyle {
    width: 250px;
    height: 250px;
    border-radius: 50%;
    border:1px solid #ccc;
    object-fit: contain;
    }

Now, our next task is to upload a new profile picture and store it in the upload folder.

  • — install formidable using node command line prompt :

node install formidable

Let’s dive into back-end of our application.

so, if want to try to upload your profile image then you have to write some code to make post request and save your image in upload folder.

  • now open index.js in routes/ index.js
  • import formidable
    var formidable = require(‘formidable’);
  • import user from models
    var User = require(‘../models/user’);
  • now make a post request and upload the image in upload folder and store the name of the file in the user’s database .
  • router.post(‘/’, function(req, res) {
    var form =new formidable.IncomingForm();
    form.parse(req);
    let reqPath= path.join(__dirname, ‘../’);
    let newfilename;
    form.on(‘fileBegin’, function(name, file){
    file.path = reqPath+ ‘public/upload/’+ req.user.username + file.name;
    newfilename= req.user.username+ file.name;
    });
    form.on(‘file’, function(name, file) {
    User.findOneAndUpdate({
    username: req.user.username
    },
    {
    ‘userImage’: newfilename
    },
    function(err, result){
    if(err) {
    console.log(err);
    }
    });
    });
    req.flash(‘success_msg’, ‘Your profile picture has been uploaded’);
    res.redirect(‘/’);
    });

WHAT AM I DOING HERE ?

I am making a post request and uploading the file to upload folder using formidable and at the same time i am updating user’s data(userImage) in database.

3- SEARCHING FRIENDS

If you try to click on search button then it will show that Cannot GET /search. it means we have to write some code to make get request.

  • So, open index.js in routes folder and write a function to make a get request.
    router.get(‘/search’, ensureAuthenticated, function(req, res){
    User.find({username: {$ne: req.user.username}}, function(err, result){
    if (err) throw err;
    res.render(‘search’,{
    result: result
    });
    });
    });

WHAT AM I DOING HERE ?

I m making a get request to /search and ensuring whether the user is logged in or not. I m getting the data of all the users except logged in user and storing the result in an array named as result.

  • our next step is to create a file search.handlebars in views folder .
    <h2>Search Friends</h2>
    <form action=”/search” method=”post”>
    <input type=”text” name=”searchfriend” placeholder=”username”>
    <input type=”submit” name=”search”>
    </form>
    <hr>
    {{#each result}}
    <div class=”usercard col-lg-3">
    <img class=”usercard-image” src=”uploads/{{this.userImage}}”>
    <h4 class=”usercard-name”>{{this.name}}</h4>
    <p class=”usercard-username”>@({{this.username}})</p>
    <button class=”usercard-button accept”>
    Accept
    </button>
    <button class=”usercard-button reject”>
    Reject
    </button>
    </div>
    {{/each}}

WHAT AM I DOING HERE ?

I have created a form where user can search for another user by username.

and there is a view which shows user-cards(with accept and reject buttons ) for all the registered user.

  • now add this css in style.
    .usercard{
    width: 190px;
    height: 300px;
    background-color: white;
    border-radius: 5px;
    box-shadow: 3px 3px 10px #A9A9A9;
    padding: 20px;
    margin: 20px;
    overflow: visible;
    }
    .usercard-image {
    width: 100px;
    height: 100px;
    border-radius: 50%;
    margin-left: 15%;
    object-fit: contain;
    }
    .usercard-name {
    margin-left: 10%;
    margin-right: 10%
    }
    .usercard-username {
    margin-left: 10%;
    margin-right: 10%;
    color: blue
    }
    .usercard-button {
    width: 100px;
    padding: 6px;
    margin:5px;
    margin-left: 15%;
    border-radius: 5px;
    color: white;
    border: none;
    }
    .accept {
    background-color: green;
    }
    .reject {
    background-color: red;
    }

Now we will make post request so that a user can search for another user.

  • open index.js in routes
    router.post(‘/search’, ensureAuthenticated, function(req, res) {
    var searchfriend = req.body.searchfriend;
    if (searchfriend == req.user.username) {
    searchfriend= null;
    }
    User.find({username: searchfriend}, function(err, result) {
    if (err) throw err;
    res.render(‘search’, {
    result: result
    });
    });
    });

WHAT AM I DOING HERE ?

I am making a post request to /search and searching for friends.and looking for user whose username matches with the search query.

4- SENDING AND ACCEPTING FRIEND REQUEST

Logic Behind Friend Request mechanism

We will be using socket.io for sending and accepting friend request. So I am using three array of objects sentRequest (to store the request a user has sent), request(to store the requests a user has received) , friendsList (to store a user’s friends).

  • create another file sendrequest.js in public/js folder .
  • open user.js in /modals folder and add this to the userSchema.
    sentRequest:[{
    username: {type: String, default: ‘’}
    }],
    request: [{
    userId: {type: mongoose.Schema.Types.ObjectId, ref: ‘User’},
    username: {type: String, default: ‘’}
    }],
    friendsList: [{
    friendId: {type: mongoose.Schema.Types.ObjectId, ref: ‘User’},
    friendName: {type: String, default: ‘’}
    }],
    totalRequest: {type: Number, default:0}

WHAT AM I DOING HERE ?

sendrequest.js is created on public folder as it will make ajax requests and it contains code to send the request , accept the request, reject the request.

— whenever a user sends a request to another user then sendrequest.js will update sentRequest object of the sender and the request object of the receiver.

— whenever a user accepts a request sendrequest.js will update friendsList of both sender and receiver.

  • now we will make some changes in handlebars.

There is a little mistake and we have to change Accept and Reject buttons to Add Friend.

  • open search.handlebars and replace this code with the previous result loop.
  • {{#each result}}
    <div class=”usercard col-lg-3">
    <img class=”usercard-image” src=”upload/{{this.userImage}}”>
    <h4 class=”usercard-name”>{{this.name}}</h4>
    <p class=”usercard-username”>@({{this.username}})</p>
    <form action=”” method=”get” class=”add_friend”>
    <input type=”hidden” name=”receiverName” class=”receiverName” value=”{{this.username}}”>
    <input type=”hidden” name=”sender-name” class=”sender-name” value=”{{user.username}}”>
    <button type=”submit” id=”” onclick=”addFriend(‘{{this.username}}’)” class=”btn add accept friend-add”><i class=”fa fa-user”></i> Add Friend</button>
    </form>
    </div>
    {{/each}}

WHAT AM I DOING HERE ?

— We are creating the user card for all the registered user and there is a button(type=submit) to send the friend request . There are also two hidden input fields which details about the sender and receiver.

— now whenever a user clicks on the Add Friend button it will run a function i.e. addFriend(RECEIVER).

  • now it’s time to write the addFriend() function.
  • but before that we need to add socket.io.js and sendRequest.js scripts to our layout.handlebars.
    <script type=”text/javascript” src=”/socket.io/socket.io.js”></script>
    <script type=”text/javascript” src=”/js/sendrequest.js”></script>
  • now open sendrequest.js and write the function.
    var sender = $(‘#currentuser’).val();
    var receiverName;
    function addFriend(name) {
    $.ajax({
    url: ‘/search’,
    type: ‘POST’,
    data: {
    receiverName: name
    },
    success: function() {
    // console.log(receiverName);
    }
    })
    }

WHAT AM I DOING HERE ?

— Whenever a user clicks on Add Friend then addFriend(RECEIVER) then it will make an AJAX call with receiver name.

LET’S WRITE THE BACKEND CODE FOR THIS.

  • I hope you have noticed that here we are making a post request to ‘/search’ and we were also making post request for the same route when we were searching for a friend.
  • so I am just checking if the input field is empty or not. thus write all the code inside if condition.
    if(searchfriend) {}
  • now install async using npm. (npm install async)
  • we will use async.parallel function because it run the tasks collection of functions in parallel, without waiting until the previous function has completed.
  • open index.js in /routes folder and include async.
    var async = require(‘async’);
  • now, after if(searchfriend){} write the async function for sending friend request.
    async.parallel([
    function(callback) {
    if(req.body.receiverName) {
    User.update({
    ‘username’: req.body.receiverName,
    ‘request.userId’: {$ne: req.user._id},
    ‘friendsList.friendId’: {$ne: req.user._id}
    },
    {
    $push: {request: {
    userId: req.user._id,
    username: req.user.username
    }},
    $inc: {totalRequest: 1}
    },(err, count) => {
    console.log(err);
    callback(err, count);
    })
    }
    },
    function(callback) {
    if(req.body.receiverName){
    User.update({
    ‘username’: req.user.username,
    ‘sentRequest.username’: {$ne: req.body.receiverName}
    },
    {
    $push: {sentRequest: {
    username: req.body.receiverName
    }}
    },(err, count) => {
    callback(err, count);
    })
    }
    }],
    (err, results)=>{
    res.redirect(‘/search’);
    });

WHAT AM I DOING HERE ?

— After making post request from sendRequest.js our job is to update the sentRequest object of sender and request of receiver.

— In the first function I am checking some conditions in

1- if sender is already a friend with the receiver(by looking into friendsList.friendId).

2- if sender has already sent request to the receiver (by looking into request.userId).

if these conditions DONOT satisfy, then I can update the request object of the receiver.

— In the second function I am checking the same situation (by looking into sentRequest.username) and then I can update the sentRequest object of the sender.

  • open layout.handlerbars and replace this code with the static request i.e <li> tag.
    {{#if user.request}}
    {{#each newfriend}}
    <li style=”border-bottom: 1px solid lightgray”>
    <a href=”#”>
    <div>
    <h4>{{this.username}}</h4>
    </div>
    <div>
    <div style=”width: 50%; display: inline;”>
    <input type=”hidden” name=”senderId” id=”senderId” value=”{{this.userId}}”>
    <input type=”hidden” name=”senderName” id=”senderName” value=”{{this.username}}”>
    <button type=”submit” id=”accept_friend” class=”btn btn-primary” style=”width: 8em”>Accept</button>
    </div>
    <div style=”width: 50%; display: inline;”>
    <input type=”hidden” name=”user_Id” id=”user_Id” value=”{{this.userId}}”>
    <button type=”submit” id=”cancel_friend” class=”btn btn-primary” style=”width: 8em”>Cancel</button>
    </div>
    </div>
    </a>
    </li>
    {{/each}}
    {{else}}
    <div>
    <h4 style=”text-align:center”>No Requests</h4>
    </div>
    {{/if}}
  • and add an attribute id=”reload” to the <nav> tag.

WHAT AM I DOING HERE ?

{{#if user.request}} here I am checking if the user have some requests, then display all the requests. otherwise print “No Requests”.

— With accept_friend button there are two hidden input fields which gives the senderId and senderName.(to store in friendList object of both sender and receiver)

— With cancel_friend button there is only one hidden input field which gives the userId of sender.(to remove sender from request object).

  • now open sendrequest.js and copy this code right after the addfriend function to accept friend request or cancel friend request.
    $(document).ready(function(){
    $(‘.friend-add’).on(‘click’, function(e){
    e.preventDefault();
    });
    $(‘#accept_friend’).on(‘click’, function(){
    var senderId= $(‘#senderId’).val();
    var senderName= $(‘#senderName’).val();
    $.ajax({
    url: ‘/search’,
    type: ‘POST’,
    data: {
    senderId:senderId,
    senderName: senderName
    },
    success: function() {
    $(this).parent().eq(1).remove();
    }
    });
    $(‘#reload’).load(location.href + ‘ #reload’);
    });
    $(‘#cancel_friend’).on(‘click’, function(){
    var user_Id= $(‘#user_Id’).val();
    // console.log(user_Id);
    $.ajax({
    url: ‘/search’,
    type: ‘POST’,
    data: {
    user_Id: user_Id
    },
    success: function() {
    $(this).parent().eq(1).remove();
    }
    });
    $(‘#reload’).load(location.href + ‘ #reload’);
    });
    });

WHAT AM I DOING HERE ?

— Whenever a user clicks on accept_friend then it will make a POST request to /search with senderId and senderName.

— With accept_friend button there are two hidden input fields which gives the senderId and senderName.(to store in friendList object of both sender and receiver)

— With cancel_friend button there is only one hidden input field which gives the userId of sender.(to remove sender from request object).

$(this).parent().eq(1).remove(); will remove the html <li> element either you click accept or reject.

— $(‘#reload’).load(location.href + ‘ #reload’); this will reload the navbar whenever you accepts or reject request without event reloading the whole page.

  • now open sendrequest.js and copy this code right after the addfriend function to accept friend request or cancel friend request.
    $(document).ready(function(){
    $(‘.friend-add’).on(‘click’, function(e){
    e.preventDefault();
    });
    $(‘#accept_friend’).on(‘click’, function(){
    var senderId= $(‘#senderId’).val();
    var senderName= $(‘#senderName’).val();
    $.ajax({
    url: ‘/search’,
    type: ‘POST’,
    data: {
    senderId:senderId,
    senderName: senderName
    },
    success: function() {
    $(this).parent().eq(1).remove();
    }
    });
    $(‘#reload’).load(location.href + ‘ #reload’);
    });
    $(‘#cancel_friend’).on(‘click’, function(){
    var user_Id= $(‘#user_Id’).val();
    // console.log(user_Id);
    $.ajax({
    url: ‘/search’,
    type: ‘POST’,
    data: {
    user_Id: user_Id
    },
    success: function() {
    $(this).parent().eq(1).remove();
    }
    });
    $(‘#reload’).load(location.href + ‘ #reload’);
    });
    });

WHAT AM I DOING HERE ?

— Whenever a user clicks on accept_friend then it will make a POST request to /search with senderId and senderName.

— With accept_friend button there are two hidden input fields which gives the senderId and senderName.(to store in friendList object of both sender and receiver)

— With cancel_friend button there is only one hidden input field which gives the userId of sender.(to remove sender from request object).

— $(this).parent().eq(1).remove(); will remove the html <li> element either you click accept or reject.

— $(‘#reload’).load(location.href + ‘ #reload’); this will reload the navbar whenever you accepts or reject request without event reloading the whole page.

LET’S WRITE THE BACKEND CODE FOR THIS.

Copy this code after the previous async function to send the friend request. here we have to write four functions to update friendsList of both the user if receiver accpets the request and if the user rejects the request then we have to update the sendRequest and request objects.

async.parallel([
// this function is updated for the receiver of the friend request when it is accepted
function(callback) {
if (req.body.senderId) {
User.update({
‘_id’: req.user._id,
‘friendsList.friendId’: {$ne:req.body.senderId}
},{
$push: {friendsList: {
friendId: req.body.senderId,
friendName: req.body.senderName
}},
$pull: {request: {
userId: req.body.senderId,
username: req.body.senderName
}},
$inc: {totalRequest: -1}
}, (err, count)=> {
callback(err, count);
});
}
},
// this function is updated for the sender of the friend request when it is accepted by the receiver
function(callback) {
if (req.body.senderId) {
User.update({
‘_id’: req.body.senderId,
‘friendsList.friendId’: {$ne:req.user._id}
},{
$push: {friendsList: {
friendId: req.user._id,
friendName: req.user.username
}},
$pull: {sentRequest: {
username: req.user.username
}}
}, (err, count)=> {
callback(err, count);
});
}
},
function(callback) {
if (req.body.user_Id) {
User.update({
‘_id’: req.user._id,
‘request.userId’: {$eq: req.body.user_Id}
},{
$pull: {request: {
userId: req.body.user_Id
}},
$inc: {totalRequest: -1}
}, (err, count)=> {
callback(err, count);
});
}
},
function(callback) {
if (req.body.user_Id) {
User.update({
‘_id’: req.body.user_Id,
‘sentRequest.username’: {$eq: req.user.username}
},{
$pull: {sentRequest: {
username: req.user.username
}}
}, (err, count)=> {
callback(err, count);
});
}
}
],(err, results)=> {
res.redirect(‘/search’);
});
});

WHAT AM I DOING HERE ?

— First two functions will update friendsList of receiver and sender respectively.

— Third function will update the receiver’s request object .

— Fourth function will update the sender’s sentRequest object.

SO, THAT’S IT. YOUR FRIEND REQUEST SYSTEM IS READY . YOU CAN FURTHER ADD MORE FEATURES TO THIS APPLICATION.

I hope you loved my approach, if you liked it then please share and feel free to ask any questions.

Github link: https://github.com/harshittpandey/friendreq-system

If you have any questions please drop a mail:

harshitpandey678@gmail.com

--

--