In 2025, building fullstack applications is easier, faster, and more scalable than ever. Thanks to the powerful trio of Node.js, Express.js, and MongoDB, you can build modern web applications with speed and efficiency.
Whether you're creating a blog, SaaS tool, or e-commerce store, this tech stack provides the flexibility and power you need.
For this tutorial, we’ll build a simple "Task Manager" app where users can:
Register/Login
Create, Read, Update, and Delete (CRUD) tasks
View their tasks on a dashboard
Node.js – JavaScript runtime for backend
Express.js – Lightweight web framework for Node.js
MongoDB – NoSQL database
Mongoose – ODM for MongoDB
JWT – For authentication
Tailwind CSS or basic HTML for the frontend
Postman – For testing APIs
Vite/React (optional) – If you want a modern frontend
task-manager/
β
βββ backend/
β βββ config/
β βββ controllers/
β βββ models/
β βββ routes/
β βββ middleware/
β βββ server.js
β βββ .env
β
βββ frontend/ (optional React/Vite setup)
β
βββ README.md
mkdir task-manager
cd task-manager
mkdir backend && cd backend
npm init -y
Install dependencies:
npm install express mongoose dotenv cors jsonwebtoken bcryptjs
npm install --save-dev nodemon
Add this to package.json
for hot reloading:
"scripts": {
"dev": "nodemon server.js"
}
backend/server.js
const express = require('express');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
const cors = require('cors');
dotenv.config();
const app = express();
app.use(cors());
app.use(express.json());
// Routes
app.use('/api/auth', require('./routes/authRoutes'));
app.use('/api/tasks', require('./routes/taskRoutes'));
// MongoDB Connection
mongoose.connect(process.env.MONGO_URI)
.then(() => console.log('MongoDB Connected'))
.catch((err) => console.error(err));
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
models/User.js
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, unique: true },
password: { type: String, required: true }
});
// Encrypt password before saving
userSchema.pre('save', async function () {
if (!this.isModified('password')) return;
this.password = await bcrypt.hash(this.password, 12);
});
module.exports = mongoose.model('User', userSchema);
models/Task.js
const mongoose = require('mongoose');
const taskSchema = new mongoose.Schema({
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
title: String,
completed: { type: Boolean, default: false },
}, { timestamps: true });
module.exports = mongoose.model('Task', taskSchema);
routes/authRoutes.js
const express = require('express');
const { register, login } = require('../controllers/authController');
const router = express.Router();
router.post('/register', register);
router.post('/login', login);
module.exports = router;
controllers/authController.js
const User = require('../models/User');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const generateToken = (id) => jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: '7d' });
exports.register = async (req, res) => {
const { name, email, password } = req.body;
const userExists = await User.findOne({ email });
if (userExists) return res.status(400).json({ msg: 'User already exists' });
const user = await User.create({ name, email, password });
const token = generateToken(user._id);
res.status(201).json({ token, user: { id: user._id, name: user.name } });
};
exports.login = async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !(await bcrypt.compare(password, user.password))) {
return res.status(401).json({ msg: 'Invalid credentials' });
}
const token = generateToken(user._id);
res.json({ token, user: { id: user._id, name: user.name } });
};
middleware/authMiddleware.js
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const protect = async (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ msg: 'Not authorized' });
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = await User.findById(decoded.id).select('-password');
next();
} catch {
res.status(401).json({ msg: 'Token failed' });
}
};
module.exports = protect;
routes/taskRoutes.js
const express = require('express');
const protect = require('../middleware/authMiddleware');
const { getTasks, addTask, updateTask, deleteTask } = require('../controllers/taskController');
const router = express.Router();
router.route('/').get(protect, getTasks).post(protect, addTask);
router.route('/:id').put(protect, updateTask).delete(protect, deleteTask);
module.exports = router;
controllers/taskController.js
const Task = require('../models/Task');
exports.getTasks = async (req, res) => {
const tasks = await Task.find({ user: req.user._id });
res.json(tasks);
};
exports.addTask = async (req, res) => {
const task = await Task.create({ ...req.body, user: req.user._id });
res.status(201).json(task);
};
exports.updateTask = async (req, res) => {
const task = await Task.findById(req.params.id);
if (task.user.toString() !== req.user._id.toString()) return res.status(401).send("Unauthorized");
const updated = await Task.findByIdAndUpdate(req.params.id, req.body, { new: true });
res.json(updated);
};
exports.deleteTask = async (req, res) => {
const task = await Task.findById(req.params.id);
if (task.user.toString() !== req.user._id.toString()) return res.status(401).send("Unauthorized");
await task.deleteOne();
res.json({ msg: 'Task removed' });
};
Use Postman to:
Register a new user (POST /api/auth/register
)
Login (POST /api/auth/login
)
Get JWT and use it in Headers as:Authorization: Bearer <token>
Create, fetch, update, and delete tasks via respective endpoints
You can use React + Vite or any frontend stack:
Fetch tasks from /api/tasks
Use Axios or Fetch API to handle login/register
Store token in localStorage
or Cookies
Protect frontend routes using the token
Make sure to set environment variables like MONGO_URI
and JWT_SECRET
.
In 2025, building fullstack applications with Node.js, Express, and MongoDB is still an excellent and in-demand approach. With rapid development, RESTful APIs, and easy deployment options, this stack empowers you to build scalable, real-world apps quickly.