Using JWT for Authentication in a MERN Backend

What is JWT and Why Use It?
JWT (JSON Web Token) is an open standard used to securely transmit information between parties as a JSON object. It is commonly used for authentication and authorization.
Authentication: Verifies who the user is.
Authorization: Verifies what the user has access to.
In a MERN stack (MongoDB, Express.js, React, Node.js), JWT is typically used to:
Log in the user and issue a token.
Store the token on the client (usually in localStorage or cookies).
Send the token in headers for protected API routes.
Validate the token on the server to allow/deny access.
Project Structure
backend/
│
├── config/
| └── db.js
├── controllers/
│ └── user.controller.js
├── middleware/
│ └── auth.middleware.js
├── models/
│ └── user.model.js
├── routes/
│ └── user.routes.js
├── index.js
└── .env
Setup & Dependencies
Install the necessary packages:
npm install express mongoose dotenv jsonwebtoken bcryptjs
.env
PORT=5000
MONGO_URI=your_mongodb_url
JWT_SECRET=your_jwt_secret_key
config/db.js
const mongoose = require('mongoose');
require('dotenv').config();
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI);
console.log('MongoDB Connected');
} catch (error) {
console.error('MongoDB connection failed:', error.message);
process.exit(1);
}
};
module.exports = connectDB;
models/user.model.js
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
mobile: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
}, { timestamps: true });
module.exports = mongoose.model('User', userSchema);
middleware/auth.middleware.js
const jwt = require('jsonwebtoken');
require('dotenv').config();
const protect = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({ message: 'Not authorized, token missing' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded; // Contains userId and email from payload
next();
} catch (error) {
return res.status(401).json({ message: 'Invalid token' });
}
};
module.exports = protect;
controllers/user.controller.js
const User = require('../models/user.model');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
require('dotenv').config();
// Generate JWT Token
const generateToken = (user) => {
return jwt.sign(
{ userId: user._id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: '1d' }
);
};
// @route POST /api/users/register
const registerUser = async (req, res) => {
const { name, mobile, email, password } = req.body;
try {
const existingUser = await User.findOne({ email });
if (existingUser) return res.status(400).json({ message: 'User already exists' });
const hashedPassword = await bcrypt.hash(password, 10);
const user = await User.create({ name, mobile, email, password: hashedPassword });
const token = generateToken(user);
res.status(201).json({ userId: user._id, email: user.email, token });
} catch (error) {
res.status(500).json({ message: 'Server error' });
}
};
// @route POST /api/users/login
const loginUser = async (req, res) => {
const { email, password } = req.body;
try {
const user = await User.findOne({ email });
if (!user) return res.status(400).json({ message: 'Invalid credentials' });
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) return res.status(400).json({ message: 'Invalid credentials' });
const token = generateToken(user);
res.status(200).json({ userId: user._id, email: user.email, token });
} catch (error) {
res.status(500).json({ message: 'Server error' });
}
};
// @route GET /api/users/profile
const getUserProfile = async (req, res) => {
try {
const user = await User.findById(req.user.userId).select('-password');
res.json(user);
} catch (error) {
res.status(500).json({ message: 'Failed to retrieve profile' });
}
};
module.exports = {
registerUser,
loginUser,
getUserProfile,
};
routes/user.routes.js
const express = require('express');
const { registerUser, loginUser, getUserProfile } = require('../controllers/user.controller');
const protect = require('../middleware/auth.middleware');
const router = express.Router();
router.post('/register', registerUser);
router.post('/login', loginUser);
router.get('/profile', protect, getUserProfile);
module.exports = router;
index.js
const express = require('express');
const dotenv = require('dotenv');
const connectDB = require('./config/db');
const userRoutes = require('./routes/user.routes');
dotenv.config();
connectDB();
const app = express();
app.use(express.json());
app.use('/api/users', userRoutes);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Testing the Flow
Use Postman or Insomnia:
POST /api/users/register
→ Register new user.POST /api/users/login
→ Get JWT token.GET /api/users/profile
→ UseAuthorization: Bearer <token>
header.
JWT provides a secure and stateless way to authenticate users in your MERN backend. By separating concerns into models, controllers, routes, and middleware, your project remains clean, scalable, and easy to maintain.
Subscribe to my newsletter
Read articles from Surajit Das directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
