5. AUTHENTICATION , AUTHORIZAION , Pagination - Node.js + Express + MongoDB
Folder Structure (MVC Pattern)
project/
│── config/
│ └── db.js # MongoDB connection
│
│── controllers/
│ └── userController.js # Request handling logic
│
│── models/
│ └── User.js # Mongoose schema
│
│── routes/
│ └── userRoutes.js # Routes for user APIs
│
│── middleware/
│ └── errorMiddleware.js # Error handling middleware
│
│── server.js # Main entry point
│── package.json
│── .env # Environment variables
Install Dependecnies
npm init -y
npm install express mongoose dotenv nodemon bcryptjs
Connect MongoDB
const mongoose = require('mongoose');
require('dotenv').config();
const dbConnect = ()=>{
mongoose.connect(process.env.DATABASE_URL)
.then(()=>{console.log("DB CONNECTION SUCESSFULL")})
.catch((err)=>{
console.log("Failed to connect DB")
console.log(err);
process.exit(1);
})
}
module.exports = dbConnect;
Create Model
const mongoose = require('mongoose') const userSchema = new mongoose.Schema({ name:{ type:String, required:true }, email:{ type:String, required:true }, password:{ type:String, required:true }, accountType:{ // for authorization type:String, enum:["Admin" , "Student" , "Instructor"], required:true }, }, { timestamps: true }) module.exports = mongoose.model("User" , userSchema)Controller (Business Logic)
//signup
exports.signup = async(req,res)=>{
try{
//Step 1 : fetch data (also fetch otp we provide it in input box once all credetial filled)
const{name , email , password ,accountType} = req.body;
//Step 2: Check validations
//(i) check all filed filled or nor
if(!name || !password || !email || !accountType){
return res.status(401).json({
success:false,
message:"All fields required"
})
}
//(ii) check password and confirm password are same or not
//(iii) check user already exist or not -> alreadyexist Please login
const existingUser = await User.findOne({email});
if(existingUser){
return res.status(401).json({
success:true,
message:"User already exist! Please Login"
})
}
//Step 4: Hash the password -> npm i bcryptjs
const hasedPassword = await bcrypt.hash(password , 10);
//Task 2.1
const profile = await Profile.create({
gender:null,
dateOfBirth:null,
about:null,
contactNumber:null
})
//Step 5: create entry in DB
const user = await User.create({
name ,
email,
password:hasedPassword,
accountType ,
})
//Step 6: return success response
return res.status(200).json({
success:true,
message: "User Ragistered Successfully",
user
})
}
catch(err){
console.log(err);
return res.status(200).json({
success:false,
message:'User can not be ragistered ! Please try again',
error:err.message
})
}
}
//login API
exports.login = async(req,res)=>{
try{
//Step 1. fetch data from req.body
const{email , password} = req.body;
//Step2. Check Validation
//(i) check all fields are filled or not
if(!email || !password){
return res.status(401).json({
success:false,
message:"All fields are required"
})
}
//(ii) check user exist or not
const user = await User.findOne({email}) //this user contain all info of user
if(!user){
return res.status(401).json({
success:false,
message:"User not found! Please signup"
})
}
////(iii) check entred passwrod and actual password matched or not if matchedd then create token
if(await bcrypt.compare(password , user.password)){
//create token
const payload={
email:user.email,
id:user._id,
accountType:user.accountType
} //token can hold this payload
const token = jwt.sign(payload ,process.env.JWT_SECRET, {expiresIn:"2h"}); // npm i jsonwebtoken
const userObj = user.toObject(); //user.toObject() converts the Mongoose document into a plain JavaScript object that can be safely modified.
userObj.token = token
userObj.password = undefined
return res.status(200).json({
success:true,
message:"Login Sucessfully",
userObj
})
}
else{
return res.status(401).json({
success:false,
message:'Password is incorrect'
})
}
}
catch(err){
console.log(err);
return res.status(500).json({
sucess:false,
message:"Internal Server Error in login"
})
}
}
Routes
const express = require('express')
const router = express.Router();
const{sendOTP,signup,login, getUserDetails } = require('../controllers/Auth')
const {forgotPasswordToken} = require('../controllers/ForgotPassword');
const { auth } = require('../middlewares/auth');
router.post('/signup' , signup);
router.post('/login', login)
module.exports = router
MiddleWare - Auth
const jwt = require('jsonwebtoken')
require('dotenv').config()
exports.auth = async(req,res , next)=>{
try{
// Extracting JWT from request cookies, body or header
const token = req?.body.token || req.header("Authorization")?.replace("Bearer ", "");//replace our Bearer + space with ""
// If JWT is missing, return 401 Unauthorized response
if(!token){
return res.status(401).json({
sucess:false,
message:"Token missing"
})
}
try{
// Verifying the JWT using the secret key stored in environment variables
const decode = await jwt.verify(token , process.env.JWT_SECRET);
req.user = decode;
}
catch(err){
return res.status(401).json({
sucess:false,
message:"Token is invalid"
})
}
next();
}
catch(err){
console.log(err)
return res.status(500).json({
success:false,
message:"Internal Server Error in auth"
})
}
}
PAGINATION
Pagination = dividing a large dataset into smaller “pages” so that clients don’t have to fetch everything at once.
Example: You have 10,000 users in DB → Instead of sending all at once, you send 10 users per request.
Page 1 → Users 1–10
Page 2 → Users 11–20
Page 3 → Users 21–30
KEY PARAMETERS:
We usually use two values:
page→ which page the user wants (1, 2, 3…)limit→ how many items per pageFrom these, we calculate:
skip = (page - 1) * limit
const express = require('express');
const mongoose = require('mongoose');
const app = express();
// Example User Schema
const UserSchema = new mongoose.Schema({
name: String,
email: String,
createdAt: { type: Date, default: Date.now }
});
const User = mongoose.model('User', UserSchema);
// GET users with pagination + sorting
app.get('/users', async (req, res) => {
try {
let { page, limit, sortBy, order } = req.query; //passed in params
// default values
page = parseInt(page) || 1;
limit = parseInt(limit) || 10;
const skip = (page - 1) * limit; // calculate skip
// sorting logic
// default sort by createdAt descending (latest first)
const sortField = sortBy || "createdAt"; //sortField → field to sort by (e.g., name, email, createdAt)
const sortOrder = order === "asc" ? 1 : -1; // asc = 1, desc = -1
// query users
const users = await User.find()
.sort({ [sortField]: sortOrder })
.skip(skip)
.limit(limit);
// total documents
const total = await User.countDocuments();
res.json({
total,
page,
limit,
totalPages: Math.ceil(total / limit),
sortBy: sortField,
order: sortOrder === 1 ? "asc" : "desc",
data: users
});
} catch (err) {
res.status(500).json({ message: err.message });
}
});
app.listen(5000, () => console.log("Server running on port 5000"));
If you don’t use sorting → you might get users in a random order depending on how they were inserted.
If you use sorting → you can control the order (e.g., newest first, alphabetical order, etc.).
FrontEnd Logic
import React, { useEffect, useState } from "react";
import axios from "axios";
function UserTable() {
const [users, setUsers] = useState([]);
const [page, setPage] = useState(1);
const [limit] = useState(5); // fixed items per page
const [totalPages, setTotalPages] = useState(0);
const [sortBy, setSortBy] = useState("createdAt");
const [order, setOrder] = useState("desc");\
// Fetch users
useEffect(() => {
fetchUsers();
}, [page, sortBy, order]);
const fetchUsers = async () => {
const res = await axios.get("http://localhost:5000/users", {
params: { page, limit, sortBy, order }
});
setUsers(res.data.data);
setTotalPages(res.data.totalPages);
};
// Change sorting
const handleSort = (field) => {
if (sortBy === field) {
// toggle order asc <-> desc
setOrder(order === "asc" ? "desc" : "asc");
} else {
setSortBy(field);
setOrder("asc"); // default to ascending when switching field
}
};
return (
<div className="p-4">
<h2 className="text-xl font-bold mb-4">Users</h2>
{/* Table */}
<table className="border w-full">
<thead>
<tr>
<th className="p-2 cursor-pointer" onClick={() => handleSort("name")}>
Name {sortBy === "name" && (order === "asc" ? "▲" : "▼")}
</th>
<th className="p-2 cursor-pointer" onClick={() => handleSort("email")}>
Email {sortBy === "email" && (order === "asc" ? "▲" : "▼")}
</th>
<th className="p-2 cursor-pointer" onClick={() => handleSort("createdAt")}>
Created At {sortBy === "createdAt" && (order === "asc" ? "▲" : "▼")}
</th>
</tr>
</thead>
<tbody>
{users.map((u) => (
<tr key={u._id} className="border-t">
<td className="p-2">{u.name}</td>
<td className="p-2">{u.email}</td>
<td className="p-2">{new Date(u.createdAt).toLocaleDateString()}</td>
</tr>
))}
</tbody>
</table>
{/* Pagination controls */}
<div className="mt-4 flex gap-2">
<button
disabled={page === 1}
onClick={() => setPage(page - 1)}
className="px-3 py-1 border rounded disabled:opacity-50"
>
Prev
</button>
<span> Page {page} of {totalPages} </span>
<button
disabled={page === totalPages}
onClick={() => setPage(page + 1)}
className="px-3 py-1 border rounded disabled:opacity-50"
>
Next
</button>
</div>
</div>
);
}
export default UserTable;