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.