In this module, we will build a fully functional blogging platform using Node.js and Express.js. This project will help you understand how to integrate various concepts and technologies you've learned throughout the course. By the end of this module, you will have a solid understanding of how to create a real-world application with Node.js.

Table of Contents

Project Setup

  1. Initialize the Project

First, create a new directory for your project and initialize it with npm.

mkdir blogging-platform
cd blogging-platform
npm init -y

  1. Install Dependencies

Install the necessary packages for our project.

npm install express mongoose bcryptjs jsonwebtoken body-parser
npm install --save-dev nodemon

  1. Project Structure

Create the following directory structure:

blogging-platform/
├── config/
│   └── db.js
├── controllers/
│   ├── authController.js
│   ├── postController.js
│   └── commentController.js
├── models/
│   ├── User.js
│   ├── Post.js
│   └── Comment.js
├── routes/
│   ├── authRoutes.js
│   ├── postRoutes.js
│   └── commentRoutes.js
├── middleware/
│   └── authMiddleware.js
├── .env
├── app.js
└── package.json

  1. Setting Up Express

Create app.js and set up the basic Express server.

const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
require('dotenv').config();

const app = express();

// Middleware
app.use(bodyParser.json());

// Routes
app.use('/api/auth', require('./routes/authRoutes'));
app.use('/api/posts', require('./routes/postRoutes'));
app.use('/api/comments', require('./routes/commentRoutes'));

// Connect to MongoDB
mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true })
    .then(() => console.log('MongoDB connected'))
    .catch(err => console.log(err));

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

  1. Database Configuration

Create config/db.js to handle the MongoDB connection.

const mongoose = require('mongoose');

const connectDB = async () => {
    try {
        await mongoose.connect(process.env.MONGO_URI, {
            useNewUrlParser: true,
            useUnifiedTopology: true,
        });
        console.log('MongoDB connected');
    } catch (err) {
        console.error(err.message);
        process.exit(1);
    }
};

module.exports = connectDB;

Database Design

  1. User Model

Create models/User.js to define the User schema.

const mongoose = require('mongoose');

const UserSchema = new mongoose.Schema({
    username: {
        type: String,
        required: true,
        unique: true,
    },
    email: {
        type: String,
        required: true,
        unique: true,
    },
    password: {
        type: String,
        required: true,
    },
    date: {
        type: Date,
        default: Date.now,
    },
});

module.exports = mongoose.model('User', UserSchema);

  1. Post Model

Create models/Post.js to define the Post schema.

const mongoose = require('mongoose');

const PostSchema = new mongoose.Schema({
    title: {
        type: String,
        required: true,
    },
    body: {
        type: String,
        required: true,
    },
    author: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User',
        required: true,
    },
    date: {
        type: Date,
        default: Date.now,
    },
});

module.exports = mongoose.model('Post', PostSchema);

  1. Comment Model

Create models/Comment.js to define the Comment schema.

const mongoose = require('mongoose');

const CommentSchema = new mongoose.Schema({
    body: {
        type: String,
        required: true,
    },
    author: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User',
        required: true,
    },
    post: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'Post',
        required: true,
    },
    date: {
        type: Date,
        default: Date.now,
    },
});

module.exports = mongoose.model('Comment', CommentSchema);

User Authentication

  1. Register User

Create controllers/authController.js to handle user registration.

const User = require('../models/User');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');

exports.register = async (req, res) => {
    const { username, email, password } = req.body;

    try {
        let user = await User.findOne({ email });
        if (user) {
            return res.status(400).json({ msg: 'User already exists' });
        }

        user = new User({
            username,
            email,
            password,
        });

        const salt = await bcrypt.genSalt(10);
        user.password = await bcrypt.hash(password, salt);

        await user.save();

        const payload = {
            user: {
                id: user.id,
            },
        };

        jwt.sign(
            payload,
            process.env.JWT_SECRET,
            { expiresIn: 360000 },
            (err, token) => {
                if (err) throw err;
                res.json({ token });
            }
        );
    } catch (err) {
        console.error(err.message);
        res.status(500).send('Server error');
    }
};

  1. Login User

Add the login functionality in authController.js.

exports.login = async (req, res) => {
    const { email, password } = req.body;

    try {
        let user = await User.findOne({ email });
        if (!user) {
            return res.status(400).json({ msg: 'Invalid Credentials' });
        }

        const isMatch = await bcrypt.compare(password, user.password);
        if (!isMatch) {
            return res.status(400).json({ msg: 'Invalid Credentials' });
        }

        const payload = {
            user: {
                id: user.id,
            },
        };

        jwt.sign(
            payload,
            process.env.JWT_SECRET,
            { expiresIn: 360000 },
            (err, token) => {
                if (err) throw err;
                res.json({ token });
            }
        );
    } catch (err) {
        console.error(err.message);
        res.status(500).send('Server error');
    }
};

  1. Authentication Middleware

Create middleware/authMiddleware.js to protect routes.

const jwt = require('jsonwebtoken');

module.exports = function (req, res, next) {
    const token = req.header('x-auth-token');

    if (!token) {
        return res.status(401).json({ msg: 'No token, authorization denied' });
    }

    try {
        const decoded = jwt.verify(token, process.env.JWT_SECRET);
        req.user = decoded.user;
        next();
    } catch (err) {
        res.status(401).json({ msg: 'Token is not valid' });
    }
};

  1. Auth Routes

Create routes/authRoutes.js to handle authentication routes.

const express = require('express');
const router = express.Router();
const { register, login } = require('../controllers/authController');

router.post('/register', register);
router.post('/login', login);

module.exports = router;

Creating Blog Posts

  1. Create Post Controller

Create controllers/postController.js to handle creating posts.

const Post = require('../models/Post');

exports.createPost = async (req, res) => {
    const { title, body } = req.body;

    try {
        const newPost = new Post({
            title,
            body,
            author: req.user.id,
        });

        const post = await newPost.save();
        res.json(post);
    } catch (err) {
        console.error(err.message);
        res.status(500).send('Server error');
    }
};

  1. Post Routes

Create routes/postRoutes.js to handle post routes.

const express = require('express');
const router = express.Router();
const { createPost } = require('../controllers/postController');
const auth = require('../middleware/authMiddleware');

router.post('/', auth, createPost);

module.exports = router;

Reading Blog Posts

  1. Get All Posts

Add functionality to get all posts in postController.js.

exports.getAllPosts = async (req, res) => {
    try {
        const posts = await Post.find().populate('author', ['username']);
        res.json(posts);
    } catch (err) {
        console.error(err.message);
        res.status(500).send('Server error');
    }
};

  1. Get Single Post

Add functionality to get a single post by ID in postController.js.

exports.getPostById = async (req, res) => {
    try {
        const post = await Post.findById(req.params.id).populate('author', ['username']);
        if (!post) {
            return res.status(404).json({ msg: 'Post not found' });
        }
        res.json(post);
    } catch (err) {
        console.error(err.message);
        res.status(500).send('Server error');
    }
};

  1. Update Post Routes

Update routes/postRoutes.js to include the new routes.

const express = require('express');
const router = express.Router();
const { createPost, getAllPosts, getPostById } = require('../controllers/postController');
const auth = require('../middleware/authMiddleware');

router.post('/', auth, createPost);
router.get('/', getAllPosts);
router.get('/:id', getPostById);

module.exports = router;

Updating and Deleting Blog Posts

  1. Update Post

Add functionality to update a post in postController.js.

exports.updatePost = async (req, res) => {
    const { title, body } = req.body;

    try {
        let post = await Post.findById(req.params.id);
        if (!post) {
            return res.status(404).json({ msg: 'Post not found' });
        }

        if (post.author.toString() !== req.user.id) {
            return res.status(401).json({ msg: 'User not authorized' });
        }

        post = await Post.findByIdAndUpdate(
            req.params.id,
            { $set: { title, body } },
            { new: true }
        );

        res.json(post);
    } catch (err) {
        console.error(err.message);
        res.status(500).send('Server error');
    }
};

  1. Delete Post

Add functionality to delete a post in postController.js.

exports.deletePost = async (req, res) => {
    try {
        const post = await Post.findById(req.params.id);
        if (!post) {
            return res.status(404).json({ msg: 'Post not found' });
        }

        if (post.author.toString() !== req.user.id) {
            return res.status(401).json({ msg: 'User not authorized' });
        }

        await post.remove();
        res.json({ msg: 'Post removed' });
    } catch (err) {
        console.error(err.message);
        res.status(500).send('Server error');
    }
};

  1. Update Post Routes

Update routes/postRoutes.js to include the new routes.

const express = require('express');
const router = express.Router();
const { createPost, getAllPosts, getPostById, updatePost, deletePost } = require('../controllers/postController');
const auth = require('../middleware/authMiddleware');

router.post('/', auth, createPost);
router.get('/', getAllPosts);
router.get('/:id', getPostById);
router.put('/:id', auth, updatePost);
router.delete('/:id', auth, deletePost);

module.exports = router;

Commenting System

  1. Create Comment Controller

Create controllers/commentController.js to handle comments.

const Comment = require('../models/Comment');
const Post = require('../models/Post');

exports.createComment = async (req, res) => {
    const { body } = req.body;

    try {
        const post = await Post.findById(req.params.postId);
        if (!post) {
            return res.status(404).json({ msg: 'Post not found' });
        }

        const newComment = new Comment({
            body,
            author: req.user.id,
            post: req.params.postId,
        });

        const comment = await newComment.save();
        res.json(comment);
    } catch (err) {
        console.error(err.message);
        res.status(500).send('Server error');
    }
};

  1. Comment Routes

Create routes/commentRoutes.js to handle comment routes.

const express = require('express');
const router = express.Router();
const { createComment } = require('../controllers/commentController');
const auth = require('../middleware/authMiddleware');

router.post('/:postId', auth, createComment);

module.exports = router;

Deploying the Application

  1. Environment Variables

Create a .env file to store environment variables.

MONGO_URI=your_mongodb_connection_string
JWT_SECRET=your_jwt_secret
PORT=5000

  1. Using PM2 for Process Management

Install PM2 globally and use it to manage your application.

npm install -g pm2
pm2 start app.js

  1. Deploying to Heroku

Follow the steps to deploy your application to Heroku.

  1. Create a new Heroku application.
  2. Set up your environment variables on Heroku.
  3. Push your code to Heroku.
heroku create
heroku config:set MONGO_URI=your_mongodb_connection_string
heroku config:set JWT_SECRET=your_jwt_secret
git push heroku main

Conclusion

In this module, we have built a complete blogging platform using Node.js and Express.js. We covered user authentication, creating, reading, updating, and deleting blog posts, and adding a commenting system. We also learned how to deploy our application to Heroku. This project has provided a comprehensive overview of how to build a real-world application with Node.js.

Node.js Course

Module 1: Introduction to Node.js

Module 2: Core Concepts

Module 3: File System and I/O

Module 4: HTTP and Web Servers

Module 5: NPM and Package Management

Module 6: Express.js Framework

Module 7: Databases and ORMs

Module 8: Authentication and Authorization

Module 9: Testing and Debugging

Module 10: Advanced Topics

Module 11: Deployment and DevOps

Module 12: Real-World Projects

© Copyright 2024. All rights reserved