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
- Initialize the Project
First, create a new directory for your project and initialize it with npm.
- Install Dependencies
Install the necessary packages for our project.
- 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
- 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}`));
- 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
- 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);
- 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);
- 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
- 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'); } };
- 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'); } };
- 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' }); } };
- 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
- 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'); } };
- 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
- 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'); } };
- 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'); } };
- 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
- 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'); } };
- 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'); } };
- 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
- 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'); } };
- 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
- Environment Variables
Create a .env
file to store environment variables.
- Using PM2 for Process Management
Install PM2 globally and use it to manage your application.
- Deploying to Heroku
Follow the steps to deploy your application to Heroku.
- Create a new Heroku application.
- Set up your environment variables on Heroku.
- 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
- Introduction to NPM
- Installing and Using Packages
- Creating and Publishing Packages
- Semantic Versioning
Module 6: Express.js Framework
- Introduction to Express.js
- Setting Up an Express Application
- Middleware
- Routing in Express
- Error Handling
Module 7: Databases and ORMs
- Introduction to Databases
- Using MongoDB with Mongoose
- Using SQL Databases with Sequelize
- CRUD Operations
Module 8: Authentication and Authorization
Module 9: Testing and Debugging
- Introduction to Testing
- Unit Testing with Mocha and Chai
- Integration Testing
- Debugging Node.js Applications
Module 10: Advanced Topics
Module 11: Deployment and DevOps
- Environment Variables
- Using PM2 for Process Management
- Deploying to Heroku
- Continuous Integration and Deployment