Stop Storing Passwords Like It's 1999: The Node.js + MySQL Reality Check
Source: Dev.to

The Horror You’re Creating
You just built your first Node.js auth system. Feeling brilliant. Then you write THIS:
app.post('/register', (req, res) => {
const { username, password } = req.body;
db.query('INSERT INTO users (username, password) VALUES (?, ?)',
[username, password]); // 🚨 DISASTER
});
Congrats! When your database leaks (when, not if), every password sits there like free samples at Costco. People reuse passwords everywhere. That "fluffy2023"? You just gave hackers access to Sarah’s email, banking, and Instagram.
Solution: BCrypt Saves Everything
BCrypt blends passwords beyond recognition with no “undo” button. Hackers see gibberish like $2b$10$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW.
const bcrypt = require('bcrypt');
app.post('/register', async (req, res) => {
const { username, password } = req.body;
const hashedPassword = await bcrypt.hash(password, 10);
db.query('INSERT INTO users (username, password) VALUES (?, ?)',
[username, hashedPassword]);
});
The 10 is salt rounds—how many scramble cycles. Ten is a good default for 2025.
Mistake #2: Comparing Wrong
You hashed passwords? Don’t ruin it with this:
// WRONG - Never works
if (results[0].password === password) {
res.send('Logged in!');
}
You can’t compare a hashed value with plain text using ===. The fix:
app.post('/login', async (req, res) => {
const { username, password } = req.body;
db.query('SELECT * FROM users WHERE username = ?', [username],
async (err, results) => {
const match = await bcrypt.compare(password, results[0].password);
if (match) {
res.send('Welcome!');
} else {
res.send('Try again');
}
});
});
bcrypt.compare() handles verification securely.
Mistake #3: No Validation
Accepting any password is suicide. "1"? Empty string? Sure!
function isPasswordValid(password) {
const regex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/;
return regex.test(password);
}
app.post('/register', async (req, res) => {
if (!isPasswordValid(req.body.password)) {
return res.status(400).send('Need 8+ chars, letters, numbers & symbols');
}
// continue with hashing...
});
Mistake #4: SQL Injection
Building queries with template strings invites disaster:
// NEVER
const query = `SELECT * FROM users WHERE username = '${username}'`;
An input like admin'-- can bypass authentication. Use parameterized queries always:
db.query('SELECT * FROM users WHERE username = ?', [username]);
The ? placeholders treat input as data, not code.
Complete Secure Example
const express = require('express');
const bcrypt = require('bcrypt');
const mysql = require('mysql2/promise');
const app = express();
app.use(express.json());
async function initDb() {
return mysql.createPool({
host: 'localhost',
user: 'root',
password: 'your_password',
database: 'your_db',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
}
const db = await initDb();
function isPasswordValid(password) {
const regex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/;
return regex.test(password);
}
app.post('/register', async (req, res) => {
const { username, password } = req.body;
if (!isPasswordValid(password)) {
return res.status(400).json({ error: 'Weak password!' });
}
const hashedPassword = await bcrypt.hash(password, 10);
await db.query('INSERT INTO users (username, password) VALUES (?, ?)',
[username, hashedPassword]);
res.json({ message: 'Account created!' });
});
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const [results] = await db.query('SELECT * FROM users WHERE username = ?',
[username]);
if (!results.length) {
return res.status(401).send('Invalid credentials');
}
const match = await bcrypt.compare(password, results[0].password);
if (match) {
res.json({ message: 'Login successful!' });
} else {
res.status(401).send('Invalid credentials');
}
});
app.listen(3000, () => console.log('Server running on port 3000'));
Wake Up Call
It’s 2025. AI writes code and cars drive themselves. Zero excuse for MySpace‑era password storage.
- Hash passwords with BCrypt (or Argon2).
- Validate input strength.
- Use parameterized queries to prevent SQL injection.
- Never store plain‑text passwords.
Your users trust you—don’t become tomorrow’s breach headline. Secure those passwords before the internet finds you.