Building a Social Network with Node.js and MongoDB

Social networks have become an integral part of modern life, allowing people to connect with others across the world, share experiences, and build communities. Building a social network from scratch can seem like a daunting task, but with the right tools and techniques, it can be an achievable goal. In this article, we will explore how to build a social network using Node.js and MongoDB, two powerful technologies that can help you create a scalable, fast, and secure platform.

Node.js is a JavaScript runtime built on top of Google’s V8 engine that allows you to run JavaScript on the server-side. It provides an event-driven architecture and a non-blocking I/O model that allows you to handle a large number of requests without slowing down your application. On the other hand, MongoDB is a document-oriented NoSQL database that can store data in a flexible, scalable, and efficient way. Together, Node.js and MongoDB make a great combination for building web applications that require real-time updates, such as social networks.

Before we dive into the technical details, let’s define the basic features of a social network. Typically, a social network allows users to create profiles, post content, follow other users, like and comment on posts, and send private messages. To build a social network, we need to implement these features and more. Let’s get started!

Setting up the Development Environment

To begin, we need to set up our development environment. We assume you have basic knowledge of Node.js and MongoDB. You can download and install the latest version of Node.js from nodejs.org and MongoDB from mongodb.com.

Next, we need to create a new Node.js project and install the necessary dependencies. We will use Express, a popular Node.js web framework, to build our application. To create a new project, open your terminal and run the following commands:

bash
mkdir social-network
cd social-network
npm init -y
npm install express body-parser cors mongoose bcrypt jsonwebtoken

We have created a new project, installed the Express framework, and some other necessary dependencies. The body-parser middleware is used to parse the request body, cors middleware is used to enable Cross-Origin Resource Sharing, mongoose is used to interact with MongoDB, bcrypt is used to hash passwords, and jsonwebtoken is used to generate and verify JSON Web Tokens for user authentication.

Designing the Database Schema

Before we start coding, we need to design our database schema. A schema is a blueprint that defines the structure of our data. We will use MongoDB to store our data in a flexible and scalable way. In MongoDB, data is stored in collections, and each collection can have multiple documents. A document is similar to a JSON object and can have a variable number of fields.

For our social network, we need to define the following collections:

  • Users: This collection will store information about each user, such as their username, email, password, profile picture, and other details.
  • Posts: This collection will store each post created by users, including the post content, the author, and other metadata.
  • Comments: This collection will store comments made by users on posts, including the comment text, the author, and the post to which the comment belongs.
  • Likes: This collection will store the likes made by users on posts and comments, including the user who liked the post/comment and the post/comment to which the like belongs.
  • Messages: This collection will store private messages sent between users, including the sender, the receiver, the message text, and other metadata.

We can define our database schema using the Mongoose schema definition. Open the models directory and create a new file named user.js. Add the following code:

php
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');

const userSchema = new mongoose.Schema({
  username: { type: String, required: true, unique: true },
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  profilePicture: { type: String, default: "" },
  coverPicture: { type: String, default: "" },
  followers: { type: Array, default: [] },
  following: { type: Array, default: [] },
  isAdmin: { type: Boolean, default: false },
}, { timestamps: true });

userSchema.pre('save', async function (next) {
  try {
    if (!this.isModified('password')) {
      return next();
    }
    const hashedPassword = await bcrypt.hash(this.password, 10);
    this.password = hashedPassword;
    return next();
  } catch (err) {
    return next(err);
  }
});

userSchema.methods.comparePassword = async function (candidatePassword, next) {
  try {
    const isMatch = await bcrypt.compare(candidatePassword, this.password);
    return isMatch;
  } catch (err) {
    return next(err);
  }
};

const User = mongoose.model('User', userSchema);

module.exports = User;

In this file, we first require the mongoose and bcrypt modules. Then we define the userSchema using the mongoose.Schema constructor. The schema defines the fields that will be stored for each user, including the username, email, password, profilePicture, coverPicture, followers, following, and isAdmin fields.

The password field is hashed using the bcrypt module before being saved to the database. We define a pre hook on the save event to hash the password. The comparePassword method is used to compare a user’s input password with the hashed password stored in the database.

Finally, we create a User model using the mongoose.model method and export it for use in other parts of the application.

We will define the schema for other collections (Post, Comment, Like, Message) in similar files in the models directory.

Building the REST API

Now that we have defined our database schema, we can start building the REST API to interact with the database. We will use Express to define our API routes and handlers.

Create a new file named app.js in the root directory of the project and add the following code:

php
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const mongoose = require('mongoose');

const app = express();

app.use(bodyParser.json());
app.use(cors());

mongoose.connect('mongodb://localhost/social-network', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  useCreateIndex: true,
}).then(() => {
  console.log('Connected to MongoDB');
}).catch((err) => {
  console.error(err);
});

const authRoutes = require('./routes/auth');
const userRoutes = require('./routes/users');
const postRoutes = require('./routes/posts');
const commentRoutes = require('./routes/comments');
const likeRoutes = require('./routes/likes');
const messageRoutes = require('./routes/messages');

app.use('/api/auth', authRoutes);
app.use('/api/users', userRoutes);
app.use('/api/posts', postRoutes);
app.use('/api/comments', commentRoutes);
app.use('/api/likes', likeRoutes);
app.use('/api/messages', messageRoutes);

app.listen(3000, () => {
  console.log('Server started on port 3000');
});

In this file, we first require the necessary modules (express, body-parser, cors, and mongoose). We then create an instance of the express application and use body-parser and cors as middleware.

We then connect to the MongoDB database using the mongoose.connect method. This method takes a connection string and some options to configure the connection to the database. In this case, we are connecting to a database named social-network running on the local machine.

We then require the various route files that we will define in the routes directory (auth, users, posts, comments, likes, and messages). We mount these routes to the corresponding API endpoints using the app.use method.

Finally, we start the server listening on port 3000 using the app.listen method.

Defining API Routes

Now that we have our server set up and connected to the database, we can start defining the API routes. We will define the API routes in separate files in the routes directory.

Authentication Routes

Create a new file named auth.js in the routes directory and add the following code:

javascript
const express = require('express');
const jwt = require('jsonwebtoken');
const User = require('../models/user');

const router = express.Router();

router.post('/register', async (req, res, next) => {
  try {
    const user = new User(req.body);
    await user.save();
    res.status(201).json(user);
  } catch (err) {
    next(err);
  }
});

router.post('/login', async (req, res, next) => {
  try {
    const { email, password } = req.body;
    const user = await User.findOne({ email });
    if (!user) {
      return res.status(401).json({ message: 'Authentication failed' });
    }
    const isMatch = await user.comparePassword(password);
    if (!isMatch) {
      return res.status(401).json({ message: 'Authentication failed' });
    }
    const token = jwt.sign({ userId: user._id }, 'secret');
    res.status(200).json({ token });
  } catch (err) {
    next(err);
  }
});

module.exports = router;

In this file, we first require the necessary modules (express, jsonwebtoken, and User model). We then create an instance of the express.Router class and define the API routes.

The /register route is used to create a new user account. We first create a new User object using the request body and then save it to the database using the save method. We then return a response with a status code of 201 and the newly created user object.

The /login route is used to authenticate a user. We first extract the email and password from the request body. We then find the user with the specified email using the findOne method of the User model. If no user is found, we return a response with a status code of 401 (unauthorized). If a user is found, we compare the provided password with the stored password using the comparePassword method of the User model. If the passwords do not match, we return a response with a status code of 401. If the passwords match, we generate a JWT token using the sign method of the jsonwebtoken module and return a response with a status code of 200 and the token.

User Routes

Create a new file named users.js in the routes directory and add the following code:

scss
const express = require('express');
const jwt = require('jsonwebtoken');
const User = require('../models/user');

const router = express.Router();

router.get('/', async (req, res, next) => {
  try {
    const users = await User.find();
    res.status(200).json(users);
  } catch (err) {
    next(err);
  }
});

router.get('/:userId', async (req, res, next) => {
  try {
    const user = await User.findById(req.params.userId);
    if (!user) {
      return res.status(404).json({ message: 'User not found' });
    }
    res.status(200).json(user);
  } catch (err) {
    next(err);
  }
});

router.put('/:userId', async (req, res, next) => {
  try {
    const user = await User.findByIdAndUpdate(req.params.userId, req.body, { new: true });
    if (!user) {
      return res.status(404).json({ message: 'User not found' });
    }
    res.status(200).json(user);
  } catch (err) {
    next(err);
  }
});

router.delete('/:userId', async (req, res, next) => {
  try {
    const user = await User.findByIdAndDelete(req.params.userId);
    if (!user) {
      return res.status(404).json({ message: 'User not found' });
    }
    res.status(204).json();
  } catch (err) {
    next(err);
  }
});

module.exports = router;

In this file, we first require the necessary modules (express, jsonwebtoken, and User model). We then create an instance of the express.Router class and define the API routes.

The / route is used to get all users. We use the find method of the User model to get all users from the database and return a response with a status code of 200 and the array of users.

The /:userId route is used to get a single user by ID. We use the findById method of the User model to find the user with the specified ID. If no user is found, we return a response with a status code of 404 (not found). If a user is found, we return a response with a status code of 200 and the user object.

The /:userId route with the PUT method is used to update a user by ID. We use the findByIdAndUpdate method of the User model to find and update the user with the specified ID. If no user is found, we return a response with a status code of 404. If a user is found and updated successfully, we return a response with a status code of 200 and the updated user object.

The /:userId route with the DELETE method is used to delete a user by ID. We use the findByIdAndDelete method of the User model to find and delete the user with the specified ID. If no user is found, we return a response with a status code of 404. If a user is found and deleted successfully, we return a response with a status code of 204 (no content).

Running the Server

To start the server, add the following code to the index.js file:

javascript
const express = require('express');
const mongoose = require('mongoose');
const authRoutes = require('./routes/auth');
const userRoutes = require('./routes/users');

const app = express();

mongoose.connect('mongodb://localhost/socialnetwork', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  useCreateIndex: true,
})
  .then(() => {
    console.log('Connected to MongoDB');
  })
  .catch((err) => {
    console.error('Error connecting to MongoDB', err);
  });

app.use(express.json());
app.use('/auth', authRoutes);
app.use('/users', userRoutes);

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ message: 'Something went wrong' });
});

app.listen(3000, () => {
  console.log('Server started on port 3000');
});

In this file, we first require the necessary modules (express, mongoose, authRoutes, and userRoutes). We then create an instance of the express application and connect to the MongoDB database using the mongoose.connect method.

We use the express.json() middleware to parse incoming requests with JSON payloads. We then define the API routes using the app.use method and the authRoutes and userRoutes routers.

Finally, we define an error handling middleware function that will catch any errors thrown by the API routes or middleware functions and return a response with a status code of 500 (internal server error) and a generic error message.

We then start the server by calling the listen method of the app object and passing in the port number (3000 in this example) and a callback function to log a message when the server has started.

Conclusion

In this article, we have built a simple social network API using Node.js and MongoDB. We have created a user authentication system using JSON Web Tokens, created a User model using Mongoose, and defined API routes for user registration, login, and management.

0368826868