How to Hash Passwords in Node.js With bcrypt
Hashing passwords is non-negotiable. Here is how to do it with bcrypt in Node.js.
How to Hash Passwords in Node.js With bcrypt
Storing passwords in plain text is a disaster. Hashing is non-negotiable. Here is how to do it with bcrypt in Node.js.
Why Hash Passwords
If your database is leaked, plain-text passwords expose every user. People reuse passwords, so one leak breaks their other accounts too. Hashing turns passwords into irreversible strings. Even if the database is leaked, attackers cannot recover the passwords.
Why bcrypt
bcrypt is a password hashing function designed for passwords. It is slow on purpose (so brute-forcing hashes is expensive) and has a salt built in (so two users with the same password get different hashes).
Install
npm install bcrypt.
Hash a Password
const bcrypt = require('bcrypt');
const hashed = await bcrypt.hash(password, 10);
The second argument is the salt rounds. 10 is a good default. Higher is slower (and safer); lower is faster (and weaker). 10 takes about 100ms on a modern CPU.
Verify a Password
const match = await bcrypt.compare(password, user.passwordHash);
if (!match) return res.status(401).json({ error: 'Invalid credentials' });
bcrypt.compare handles the salt extraction. You do not need to store the salt separately.
Where to Hash
Hash in a pre('save') hook in Mongoose, or in the controller before calling User.create:
userSchema.pre('save', async function(next) {
if (!this.isModified('passwordHash')) return next();
this.passwordHash = await bcrypt.hash(this.passwordHash, 10);
next();
});
Hash only when the password has changed. Skip otherwise (profile updates should not rehash).
Handling Updates
For findByIdAndUpdate, hash in a pre('findOneAndUpdate') hook:
userSchema.pre('findOneAndUpdate', async function(next) {
const update = this.getUpdate();
if (update.passwordHash) {
update.passwordHash = await bcrypt.hash(update.passwordHash, 10);
}
next();
});
Mark the Field With select: false
passwordHash: { type: String, required: true, select: false }
Excluded from find queries by default. Use .select('+passwordHash') only when needed (e.g., for login).
Common Mistakes
- Hashing sync in request handlers: blocks the event loop. Use async APIs.
- Using a low salt rounds (e.g., 5): too fast to brute-force. Use 10+.
- Storing the salt separately: bcrypt stores it in the hash. You do not need a separate column.
- Logging passwords or hashes: never log either. Filter them from logs.
- Returning passwordHash in responses: use select: false and a serializer.
The Takeaway
Hash passwords with bcrypt using bcrypt.hash(password, 10) and verify with bcrypt.compare(password, hash). Use 10 salt rounds. Hash in a pre('save') hook. Mark the field with select: false. Never log passwords or hashes. Never return passwordHash in responses.
Use bcrypt. const hashed = await bcrypt.hash(password, 10). The second argument is salt rounds; 10 is a good default. Verify with bcrypt.compare(password, hash). Hash in a pre('save') Mongoose hook or in the controller.
bcrypt is designed for passwords. It is slow on purpose (so brute-forcing hashes is expensive) and has a salt built in (so two users with the same password get different hashes). It is the standard choice.
10 is a good default. It takes about 100ms on a modern CPU. Higher is slower (safer against brute force); lower is faster (weaker). For high-security apps, use 12. For most apps, 10 is fine.
No. bcrypt stores the salt inside the hash. You only store the hash. bcrypt.compare extracts the salt automatically when verifying.
To exclude it from find queries by default. Without this, User.find() returns passwordHash to the client. Use .select('+passwordHash') only when needed (e.g., for login).
Ready to master Node.js completely?
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.
Master Node.js
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course.

