🔹 BACKEND: authController.
js
✅ Signup (Register) Controller
js
CopyEdit
export const signup = async (req, res) => {
const { name, email, password, terms } = req.body;
🔍 Extracts data from the request body sent from frontend: name, email, password, and
optionally terms.
js
CopyEdit
try {
const existingUser = await User.findOne({ email });
✅ Checks if a user with the given email already exists in MongoDB using User.findOne().
js
CopyEdit
if (existingUser) {
return res.status(409).json({
success: false,
error: 'Email already in use'
});
❌ If email is found, it returns HTTP status 409 Conflict with a message.
js
CopyEdit
const user = await User.create({ name, email, password });
✅ If not found, it creates a new user using the User model. Password is hashed (inside User
model logic, usually via pre-save).
js
CopyEdit
res.status(201).json({
success: true,
token: generateToken(user._id),
user: {
_id: user._id,
name: user.name,
email: user.email,
isAdmin: user.isAdmin
});
🔐 Returns HTTP 201 Created response with:
o a JWT token (generated from user ID)
o user data (but not password)
js
CopyEdit
} catch (err) {
console.error(err);
res.status(500).json({
success: false,
error: 'Server error'
});
};
🛑 Catches any errors and returns 500 Internal Server Error.
✅ Login Controller
js
CopyEdit
export const login = async (req, res) => {
const { email, password } = req.body;
📥 Extracts email and password from request body.
js
CopyEdit
const user = await User.findOne({ email }).select('+password');
🔍 Finds user by email and includes password field (which is usually excluded in the schema for
security).
js
CopyEdit
if (!user || !(await user.matchPassword(password))) {
❌ If user is not found, or if the password doesn’t match (using method from schema), return 401
Unauthorized.
js
CopyEdit
return res.status(401).json({
success: false,
error: 'Invalid email or password'
});
🧠 Valid login goes here:
js
CopyEdit
res.status(200).json({
success: true,
token: generateToken(user._id),
user: {
_id: user._id,
name: user.name,
email: user.email,
isAdmin: user.isAdmin
}
});
🔐 Sends 200 OK with JWT token and user details.
🔹 FRONTEND: Register.js
js
CopyEdit
const [formData, setFormData] = useState({
name: '',
email: '',
password: '',
});
🎯 Tracks form values using state. Updated by user input.
js
CopyEdit
const handleChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
📝 Handles text input changes and updates state accordingly.
js
CopyEdit
const handleSubmit = async (e) => {
e.preventDefault();
🚫 Prevents page from refreshing on form submit.
js
CopyEdit
try {
await registerUser(formData);
alert('Registration successful!');
navigate('/login');
✅ Calls the registerUser service, which makes a POST request to backend. Then redirects to login
page.
js
CopyEdit
} catch (err) {
console.error(err);
alert(err.response?.data?.message || 'Registration failed');
};
⚠️Catches any errors and shows message.
🔹 FRONTEND: auth.js service file
js
CopyEdit
import api from './api';
📦 Uses pre-configured Axios instance (base URL is http://localhost:5000/api).
js
CopyEdit
export const registerUser = async (userData) => {
const res = await api.post('/auth/register', userData);
return res.data;
};
🔁 Makes POST request to /auth/register and sends user data.
js
CopyEdit
export const loginUser = async (userData) => {
const res = await api.post('/auth/login', userData);
return res.data;
};
🔁 Login request to /auth/login.
🔐 ProtectedRoute.js
js
CopyEdit
const token = localStorage.getItem('token');
if (!token) return <Navigate to="/login" />;
🚫 If no token is found, redirect to login.
🔹 RELATION BETWEEN COMPONENTS
Part Connected To
Register form ➡️registerUser() in auth.js
registerUser ➡️POST /api/auth/register ➡️signup controller
Login form ➡️loginUser() in auth.js
loginUser ➡️POST /api/auth/login ➡️login controller
After login ➡️Save token in localStorage
ProtectedRoute ➡️Reads token and shows protected Dashboard
✅ SUMMARY
🔄 Frontend sends POST to backend using Axios.
🔐 Backend handles validation, token generation, and sends a response.
🛡️ProtectedRoute ensures only logged-in users see certain pages.
🧠 MongoDB stores user securely with hashed passwords.
Let me know when you're ready — next, we can go through the task creation, update, delete, toggle
features step-by-step too.
Tools
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Great! Let's now dive deep into the full flow of signup (registration) and login functionality in
your Task Management System — covering both frontend and backend logic, step by step, with
highlighted code and in-depth explanation of how everything connects and works together.
✅ PART 1: SIGNUP / REGISTRATION FLOW
🧠 OVERVIEW:
Frontend sends a POST request with name, email, password to /api/auth/register
Backend receives and validates data, creates a new user, hashes password, saves it to
MongoDB
JWT Token is generated and sent back
Frontend shows success and navigates user to /login
🔹 FRONTEND CODE: Register.js
const [formData, setFormData] = useState({
name: '',
email: '',
password: '',
});
🧠 This creates a state object to hold the values from the form inputs (controlled inputs).
const handleChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
🧠 This function dynamically updates the formData state whenever the user types in an input.
e.target.name matches one of the keys in formData
It spreads the previous state and updates the one that changed.
const handleSubmit = async (e) => {
e.preventDefault();
try {
await registerUser(formData);
alert('Registration successful!');
navigate('/login');
} catch (err) {
alert(err.response?.data?.message || 'Registration failed');
}
};
🧠 When form is submitted:
registerUser(formData) sends a POST request to backend
If it succeeds: shows success alert and navigates to login
If it fails: shows error from backend
// services/auth.js
export const registerUser = async (userData) => {
const res = await api.post('/auth/register', userData);
return res.data;
};
🧠 api is an Axios instance with base URL: http://localhost:5000/api
🔹 BACKEND CODE: authRoutes.js
router.post('/register', signup);
🧠 Defines the API endpoint /api/auth/register which is handled by the signup function.
🔹 CONTROLLER: authController.js
export const signup = async (req, res) => {
const { name, email, password } = req.body;
🧠 Reads name, email, password from request body.
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(409).json({
success: false,
error: 'Email already in use'
});
}
🧠 Checks if email is already registered. If yes, sends 409 Conflict.
const user = await User.create({ name, email, password });
🧠 Creates new user and stores in MongoDB.
❗ Your Mongoose schema should hash the password before saving (using pre-save hook with
bcrypt).
res.status(201).json({
success: true,
token: generateToken(user._id),
user: {
_id: user._id,
name: user.name,
email: user.email,
isAdmin: user.isAdmin
}
});
🧠 On success:
Responds with 201 Created
Includes a JWT token and user info (except password)
🔐 generateToken.js
import jwt from 'jsonwebtoken';
const generateToken = (id) => {
return jwt.sign({ id }, process.env.JWT_SECRET, {
expiresIn: '30d',
});
};
export default generateToken;
🧠 Takes user ID, signs it using a secret key, and returns a token valid for 30 days.
🧱 DATABASE MODEL: User.js
userSchema.pre('save', async function (next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 10);
});
🧠 Automatically hashes the password before saving the user.
✅ PART 2: LOGIN FLOW
🧠 OVERVIEW:
User enters email + password
Frontend sends POST request to /api/auth/login
Backend checks if user exists and password is valid
Returns JWT token if correct
🔹 FRONTEND Login.js
const res = await loginUser(formData);
localStorage.setItem('token', res.token);
navigate('/dashboard');
🧠 On successful login, token is saved in localStorage and user is redirected to protected
/dashboard.
🔹 BACKEND authController.js
const user = await User.findOne({ email }).select('+password');
if (!user || !(await user.matchPassword(password))) {
return res.status(401).json({ error: 'Invalid email or password' });
}
🧠 Looks up user by email, ensures password matches using:
userSchema.methods.matchPassword = async function (enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password);
};
res.status(200).json({
success: true,
token: generateToken(user._id),
user: { ... }
});
🧠 Sends token back to frontend to be stored.
🔐 PROTECTED ROUTES (Frontend)
const token = localStorage.getItem('token');
if (!token) return <Navigate to="/login" replace />;
🧠 If user tries to visit /dashboard without login, they are redirected.
🔄 RELATIONSHIP FLOW
Form (React) → API Request (Axios) → Express Route → Controller → MongoDB
⬑ JWT Token returned ⬐
✅ You can now:
Check MongoDB using MongoDB Compass or CLI
See registered users and verify hashed passwords
Use Bearer token in headers for protected APIs
Would you like me to now explain:
How ProtectedRoute works in more depth?
How to check if a user is logged in?
How to logout / destroy token?
Or continue to task creation module?
Let me know and I’ll guide you step by step.