Creating Login Endpoint & Login Existing User in CloudNoteBook ๐๐ค
So far in CloudNoteBook, we've built a Sign Up endpoint that creates a new user and
stores their hashed password in MongoDB. But a real app also needs a way for
existing users to log back in. That means checking their email, comparing their
password securely, and issuing them a fresh JWT (JSON Web Token) to prove they're
authenticated on future requests.
In this tutorial, we will learn:
express-validatorbcrypt.compareWhat Does a Login Endpoint Do?
A login endpoint takes a user's email and password, checks them against what's stored in the database, and โ if they match โ returns a JWT auth token. This token is sent back by the frontend on every future request (like fetching or adding notes) so our server knows who is making the request, without asking the user to log in again and again.
---Step 1: Import What We Need in routes/auth.js
Open routes/auth.js โ the same file where we created the Sign Up route โ and make
sure these packages are imported at the top.
const express = require("express");
const router = express.Router();
const User = require("../models/User");
const { body, validationResult } = require("express-validator");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const JWT_SECRET = "CloudNoteBook$ecretKey";
Step 2: Define the Login Route with Validation
We create a new route: POST /api/auth/login. We validate that the email is a
valid email address and the password is not empty, using express-validator.
// ROUTE 2: Login an existing user using: POST "/api/auth/login" โ No login required
router.post(
"/login",
[
body("email", "Enter a valid email").isEmail(),
body("password", "Password cannot be blank").exists(),
],
async (req, res) => {
// We will fill this in the next step
}
);
Step 3: Check Validation Errors and Find the User
Inside the route handler, first check if there are any validation errors. Then look up the
user in MongoDB by their email. If no such user exists, we return an error โ
without revealing whether it was the email or password that was wrong (this is good security practice).
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { email, password } = req.body;
try {
let user = await User.findOne({ email });
if (!user) {
return res
.status(400)
.json({ error: "Please try to login with correct credentials" });
}
} catch (error) {
console.error(error.message);
res.status(500).send("Internal Server Error");
}
Step 4: Compare the Password with bcrypt
Since we never store plain-text passwords, we can't check password === user.password
directly. Instead, we use bcrypt.compare, which hashes the entered password the same
way and checks it against the stored hash.
const passwordCompare = await bcrypt.compare(password, user.password);
if (!passwordCompare) {
return res
.status(400)
.json({ error: "Please try to login with correct credentials" });
}
Step 5: Generate and Return the JWT Auth Token
Once the password matches, we sign a JWT containing the user's MongoDB id and send
it back as authtoken. The frontend will store this token and attach it to future
requests to prove the user is logged in.
const data = {
user: {
id: user.id,
},
};
const authtoken = jwt.sign(data, JWT_SECRET);
res.json({ success: true, authtoken });
Step 6: Full Updated Login Route
Here is the complete /login route combined into one block, ready to sit alongside
your existing /createuser route in routes/auth.js.
const express = require("express");
const router = express.Router();
const User = require("../models/User");
const { body, validationResult } = require("express-validator");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const JWT_SECRET = "CloudNoteBook$ecretKey";
// ROUTE 2: Login an existing user using: POST "/api/auth/login" โ No login required
router.post(
"/login",
[
body("email", "Enter a valid email").isEmail(),
body("password", "Password cannot be blank").exists(),
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { email, password } = req.body;
try {
let user = await User.findOne({ email });
if (!user) {
return res
.status(400)
.json({ error: "Please try to login with correct credentials" });
}
const passwordCompare = await bcrypt.compare(password, user.password);
if (!passwordCompare) {
return res
.status(400)
.json({ error: "Please try to login with correct credentials" });
}
const data = {
user: {
id: user.id,
},
};
const authtoken = jwt.sign(data, JWT_SECRET);
res.json({ success: true, authtoken });
} catch (error) {
console.error(error.message);
res.status(500).send("Internal Server Error");
}
}
);
module.exports = router;
Testing the Endpoint in Postman
Send a POST request to http://localhost:5000/api/auth/login with a JSON
body containing email and password. On success, you'll get back an
authtoken โ save this, we'll use it in the next tutorial to access protected routes
like fetching a user's notes.
| Scenario | Response | Status Code |
|---|---|---|
| Valid email & correct password | { success: true, authtoken } |
200 |
| Email not registered | { error: "Please try to login with correct credentials" } |
400 |
| Wrong password for existing email | { error: "Please try to login with correct credentials" } |
400 |
| Missing/invalid email or password field | { errors: [...] } from express-validator |
400 |
Features and Learnings:-
express-validator.User.findOne({ email }).bcrypt.compare.authtoken on successful login using jsonwebtoken./api/auth/login endpoint in Postman and reviewed all response scenarios.