PostgreSQL — CRUD, parametrli so'rovlar, SQL Injection himoyasi
SQL Injection — eng xavfli hujum
Avval eng muhim narsadan boshlaymiz, chunki bu xavfsizlik masalasi.
Tasavvur qiling: siz foydalanuvchini ID bo'yicha qidirasiz. Shoshib qolsangiz, shunday yozib qo'yishingiz mumkin:
const id = req.params.id;
const result = await pool.query(`SELECT * FROM users WHERE id = ${id}`);
Bu ishlaydi. Lekin hacker id o'rniga quyidagini yuborsa:
1 OR 1=1
SQL so'rov bunday bo'ladi:
SELECT * FROM users WHERE id = 1 OR 1=1
1=1 har doim to'g'ri — barcha foydalanuvchilar qaytariladi. Yoki yanada xavflisi:
1; DROP TABLE users; --
SELECT * FROM users WHERE id = 1; DROP TABLE users; --
Butun jadval o'chiriladi. Bu — SQL Injection.
Himoya — parametrli so'rovlar
Yechim oddiy: foydalanuvchi kiritmasini SQL ga to'g'ridan-to'g'ri qo'shmaslik. O'rniga $1, $2 placeholder ishlatiladi, qiymatlar alohida beriladi:
// Xavfli:
pool.query(`SELECT * FROM users WHERE id = ${id}`);
// Xavfsiz:
pool.query('SELECT * FROM users WHERE id = $1', [id]);
pg kutubxonasi $1 ni qiymat bilan almashtiradi, lekin avval uni escape qiladi — maxsus belgilar zararsizlantiriladi. Hacker nima yubbormasin — SQL kodi sifatida bajarilmaydi.
Bu qoida — har doim, istisnossiz. Foydalanuvchidan kelgan har qanday ma'lumot parametr sifatida beriladi.
CREATE — yangi qator qo'shish
router.post('/', async (req, res, next) => {
try {
const { name, email, age } = req.body;
const result = await pool.query(
'INSERT INTO users (name, email, age) VALUES ($1, $2, $3) RETURNING *',
[name, email, age]
);
res.status(201).json(result.rows[0]);
} catch (err) {
next(err);
}
});
RETURNING * — qo'shilgan qatorni qaytaradi. Bu muhim: ma'lumotlar bazasi id, created_at kabi avtomatik qiymatlarni o'zi belgilaydi. RETURNING * siz ularni alohida so'ramasdan darhol olasiz.
READ — o'qish
// Hammasi
router.get('/', async (req, res, next) => {
try {
const result = await pool.query(
'SELECT id, name, email FROM users ORDER BY id'
);
res.json(result.rows);
} catch (err) {
next(err);
}
});
// Bittasi
router.get('/:id', async (req, res, next) => {
try {
const result = await pool.query(
'SELECT id, name, email FROM users WHERE id = $1',
[req.params.id]
);
if (result.rows.length === 0) {
return res.status(404).json({ message: 'Topilmadi' });
}
res.json(result.rows[0]);
} catch (err) {
next(err);
}
});
result.rows.length === 0 — foydalanuvchi topilmadimi tekshirish. pg xato chiqarmaydi — bo'sh massiv qaytaradi.
UPDATE — yangilash
router.patch('/:id', async (req, res, next) => {
try {
const { name, email } = req.body;
const result = await pool.query(
'UPDATE users SET name = $1, email = $2 WHERE id = $3 RETURNING *',
[name, email, req.params.id]
);
if (result.rows.length === 0) {
return res.status(404).json({ message: 'Topilmadi' });
}
res.json(result.rows[0]);
} catch (err) {
next(err);
}
});
RETURNING * bu yerda ham muhim — yangilangan qatorni qaytaradi. Aks holda foydalanuvchiga eski ma'lumotni qaytarishga majbur bo'lasiz.
DELETE — o'chirish
router.delete('/:id', async (req, res, next) => {
try {
const result = await pool.query(
'DELETE FROM users WHERE id = $1 RETURNING id',
[req.params.id]
);
if (result.rows.length === 0) {
return res.status(404).json({ message: 'Topilmadi' });
}
res.status(204).send(); // 204 — muvaffaqiyatli, kontent yo'q
} catch (err) {
next(err);
}
});
204 No Content — o'chirishda standart status. Javobda kontent bo'lmaydi.
rowCount — nechta qator ta'sirlandi
result.rows.length o'rniga result.rowCount ham ishlatiladi — ayniqsa UPDATE va DELETE da:
const result = await pool.query(
'DELETE FROM users WHERE id = $1',
[req.params.id]
);
if (result.rowCount === 0) {
return res.status(404).json({ message: 'Topilmadi' });
}
rowCount — nechta qator o'zgarganini ko'rsatadi. 0 bo'lsa — hech narsa topilmadi.
Unique constraint xatosi
Email takrorlanmasligi kerak bo'lsa — ma'lumotlar bazasida UNIQUE constraint qo'yiladi. Takrorlansa — pg xato chiqaradi. Bu xatoni ushlab, foydalanuvchiga tushunarli xabar berish kerak:
router.post('/', async (req, res, next) => {
try {
const result = await pool.query(
'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
[req.body.name, req.body.email]
);
res.status(201).json(result.rows[0]);
} catch (err) {
// PostgreSQL unique violation kodi: 23505
if (err.code === '23505') {
return res.status(409).json({ message: 'Bu email allaqachon mavjud' });
}
next(err);
}
});
err.code === '23505' — PostgreSQL ning unique constraint xatosi kodi. Boshqa PostgreSQL xato kodlari ham bor — ularni bilish kerak bo'lganda PostgreSQL dokumentatsiyasidan topiladi.
Tez-tez uchraydigan xatolar
String interpolatsiya bilan SQL yozish:
// Hech qachon bunday qilmang:
pool.query(`SELECT * FROM users WHERE email = '${email}'`);
Foydalanuvchidan kelgan har qanday ma'lumot — $1, $2 orqali.
rows[0] ni tekshirmaslik:
const result = await pool.query('SELECT * FROM users WHERE id = $1', [id]);
return result.rows[0].name; // rows[0] undefined bo'lsa — TypeError!
Avval rows.length === 0 tekshiriladi, keyin rows[0] ishlatiladi.
Parolni ochiq saqlash:
// Xato — parol ochiq:
INSERT INTO users (email, password) VALUES ($1, $2)
// password = 'qwerty123'
// To'g'ri — hash qilib saqlash (keyingi darslarda):
// password = '$2b$10$...'
Xulosa
CRUD — Create, Read, Update, Delete. Har biri uchun SQL: INSERT, SELECT, UPDATE, DELETE.
RETURNING * — o'zgartirilgan qatorni qaytaradi. rowCount — nechta qator ta'sirlandi.
SQL Injection — eng xavfli hujum. Himoya — parametrli so'rovlar ($1, $2). Bu qoida istisnossiz.
Keyingi dars
15-dars: MySQL — mysql2 moduli, CRUD