Express: ro'yxatdan o'tish va parol hash
Sessiya darsida server foydalanuvchini eslab qolishni o’rgandi. Lekin hali eslab qoladigan odam yo’q: hisob yaratish imkoniyati yo’q.
Bu darsda foydalanuvchi ro’yxatdan o’tadi. Darsning markazida bitta qoida turadi va u boshqa hammasidan muhim: parolni hech qachon ochiq saqlamang.
Nega ochiq parol falokat
Tasavvur qiling, users jadvalida parollar shundoq yozilgan. Bazaga
kirish huquqi bo’lgan har kim ularni o’qiydi: siz, hamkasbingiz, zaxira
nusxani ko’rgan har kim va bazani sizib chiqargan hujumchi.
Zarar bitta sayt bilan tugamaydi. Ko’p odam bir xil parolni pochta, bank va ijtimoiy tarmoqda ishlatadi. Sizning bazangiz sizib chiqsa, ularning butun raqamli hayoti ochiladi.
Hash va salt
bcrypt ikkalasini ham o’zi bajaradi — salt yaratadi, hash qiladi va
ikkalasini bitta qatorga jamlaydi:
npm install bcrypt
const bcrypt = require("bcrypt");
const first = bcrypt.hashSync("parol12345", 10);
const second = bcrypt.hashSync("parol12345", 10);
console.log(first);
console.log(second);
console.log("Teng?", first === second);
console.log("To'g'ri parol:", bcrypt.compareSync("parol12345", first));
console.log("Xato parol:", bcrypt.compareSync("parol123", first));
node bcrypt-demo.js
$2b$10$B1IVEZQy6PW7xidn7SHdxu9EiugddTdZtKYPm46Wn5uE9WdNVVPeu
$2b$10$K4MIXOrKx.DviS9y7MkH9.slWzjmeMYPvrdXPytVNFU9vpCmsG/xe
Teng? false
To'g'ri parol: true
Xato parol: false
Ikki natija butunlay har xil — salt tasodifiy edi. Shunga qaramay
compareSync ikkalasida ham to’g’ri javob beradi, chunki salt hash
qatorining ichida saqlangan.
Qatorni qismlarga bo’lib qarang:
| Qism | Ma’nosi |
|---|---|
$2b$ |
Algoritm belgisi (bcrypt) |
10$ |
Murakkablik darajasi |
| Keyingi 22 belgi | Salt |
| Qolgani | Hash natijasi |
Butun qator 60 belgi — jadval ustunini shunga mo’ljallab olasiz.
users jadvali
Migratsiya darsida qurgan tizimga yangi fayl
qo’shamiz. migrations/003-create-users.js:
// migrations/003-create-users.js
module.exports = {
name: "003-create-users",
up(database) {
database.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
)
`);
},
};
npm run migrate
O'tkazib yuborildi: 001-create-tasks
O'tkazib yuborildi: 002-add-priority
Bajarildi: 003-create-users
Jami yangi migratsiya: 1
Ikki tafsilot ataylab tanlangan. Ustun nomi password emas,
password_hash — kod o’qigan har kim u yerda ochiq parol emasligini
darhol tushunadi. email ustunida esa UNIQUE cheklovi bor: baza
darajasida bir email ikki marta yozilmaydi.
Foydalanuvchi modeli
models/user.js:
// models/user.js
const bcrypt = require("bcrypt");
const database = require("../db");
const SALT_ROUNDS = 10;
// Javobga password_hash tushmasligi uchun alohida shakl
function toUser(row) {
return {
id: row.id,
email: row.email,
createdAt: row.created_at,
};
}
function findByEmail(email) {
const row = database
.prepare("SELECT * FROM users WHERE email = ?")
.get(email.toLowerCase());
return row || null;
}
async function create(email, password) {
const passwordHash = await bcrypt.hash(password, SALT_ROUNDS);
const result = database
.prepare("INSERT INTO users (email, password_hash) VALUES (?, ?)")
.run(email.toLowerCase(), passwordHash);
const row = database
.prepare("SELECT * FROM users WHERE id = ?")
.get(result.lastInsertRowid);
return toUser(row);
}
module.exports = { findByEmail, create, toUser };
Uch qaror izoh talab qiladi.
toUser password_hash ni tashlab ketadi. Hash — ichki ma’lumot,
JSON javobda unga o’rin yo’q. Tasodifan chiqib ketmasligi uchun uni
model darajasida kesib tashlash eng ishonchli yo’l.
email.toLowerCase() — email katta-kichik harfga sezgir emas.
Aziz@example.com va aziz@example.com bir xil odam. Saqlashda ham,
qidirishda ham kichik harfga keltiriladi.
findByEmail to’liq qatorni qaytaradi, toUser dan o’tkazmaydi —
keyingi darsda parolni solishtirish uchun
password_hash kerak bo’ladi.
Ro’yxatdan o’tish marshruti
routes/auth.js:
const express = require("express");
const User = require("../models/user");
const router = express.Router();
// Email va parol shaklini tekshiradi
function validateCredentials(body) {
const errors = [];
const email = typeof body.email === "string" ? body.email.trim() : "";
const password = typeof body.password === "string" ? body.password : "";
if (!email.includes("@") || email.length < 5) {
errors.push({ field: "email", message: "Email manzili noto'g'ri" });
}
if (password.length < 8) {
errors.push({ field: "password", message: "Parol kamida 8 belgi bo'lsin" });
}
return errors;
}
router.post("/register", async (request, response, next) => {
try {
const errors = validateCredentials(request.body);
if (errors.length > 0) {
return response.status(400).json({ errors: errors });
}
const email = request.body.email.trim();
// Bu email bilan hisob bormi
if (User.findByEmail(email)) {
return response.status(409).json({
errors: [
{ field: "email", message: "Bu email allaqachon ro'yxatdan o'tgan" },
],
});
}
const user = await User.create(email, request.body.password);
response.status(201).json(user);
} catch (error) {
next(error);
}
});
module.exports = router;
Tekshiruv usuli forma va validatsiya darsidagi
qolipda: xatolar massivga yig’iladi, har biri field va message bilan.
try/catch va next(error) bu yerda majburiy —
xatolarni boshqarish darsida ko’rilganidek,
Express 4 da async marshrutdagi xato o’z-o’zidan ushlanmaydi.
app.js ga ulaymiz:
const authRouter = require("./routes/auth");
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use("/api/tasks", tasksRouter);
app.use("/", authRouter);
Sinash
curl -i -X POST http://localhost:3000/register -H "Content-Type: application/json" -d '{"email":"nodira@example.com","password":"kuchli-parol-123"}'
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
{"id":2,"email":"nodira@example.com","createdAt":"2026-09-12 14:09:21"}
Javobda password_hash yo’q — toUser uni kesib tashladi.
Xuddi shu so’rovni qayta yuboring:
HTTP/1.1 409 Conflict
Content-Type: application/json; charset=utf-8
{"errors":[{"field":"email","message":"Bu email allaqachon ro'yxatdan o'tgan"}]}
Endi katta harflar bilan:
curl -X POST http://localhost:3000/register -H "Content-Type: application/json" -d '{"email":"NODIRA@Example.com","password":"kuchli-parol-123"}'
{"errors":[{"field":"email","message":"Bu email allaqachon ro'yxatdan o'tgan"}]}
toLowerCase ishladi: katta harf bilan yozilgan email yangi hisob
yaratmadi.
Noto’g’ri ma’lumot bilan:
curl -i -X POST http://localhost:3000/register -H "Content-Type: application/json" -d '{"email":"salom","password":"123"}'
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
{"errors":[{"field":"email","message":"Email manzili noto'g'ri"},{"field":"password","message":"Parol kamida 8 belgi bo'lsin"}]}
Ikkala xato bir yo’la qaytdi — foydalanuvchi ikki marta urinmaydi.
Ikki tekshiruv: kodda va bazada
Diqqat qilgan bo’lsangiz, takroriy email ikki joyda to’siladi: marshrutdagi
findByEmail va jadvaldagi UNIQUE cheklovi.
Takrorlanish emas, bu. Ikkisi har xil ish qiladi:
| To’siq | Nima beradi |
|---|---|
findByEmail tekshiruvi |
Foydalanuvchiga tushunarli xabar |
UNIQUE cheklovi |
Kafolat: baza hech qachon ikki xil yozuvga ruxsat bermaydi |
Kodli tekshiruv yolg’iz yetarli emas: ikki so’rov bir vaqtda kelsa,
ikkalasi ham “email bo’sh” deb topib, ikkalasi ham yozishga urinadi.
Baza cheklovi shu paytda ikkinchisini to’xtatadi — va u catch orqali
xato middleware’iga boradi.
Ro’yxatdan o’tish formasi
Brauzerdan sinash uchun sahifa. views/register.ejs:
<%- include("partials/header") %>
<h1>Ro'yxatdan o'tish</h1>
<form action="/register" method="POST">
<p>
<label for="email">Email:</label>
<input type="email" id="email" name="email" required />
</p>
<p>
<label for="password">Parol:</label>
<input type="password" id="password" name="password" minlength="8" required />
</p>
<button type="submit">Ro'yxatdan o'tish</button>
</form>
<%- include("partials/footer") %>
app.get("/register", (request, response) => {
response.render("register", { pageTitle: "Ro'yxatdan o'tish" });
});
type="password" matnni yulduzchalar bilan yashiradi. required va
minlength esa foydalanuvchiga qulaylik beradi — lekin
validatsiya darsidagi qoida kuchida
qoladi: brauzerdagi tekshiruv himoya emas, server tekshiruvi
majburiy.
Amaliy mashq
task-app loyihasida davom eting:
bcryptni o’rnatib, bir xil parolni ikki marta hash qiling va natijalar har xil chiqishini ko’ring;003-create-users.jsmigratsiyasini yozib,npm run migratebilan jadval yarating;models/user.jsdacreatevafindByEmailni yozing;POST /registermarshrutini ulab, bitta hisob yarating va javobdapassword_hashyo’qligini tasdiqlang;- O’sha emailni qayta yuborib
409, so’ng katta harflar bilan yuborib ham409olishingizni tekshiring; - Bazaga
node -e "..."bilan qarang vapassword_hashustunida$2b$10$bilan boshlanadigan qator turganini ko’ring.
6-qadam eng muhimi: bazaga o’z ko’zingiz bilan qarab, parol ochiq emasligini tasdiqlash — bu odatni har loyihada takrorlang.
Qisqacha xulosa
- Parol hech qachon ochiq saqlanmaydi;
md5vasha1ham yaramaydi, chunki juda tez. bcryptataylab sekin ishlaydi va salt’ni o’zi yaratadi.- Salt tufayli bir xil paroldan har safar boshqa hash chiqadi, lekin
comparebaribir to’g’ri javob beradi. - Ustun nomi
password_hashdeb ataladi va 60 belgilik qatorni saqlaydi. bcrypt.hash—async;awaitunutilsa bazaga[object Promise]tushadi.- Email
toLowerCase()bilan saqlanadi va qidiriladi. - Takroriy email ikki qavat to’siladi: kodda tushunarli
409xabari, bazadaUNIQUEkafolati.
Keyingi darsda kirish va marshrutni himoyalash — parolni solishtirish, sessiya ochish va himoyalangan sahifalar.