DEV Community

Cover image for Stop Storing Passwords Like It's 1999: The Node.js + MySQL Reality Check
Vasu Ghanta
Vasu Ghanta

Posted on

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

If you're storing passwords in plain text in MySQL, we need to have an intervention. It's 2025. We have drones delivering tacos, but some devs treat passwords like grocery lists.

Opening a database and seeing "password123" just chilling there unprotected? That's leaving your door open with a neon "FREE STUFF" sign.

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
});
✨ อัปเดตล่าสุด: vasughanta/stop storing passwords like its the nodejs mysql reality check n - สรุปดราม่าล่าสุด ✨ อัปเดตล่าสุด: vasughanta/stop storing passwords like its the nodejs mysql reality check n - สรุปดราม่าล่าสุด

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. Her cat Fluffy is judging you.

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]);
});
✨ อัปเดตล่าสุด: vasughanta/stop storing passwords like its the nodejs mysql reality check n - สรุปดราม่าล่าสุด ✨ อัปเดตล่าสุด: vasughanta/stop storing passwords like its the nodejs mysql reality check n - สรุปดราม่าล่าสุด

The 10 is "salt rounds"—how many scramble cycles. Ten is perfect 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!');
}
✨ อัปเดตล่าสุด: vasughanta/stop storing passwords like its the nodejs mysql reality check n - สรุปดราม่าล่าสุด ✨ อัปเดตล่าสุด: vasughanta/stop storing passwords like its the nodejs mysql reality check n - สรุปดราม่าล่าสุด

You can't compare hashed with plain text using ===. That's asking if a smoothie equals an apple.

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');
      }
  });
});
✨ อัปเดตล่าสุด: vasughanta/stop storing passwords like its the nodejs mysql reality check n - สรุปดราม่าล่าสุด ✨ อัปเดตล่าสุด: vasughanta/stop storing passwords like its the nodejs mysql reality check n - สรุปดราม่าล่าสุด

BCrypt's compare() handles verification magically (it's math, but feels like magic).

Mistake #3: No Validation

Accepting ANY password is suicide. "1"? Welcome! 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...
});
✨ อัปเดตล่าสุด: vasughanta/stop storing passwords like its the nodejs mysql reality check n - สรุปดราม่าล่าสุด ✨ อัปเดตล่าสุด: vasughanta/stop storing passwords like its the nodejs mysql reality check n - สรุปดราม่าล่าสุด

Mistake #4: SQL Injection

Building queries with template strings? Asking for disaster:

// NEVER
const query = `SELECT * FROM users WHERE username = '${username}'`;
✨ อัปเดตล่าสุด: vasughanta/stop storing passwords like its the nodejs mysql reality check n - สรุปดราม่าล่าสุด ✨ อัปเดตล่าสุด: vasughanta/stop storing passwords like its the nodejs mysql reality check n - สรุปดราม่าล่าสุด

Type admin'-- and BAM—logged in as admin without a password.

Use parameterized queries ALWAYS:

db.query('SELECT * FROM users WHERE username = ?', [username]);
✨ อัปเดตล่าสุด: vasughanta/stop storing passwords like its the nodejs mysql reality check n - สรุปดราม่าล่าสุด ✨ อัปเดตล่าสุด: vasughanta/stop storing passwords like its the nodejs mysql reality check n - สรุปดราม่าล่าสุด

Those ? placeholders treat input as VALUES, 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());

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');
  }
});
✨ อัปเดตล่าสุด: vasughanta/stop storing passwords like its the nodejs mysql reality check n - สรุปดราม่าล่าสุด ✨ อัปเดตล่าสุด: vasughanta/stop storing passwords like its the nodejs mysql reality check n - สรุปดราม่าล่าสุด

Wake Up Call

It's 2025. AI writes code and cars drive themselves. Zero excuse for MySpace-era password storage.

Hash with BCrypt, validate inputs, use parameterized queries, never store plain text. Your users trust you—don't be tomorrow's breach headline.

Go secure those passwords before the internet finds you. And trust me, it will.

Top comments (2)

✨ อัปเดตล่าสุด: vasughanta/stop storing passwords like its the nodejs mysql reality check n - สรุปดราม่าล่าสุด
 
bhavin-allinonetools profile image
Bhavin Sheth •

This should honestly be required reading for anyone building auth for the first time.
The “when, not if” database leak line hits hard — and the bcrypt examples are super clear.
Plain-text passwords still showing up in 2025 is scary, but posts like this help fix that. Great write-up 👍

✨ อัปเดตล่าสุด: vasughanta/stop storing passwords like its the nodejs mysql reality check n - สรุปดราม่าล่าสุด
 
vasughanta09 profile image
Vasu Ghanta •

Thanks so much, Bhavin! 🙌 Really happy to hear the article came across as clear and useful — especially the “when, not if” bit about database leaks. That’s exactly the mindset shift I was hoping to spark: secure password handling isn’t optional anymore. Appreciate the encouragement and glad you found the bcrypt examples approachable! 🚀