Node.js Intermediate Projects - সম্পূর্ণ গাইড
এই গাইডে ৬টি intermediate-level project এর complete implementation দেওয়া আছে। প্রতিটি project এ আপনি শিখবেন real-world application development এর সব aspects।
📑 Projects List
- Blog API with Authentication
- E-commerce Backend
- Social Media API
- Task Management System
- User Management System with Roles
- File Upload Service
Project 1: Blog API with Authentication
একটি complete blog API যেখানে authentication, CRUD operations, এবং user-specific content থাকবে।
Features:
- ✅ User registration & login (JWT)
- ✅ Create, read, update, delete blog posts
- ✅ Only author can edit/delete their posts
- ✅ Public posts viewing
- ✅ Categories and tags
- ✅ Search functionality
Tech Stack:
- Express.js
- MongoDB + Mongoose
- JWT authentication
- bcrypt for password hashing
Project Structure:
blog-api/
├── src/
│ ├── config/
│ │ └── database.js
│ ├── models/
│ │ ├── User.js
│ │ └── Post.js
│ ├── controllers/
│ │ ├── authController.js
│ │ └── postController.js
│ ├── middleware/
│ │ ├── auth.js
│ │ └── errorHandler.js
│ ├── routes/
│ │ ├── authRoutes.js
│ │ └── postRoutes.js
│ ├── utils/
│ │ └── validation.js
│ └── app.js
├── .env
├── package.json
└── server.jsStep 1: Setup & Installation
mkdir blog-api
cd blog-api
npm init -y
# Install dependencies
npm install express mongoose bcryptjs jsonwebtoken dotenv express-validator cors
npm install --save-dev nodemonpackage.json scripts:
{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
}.env:
PORT=5000
MONGODB_URI=mongodb://localhost:27017/blog-api
JWT_SECRET=your-secret-key-here-change-in-production
JWT_EXPIRE=7d
NODE_ENV=developmentStep 2: Database Configuration
src/config/database.js:
const mongoose = require('mongoose');
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGODB_URI);
console.log('✅ MongoDB connected successfully');
} catch (error) {
console.error('❌ MongoDB connection error:', error.message);
process.exit(1);
}
};
module.exports = connectDB;Step 3: Models
src/models/User.js:
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Name is required'],
trim: true,
minlength: [3, 'Name must be at least 3 characters']
},
email: {
type: String,
required: [true, 'Email is required'],
unique: true,
lowercase: true,
match: [/^\S+@\S+\.\S+$/, 'Please provide a valid email']
},
password: {
type: String,
required: [true, 'Password is required'],
minlength: [6, 'Password must be at least 6 characters'],
select: false // Don't return password by default
},
role: {
type: String,
enum: ['user', 'admin'],
default: 'user'
},
createdAt: {
type: Date,
default: Date.now
}
});
// Hash password before saving
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
});
// Compare password method
userSchema.methods.comparePassword = async function(candidatePassword) {
return await bcrypt.compare(candidatePassword, this.password);
};
module.exports = mongoose.model('User', userSchema);src/models/Post.js:
const mongoose = require('mongoose');
const postSchema = new mongoose.Schema({
title: {
type: String,
required: [true, 'Title is required'],
trim: true,
maxlength: [200, 'Title cannot exceed 200 characters']
},
content: {
type: String,
required: [true, 'Content is required']
},
excerpt: {
type: String,
maxlength: [300, 'Excerpt cannot exceed 300 characters']
},
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
category: {
type: String,
required: [true, 'Category is required'],
enum: ['Technology', 'Lifestyle', 'Travel', 'Food', 'Other']
},
tags: [{
type: String,
trim: true
}],
status: {
type: String,
enum: ['draft', 'published'],
default: 'draft'
},
views: {
type: Number,
default: 0
},
publishedAt: Date,
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
});
// Update updatedAt on save
postSchema.pre('save', function(next) {
this.updatedAt = Date.now();
if (this.status === 'published' && !this.publishedAt) {
this.publishedAt = Date.now();
}
next();
});
// Create excerpt from content if not provided
postSchema.pre('save', function(next) {
if (!this.excerpt && this.content) {
this.excerpt = this.content.substring(0, 150) + '...';
}
next();
});
module.exports = mongoose.model('Post', postSchema);Step 4: Middleware
src/middleware/auth.js:
const jwt = require('jsonwebtoken');
const User = require('../models/User');
exports.protect = async (req, res, next) => {
try {
let token;
// Check if token exists in headers
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
token = req.headers.authorization.split(' ')[1];
}
if (!token) {
return res.status(401).json({
success: false,
error: 'Not authorized to access this route'
});
}
// Verify token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Get user from token
req.user = await User.findById(decoded.id);
if (!req.user) {
return res.status(401).json({
success: false,
error: 'User not found'
});
}
next();
} catch (error) {
return res.status(401).json({
success: false,
error: 'Not authorized to access this route'
});
}
};
// Check if user is admin
exports.authorize = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({
success: false,
error: 'User role not authorized to access this route'
});
}
next();
};
};src/middleware/errorHandler.js:
const errorHandler = (err, req, res, next) => {
let error = { ...err };
error.message = err.message;
// Mongoose validation error
if (err.name === 'ValidationError') {
const message = Object.values(err.errors).map(e => e.message);
error = {
statusCode: 400,
message: message
};
}
// Mongoose duplicate key
if (err.code === 11000) {
const message = 'Duplicate field value entered';
error = {
statusCode: 400,
message: message
};
}
// Mongoose bad ObjectId
if (err.name === 'CastError') {
const message = 'Resource not found';
error = {
statusCode: 404,
message: message
};
}
res.status(error.statusCode || 500).json({
success: false,
error: error.message || 'Server Error'
});
};
module.exports = errorHandler;Step 5: Controllers
src/controllers/authController.js:
const User = require('../models/User');
const jwt = require('jsonwebtoken');
// Generate JWT Token
const generateToken = (id) => {
return jwt.sign({ id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRE
});
};
// @desc Register user
// @route POST /api/auth/register
// @access Public
exports.register = async (req, res, next) => {
try {
const { name, email, password } = req.body;
// Create user
const user = await User.create({
name,
email,
password
});
// Generate token
const token = generateToken(user._id);
res.status(201).json({
success: true,
data: {
id: user._id,
name: user.name,
email: user.email,
role: user.role,
token
}
});
} catch (error) {
next(error);
}
};
// @desc Login user
// @route POST /api/auth/login
// @access Public
exports.login = async (req, res, next) => {
try {
const { email, password } = req.body;
// Validate email & password
if (!email || !password) {
return res.status(400).json({
success: false,
error: 'Please provide email and password'
});
}
// Check for user (include password)
const user = await User.findOne({ email }).select('+password');
if (!user) {
return res.status(401).json({
success: false,
error: 'Invalid credentials'
});
}
// Check if password matches
const isMatch = await user.comparePassword(password);
if (!isMatch) {
return res.status(401).json({
success: false,
error: 'Invalid credentials'
});
}
// Generate token
const token = generateToken(user._id);
res.status(200).json({
success: true,
data: {
id: user._id,
name: user.name,
email: user.email,
role: user.role,
token
}
});
} catch (error) {
next(error);
}
};
// @desc Get current logged in user
// @route GET /api/auth/me
// @access Private
exports.getMe = async (req, res, next) => {
try {
const user = await User.findById(req.user.id);
res.status(200).json({
success: true,
data: user
});
} catch (error) {
next(error);
}
};src/controllers/postController.js:
const Post = require('../models/Post');
// @desc Get all posts
// @route GET /api/posts
// @access Public
exports.getPosts = async (req, res, next) => {
try {
const { status, category, search, page = 1, limit = 10 } = req.query;
// Build query
let query = {};
// Filter by status (default: published for public)
if (status) {
query.status = status;
} else {
query.status = 'published';
}
// Filter by category
if (category) {
query.category = category;
}
// Search in title and content
if (search) {
query.$or = [
{ title: { $regex: search, $options: 'i' } },
{ content: { $regex: search, $options: 'i' } }
];
}
// Pagination
const skip = (page - 1) * limit;
const posts = await Post.find(query)
.populate('author', 'name email')
.sort({ createdAt: -1 })
.skip(skip)
.limit(parseInt(limit));
const total = await Post.countDocuments(query);
res.status(200).json({
success: true,
count: posts.length,
total,
totalPages: Math.ceil(total / limit),
currentPage: parseInt(page),
data: posts
});
} catch (error) {
next(error);
}
};
// @desc Get single post
// @route GET /api/posts/:id
// @access Public
exports.getPost = async (req, res, next) => {
try {
const post = await Post.findById(req.params.id)
.populate('author', 'name email');
if (!post) {
return res.status(404).json({
success: false,
error: 'Post not found'
});
}
// Increment views
post.views += 1;
await post.save();
res.status(200).json({
success: true,
data: post
});
} catch (error) {
next(error);
}
};
// @desc Create new post
// @route POST /api/posts
// @access Private
exports.createPost = async (req, res, next) => {
try {
// Add user to req.body
req.body.author = req.user.id;
const post = await Post.create(req.body);
res.status(201).json({
success: true,
data: post
});
} catch (error) {
next(error);
}
};
// @desc Update post
// @route PUT /api/posts/:id
// @access Private
exports.updatePost = async (req, res, next) => {
try {
let post = await Post.findById(req.params.id);
if (!post) {
return res.status(404).json({
success: false,
error: 'Post not found'
});
}
// Make sure user is post author
if (post.author.toString() !== req.user.id && req.user.role !== 'admin') {
return res.status(401).json({
success: false,
error: 'Not authorized to update this post'
});
}
post = await Post.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true
});
res.status(200).json({
success: true,
data: post
});
} catch (error) {
next(error);
}
};
// @desc Delete post
// @route DELETE /api/posts/:id
// @access Private
exports.deletePost = async (req, res, next) => {
try {
const post = await Post.findById(req.params.id);
if (!post) {
return res.status(404).json({
success: false,
error: 'Post not found'
});
}
// Make sure user is post author
if (post.author.toString() !== req.user.id && req.user.role !== 'admin') {
return res.status(401).json({
success: false,
error: 'Not authorized to delete this post'
});
}
await post.deleteOne();
res.status(200).json({
success: true,
data: {}
});
} catch (error) {
next(error);
}
};
// @desc Get my posts
// @route GET /api/posts/my-posts
// @access Private
exports.getMyPosts = async (req, res, next) => {
try {
const posts = await Post.find({ author: req.user.id })
.sort({ createdAt: -1 });
res.status(200).json({
success: true,
count: posts.length,
data: posts
});
} catch (error) {
next(error);
}
};Step 6: Routes
src/routes/authRoutes.js:
const express = require('express');
const { register, login, getMe } = require('../controllers/authController');
const { protect } = require('../middleware/auth');
const router = express.Router();
router.post('/register', register);
router.post('/login', login);
router.get('/me', protect, getMe);
module.exports = router;src/routes/postRoutes.js:
const express = require('express');
const {
getPosts,
getPost,
createPost,
updatePost,
deletePost,
getMyPosts
} = require('../controllers/postController');
const { protect } = require('../middleware/auth');
const router = express.Router();
router.route('/')
.get(getPosts)
.post(protect, createPost);
router.get('/my-posts', protect, getMyPosts);
router.route('/:id')
.get(getPost)
.put(protect, updatePost)
.delete(protect, deletePost);
module.exports = router;Step 7: App Setup
src/app.js:
const express = require('express');
const cors = require('cors');
const authRoutes = require('./routes/authRoutes');
const postRoutes = require('./routes/postRoutes');
const errorHandler = require('./middleware/errorHandler');
const app = express();
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Routes
app.use('/api/auth', authRoutes);
app.use('/api/posts', postRoutes);
// Error handler
app.use(errorHandler);
module.exports = app;server.js:
require('dotenv').config();
const app = require('./src/app');
const connectDB = require('./src/config/database');
// Connect to database
connectDB();
const PORT = process.env.PORT || 5000;
const server = app.listen(PORT, () => {
console.log(`🚀 Server running on port ${PORT}`);
});
// Handle unhandled promise rejections
process.on('unhandledRejection', (err) => {
console.error(`❌ Error: ${err.message}`);
server.close(() => process.exit(1));
});Testing the API:
# Start server
npm run dev
# Register a user
curl -X POST http://localhost:5000/api/auth/register \
-H "Content-Type: application/json" \
-d '{"name":"John Doe","email":"john@example.com","password":"password123"}'
# Login
curl -X POST http://localhost:5000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"john@example.com","password":"password123"}'
# Create a post (replace TOKEN with actual token)
curl -X POST http://localhost:5000/api/posts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN" \
-d '{"title":"My First Post","content":"This is the content","category":"Technology","status":"published"}'
# Get all posts
curl http://localhost:5000/api/posts
# Get single post
curl http://localhost:5000/api/posts/POST_IDProject 2: E-commerce Backend
একটি complete e-commerce backend যেখানে products, cart, এবং orders management থাকবে।
Features:
- ✅ Product management (CRUD)
- ✅ Categories
- ✅ Shopping cart
- ✅ Order management
- ✅ User authentication
- ✅ Admin panel features
Additional Models:
models/Product.js:
const mongoose = require('mongoose');
const productSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Product name is required'],
trim: true,
maxlength: [200, 'Name cannot exceed 200 characters']
},
description: {
type: String,
required: [true, 'Description is required']
},
price: {
type: Number,
required: [true, 'Price is required'],
min: [0, 'Price cannot be negative']
},
comparePrice: {
type: Number,
min: [0, 'Compare price cannot be negative']
},
category: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Category',
required: true
},
brand: {
type: String,
trim: true
},
stock: {
type: Number,
required: [true, 'Stock is required'],
min: [0, 'Stock cannot be negative'],
default: 0
},
images: [{
url: String,
alt: String
}],
featured: {
type: Boolean,
default: false
},
rating: {
average: {
type: Number,
default: 0,
min: 0,
max: 5
},
count: {
type: Number,
default: 0
}
},
tags: [String],
isActive: {
type: Boolean,
default: true
},
createdAt: {
type: Date,
default: Date.now
}
});
// Index for search
productSchema.index({ name: 'text', description: 'text' });
module.exports = mongoose.model('Product', productSchema);models/Category.js:
const mongoose = require('mongoose');
const categorySchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Category name is required'],
unique: true,
trim: true
},
slug: {
type: String,
unique: true,
lowercase: true
},
description: String,
parent: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Category',
default: null
},
image: String,
isActive: {
type: Boolean,
default: true
},
createdAt: {
type: Date,
default: Date.now
}
});
// Generate slug before save
categorySchema.pre('save', function(next) {
if (this.isModified('name')) {
this.slug = this.name.toLowerCase().replace(/\s+/g, '-');
}
next();
});
module.exports = mongoose.model('Category', categorySchema);models/Cart.js:
const mongoose = require('mongoose');
const cartItemSchema = new mongoose.Schema({
product: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Product',
required: true
},
quantity: {
type: Number,
required: true,
min: [1, 'Quantity cannot be less than 1'],
default: 1
},
price: {
type: Number,
required: true
}
});
const cartSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
unique: true
},
items: [cartItemSchema],
totalAmount: {
type: Number,
default: 0
},
updatedAt: {
type: Date,
default: Date.now
}
});
// Calculate total amount before save
cartSchema.pre('save', function(next) {
this.totalAmount = this.items.reduce((total, item) => {
return total + (item.price * item.quantity);
}, 0);
this.updatedAt = Date.now();
next();
});
module.exports = mongoose.model('Cart', cartSchema);models/Order.js:
const mongoose = require('mongoose');
const orderItemSchema = new mongoose.Schema({
product: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Product',
required: true
},
name: String,
quantity: {
type: Number,
required: true,
min: 1
},
price: {
type: Number,
required: true
}
});
const orderSchema = new mongoose.Schema({
orderNumber: {
type: String,
unique: true,
required: true
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
items: [orderItemSchema],
shippingAddress: {
fullName: { type: String, required: true },
phone: { type: String, required: true },
address: { type: String, required: true },
city: { type: String, required: true },
postalCode: { type: String, required: true },
country: { type: String, required: true }
},
paymentMethod: {
type: String,
enum: ['cash', 'card', 'mobile-banking'],
required: true
},
paymentStatus: {
type: String,
enum: ['pending', 'paid', 'failed', 'refunded'],
default: 'pending'
},
orderStatus: {
type: String,
enum: ['pending', 'processing', 'shipped', 'delivered', 'cancelled'],
default: 'pending'
},
subtotal: {
type: Number,
required: true
},
shippingCost: {
type: Number,
default: 0
},
tax: {
type: Number,
default: 0
},
totalAmount: {
type: Number,
required: true
},
notes: String,
createdAt: {
type: Date,
default: Date.now
},
paidAt: Date,
deliveredAt: Date
});
// Generate order number before save
orderSchema.pre('save', async function(next) {
if (this.isNew) {
const count = await this.constructor.countDocuments();
this.orderNumber = `ORD-${Date.now()}-${count + 1}`;
}
next();
});
module.exports = mongoose.model('Order', orderSchema);Cart Controller (controllers/cartController.js):
const Cart = require('../models/Cart');
const Product = require('../models/Product');
// @desc Get user cart
// @route GET /api/cart
// @access Private
exports.getCart = async (req, res, next) => {
try {
let cart = await Cart.findOne({ user: req.user.id })
.populate('items.product', 'name price images stock');
if (!cart) {
cart = await Cart.create({ user: req.user.id, items: [] });
}
res.status(200).json({
success: true,
data: cart
});
} catch (error) {
next(error);
}
};
// @desc Add item to cart
// @route POST /api/cart/items
// @access Private
exports.addToCart = async (req, res, next) => {
try {
const { productId, quantity = 1 } = req.body;
// Check if product exists
const product = await Product.findById(productId);
if (!product) {
return res.status(404).json({
success: false,
error: 'Product not found'
});
}
// Check stock
if (product.stock < quantity) {
return res.status(400).json({
success: false,
error: 'Insufficient stock'
});
}
// Find or create cart
let cart = await Cart.findOne({ user: req.user.id });
if (!cart) {
cart = await Cart.create({ user: req.user.id, items: [] });
}
// Check if item already in cart
const existingItemIndex = cart.items.findIndex(
item => item.product.toString() === productId
);
if (existingItemIndex > -1) {
// Update quantity
cart.items[existingItemIndex].quantity += quantity;
} else {
// Add new item
cart.items.push({
product: productId,
quantity,
price: product.price
});
}
await cart.save();
cart = await cart.populate('items.product', 'name price images stock');
res.status(200).json({
success: true,
data: cart
});
} catch (error) {
next(error);
}
};
// @desc Update cart item quantity
// @route PUT /api/cart/items/:productId
// @access Private
exports.updateCartItem = async (req, res, next) => {
try {
const { productId } = req.params;
const { quantity } = req.body;
if (quantity < 1) {
return res.status(400).json({
success: false,
error: 'Quantity must be at least 1'
});
}
const cart = await Cart.findOne({ user: req.user.id });
if (!cart) {
return res.status(404).json({
success: false,
error: 'Cart not found'
});
}
const itemIndex = cart.items.findIndex(
item => item.product.toString() === productId
);
if (itemIndex === -1) {
return res.status(404).json({
success: false,
error: 'Item not found in cart'
});
}
// Check stock
const product = await Product.findById(productId);
if (product.stock < quantity) {
return res.status(400).json({
success: false,
error: 'Insufficient stock'
});
}
cart.items[itemIndex].quantity = quantity;
await cart.save();
await cart.populate('items.product', 'name price images stock');
res.status(200).json({
success: true,
data: cart
});
} catch (error) {
next(error);
}
};
// @desc Remove item from cart
// @route DELETE /api/cart/items/:productId
// @access Private
exports.removeFromCart = async (req, res, next) => {
try {
const { productId } = req.params;
const cart = await Cart.findOne({ user: req.user.id });
if (!cart) {
return res.status(404).json({
success: false,
error: 'Cart not found'
});
}
cart.items = cart.items.filter(
item => item.product.toString() !== productId
);
await cart.save();
await cart.populate('items.product', 'name price images stock');
res.status(200).json({
success: true,
data: cart
});
} catch (error) {
next(error);
}
};
// @desc Clear cart
// @route DELETE /api/cart
// @access Private
exports.clearCart = async (req, res, next) => {
try {
const cart = await Cart.findOne({ user: req.user.id });
if (!cart) {
return res.status(404).json({
success: false,
error: 'Cart not found'
});
}
cart.items = [];
await cart.save();
res.status(200).json({
success: true,
data: cart
});
} catch (error) {
next(error);
}
};Project 3: Social Media API
একটি social media API যেখানে posts, comments, likes, এবং follow system থাকবে।
Key Models:
models/SocialPost.js:
const mongoose = require('mongoose');
const commentSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
text: {
type: String,
required: [true, 'Comment text is required'],
maxlength: [500, 'Comment cannot exceed 500 characters']
},
createdAt: {
type: Date,
default: Date.now
}
});
const socialPostSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
text: {
type: String,
required: [true, 'Post text is required'],
maxlength: [1000, 'Post cannot exceed 1000 characters']
},
image: {
url: String,
alt: String
},
likes: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}],
comments: [commentSchema],
tags: [String],
visibility: {
type: String,
enum: ['public', 'friends', 'private'],
default: 'public'
},
createdAt: {
type: Date,
default: Date.now
}
});
// Virtual for like count
socialPostSchema.virtual('likeCount').get(function() {
return this.likes.length;
});
// Virtual for comment count
socialPostSchema.virtual('commentCount').get(function() {
return this.comments.length;
});
module.exports = mongoose.model('SocialPost', socialPostSchema);models/Follow.js:
const mongoose = require('mongoose');
const followSchema = new mongoose.Schema({
follower: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
following: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
createdAt: {
type: Date,
default: Date.now
}
});
// Prevent duplicate follows
followSchema.index({ follower: 1, following: 1 }, { unique: true });
module.exports = mongoose.model('Follow', followSchema);Project 4: Task Management System
একটি complete task management system যেখানে projects, tasks, এবং team collaboration থাকবে।
Key Features:
- Projects এবং tasks
- Task assignment
- Due dates এবং priorities
- Status tracking
- Comments এবং attachments
models/Task.js:
const mongoose = require('mongoose');
const taskSchema = new mongoose.Schema({
title: {
type: String,
required: [true, 'Task title is required'],
trim: true
},
description: {
type: String
},
project: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Project',
required: true
},
assignedTo: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
status: {
type: String,
enum: ['todo', 'in-progress', 'review', 'completed'],
default: 'todo'
},
priority: {
type: String,
enum: ['low', 'medium', 'high', 'urgent'],
default: 'medium'
},
dueDate: {
type: Date
},
tags: [String],
attachments: [{
filename: String,
url: String,
uploadedAt: Date
}],
completedAt: Date,
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
});
// Update completedAt when status changes to completed
taskSchema.pre('save', function(next) {
if (this.isModified('status') && this.status === 'completed') {
this.completedAt = Date.now();
}
this.updatedAt = Date.now();
next();
});
module.exports = mongoose.model('Task', taskSchema);Project 5: User Management System with Roles
একটি advanced user management system যেখানে roles, permissions, এবং access control থাকবে।
RBAC Implementation:
models/Role.js:
const mongoose = require('mongoose');
const roleSchema = new mongoose.Schema({
name: {
type: String,
required: true,
unique: true,
enum: ['super-admin', 'admin', 'manager', 'user', 'guest']
},
permissions: [{
resource: {
type: String,
required: true
},
actions: [{
type: String,
enum: ['create', 'read', 'update', 'delete', 'manage']
}]
}],
description: String,
createdAt: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Role', roleSchema);middleware/permission.js:
const checkPermission = (resource, action) => {
return async (req, res, next) => {
try {
const userRole = await Role.findOne({ name: req.user.role });
if (!userRole) {
return res.status(403).json({
success: false,
error: 'Role not found'
});
}
const permission = userRole.permissions.find(
p => p.resource === resource
);
if (!permission || !permission.actions.includes(action)) {
return res.status(403).json({
success: false,
error: 'You do not have permission to perform this action'
});
}
next();
} catch (error) {
next(error);
}
};
};
module.exports = checkPermission;Project 6: File Upload Service
একটি complete file upload এবং management service।
Features:
- Multiple file uploads
- Image processing
- Cloud storage (AWS S3)
- File categorization
- File sharing
Complete implementation: (File Handling গাইডে বিস্তারিত আছে)
const express = require('express');
const multer = require('multer');
const sharp = require('sharp');
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const path = require('path');
const { v4: uuidv4 } = require('uuid');
const router = express.Router();
// Multer configuration
const storage = multer.memoryStorage();
const upload = multer({
storage,
limits: { fileSize: 10 * 1024 * 1024 }, // 10MB
fileFilter: (req, file, cb) => {
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Invalid file type'));
}
}
});
// S3 Client
const s3Client = new S3Client({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY,
secretAccessKey: process.env.AWS_SECRET_KEY
}
});
// Upload endpoint
router.post('/upload', upload.single('file'), async (req, res) => {
try {
// Process image
const processedImage = await sharp(req.file.buffer)
.resize(1200, 1200, { fit: 'inside' })
.webp({ quality: 85 })
.toBuffer();
// Generate filename
const filename = `${uuidv4()}.webp`;
// Upload to S3
await s3Client.send(new PutObjectCommand({
Bucket: process.env.AWS_BUCKET,
Key: `uploads/${filename}`,
Body: processedImage,
ContentType: 'image/webp'
}));
// Save to database
const file = await File.create({
filename,
originalName: req.file.originalname,
size: processedImage.length,
mimetype: 'image/webp',
url: `https://${process.env.AWS_BUCKET}.s3.amazonaws.com/uploads/${filename}`,
user: req.user.id
});
res.json({
success: true,
data: file
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
});
module.exports = router;Common Features Across All Projects
1. Input Validation
const { body, validationResult } = require('express-validator');
const validatePost = [
body('title')
.trim()
.isLength({ min: 3, max: 200 })
.withMessage('Title must be between 3-200 characters'),
body('content')
.notEmpty()
.withMessage('Content is required'),
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}
next();
}
];
router.post('/posts', protect, validatePost, createPost);2. Rate Limiting
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app.use('/api/', limiter);3. API Documentation (Swagger)
const swaggerJsDoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const swaggerOptions = {
definition: {
openapi: '3.0.0',
info: {
title: 'Blog API',
version: '1.0.0',
description: 'Blog API Documentation'
},
servers: [
{ url: 'http://localhost:5000' }
]
},
apis: ['./src/routes/*.js']
};
const swaggerDocs = swaggerJsDoc(swaggerOptions);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocs));Testing
Unit Testing with Jest
// __tests__/auth.test.js
const request = require('supertest');
const app = require('../src/app');
const User = require('../src/models/User');
describe('Auth API', () => {
beforeEach(async () => {
await User.deleteMany({});
});
describe('POST /api/auth/register', () => {
it('should register a new user', async () => {
const res = await request(app)
.post('/api/auth/register')
.send({
name: 'Test User',
email: 'test@example.com',
password: 'password123'
});
expect(res.statusCode).toBe(201);
expect(res.body.success).toBe(true);
expect(res.body.data).toHaveProperty('token');
});
it('should not register with invalid email', async () => {
const res = await request(app)
.post('/api/auth/register')
.send({
name: 'Test User',
email: 'invalid-email',
password: 'password123'
});
expect(res.statusCode).toBe(400);
});
});
});Deployment Checklist
Environment Variables
# Production .env
NODE_ENV=production
PORT=5000
MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/dbname
JWT_SECRET=very-secret-key-change-this
JWT_EXPIRE=7d
AWS_ACCESS_KEY=your-access-key
AWS_SECRET_KEY=your-secret-key
AWS_REGION=us-east-1
AWS_BUCKET=your-bucket-nameSecurity Headers
const helmet = require('helmet');
const mongoSanitize = require('express-mongo-sanitize');
const xss = require('xss-clean');
const hpp = require('hpp');
app.use(helmet());
app.use(mongoSanitize());
app.use(xss());
app.use(hpp());Production Best Practices
- ✅ Use environment variables
- ✅ Enable CORS properly
- ✅ Add rate limiting
- ✅ Implement logging
- ✅ Use HTTPS
- ✅ Add input validation
- ✅ Sanitize data
- ✅ Use compression
- ✅ Add proper error handling
- ✅ Write tests
এই ৬টি project implement করে আপনি intermediate-level Node.js developer হিসেবে নিজেকে প্রতিষ্ঠিত করতে পারবেন। প্রতিটি project এ real-world scenarios এবং best practices অনুসরণ করা হয়েছে! 🚀