16-bo‘lim
Ma'lumotlar bazasi
node:sqlite bilan ishlash, tayyorlangan so'rovlar va SQL in'yeksiyasi, tranzaksiyalar, null prototipli qatorlar va Express ga ulash.
Ushbu bo‘lim mundarijasi
Shu paytgacha ma'lumot xotirada saqlandi - dastur qayta ishga tushganda u yo'qolardi. Endi uni bazaga yozamiz.
Node 22 dan beri SQLite tilning o'zida bor: hech qanday paket o'rnatish kerak emas.
Birinchi baza #
import { DatabaseSync } from "node:sqlite";
const baza = new DatabaseSync(":memory:");
baza.exec(`
CREATE TABLE talabalar (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ism TEXT NOT NULL,
ball INTEGER NOT NULL
)
`);
const qoshish = baza.prepare(
"INSERT INTO talabalar (ism, ball) VALUES (?, ?)",
);
const natija = qoshish.run("Malika", 88);
console.log("Qo'shildi:", natija.changes, "ta, id:", natija.lastInsertRowid);
qoshish.run("Husanboy", 92);
qoshish.run("Aziza", 79);
const soni = baza.prepare("SELECT COUNT(*) AS soni FROM talabalar").get();
console.log("Jami:", soni.soni);
baza.close();
Qo'shildi: 1 ta, id: 1
Jami: 3
:memory: - bazani xotirada yaratadi. Dastur tugaganda u
yo'qoladi, shuning uchun sinash uchun ideal.
Haqiqiy loyihada fayl yo'li beriladi: new DatabaseSync("baza.db").
O'qish metodlari #
import { DatabaseSync } from "node:sqlite";
const baza = new DatabaseSync(":memory:");
baza.exec(`CREATE TABLE talabalar (
id INTEGER PRIMARY KEY, ism TEXT, ball INTEGER)`);
const q = baza.prepare("INSERT INTO talabalar VALUES (?, ?, ?)");
q.run(1, "Malika", 88);
q.run(2, "Husanboy", 92);
q.run(3, "Aziza", 79);
const bitta = baza.prepare("SELECT * FROM talabalar WHERE id = ?").get(1);
console.log("get:", { ...bitta });
const yoq = baza.prepare("SELECT * FROM talabalar WHERE id = ?").get(99);
console.log("topilmadi:", yoq);
const kuchli = baza
.prepare(`SELECT ism, ball FROM talabalar
WHERE ball > ? ORDER BY ball DESC`)
.all(80);
console.log("all:", kuchli.map((q) => ({ ...q })));
baza.close();
get: { id: 1, ism: 'Malika', ball: 88 }
topilmadi: undefined
all: [ { ism: 'Husanboy', ball: 92 }, { ism: 'Malika', ball: 88 } ]
| Metod | Qaytaradi |
|---|---|
.run(...) | { changes, lastInsertRowid } |
.get(...) | Bitta qator yoki undefined |
.all(...) | Qatorlar massivi |
E'tibor bering, yuqorida { ...bitta } yozilgan.
Sababi: node:sqlite qatorlarni null prototipli obyekt sifatida
qaytaradi. Ular deyarli hamma joyda oddiy obyektdek ishlaydi, lekin
ikki farq bor:
console.logularni[Object: null prototype] { ... }deb chiqaradi - chiqish chiroyli bo'lmaydi.- Ularda
toStringyokihasOwnPropertykabi meros metodlar yo'q.
Bu aslida xavfsizlik chorasi: null prototip prototipni ifloslash hujumidan himoya qiladi.
{ ...qator } yoki Object.assign({}, qator) uni oddiy obyektga
aylantiradi. JSON ga chiqarayotganda farq sezilmaydi -
JSON.stringify ikkalasini ham bir xil qayta ishlaydi.
SQL in'yeksiyasi #
import { DatabaseSync } from "node:sqlite";
const baza = new DatabaseSync(":memory:");
baza.exec("CREATE TABLE foydalanuvchilar (ism TEXT, rol TEXT)");
baza.prepare("INSERT INTO foydalanuvchilar VALUES (?, ?)")
.run("Malika", "oddiy");
baza.prepare("INSERT INTO foydalanuvchilar VALUES (?, ?)")
.run("Husanboy", "admin");
const yomonNiyat = "' OR '1'='1";
const xavfsiz = baza
.prepare("SELECT * FROM foydalanuvchilar WHERE ism = ?")
.all(yomonNiyat);
console.log("Tayyorlangan so'rov topdi:", xavfsiz.length);
const xavfliSql =
`SELECT * FROM foydalanuvchilar WHERE ism = '${yomonNiyat}'`;
const xavfli = baza.prepare(xavfliSql).all();
console.log("Birlashtirilgan so'rov topdi:", xavfli.length);
baza.close();
Tayyorlangan so'rov topdi: 0
Birlashtirilgan so'rov topdi: 2
Ikki natijani solishtiring. Bir xil kiritma, butunlay boshqa oqibat.
Tayyorlangan so'rov (? bilan) kiritmani ma'lumot deb
biladi. ' OR '1'='1 shunchaki g'alati ism - unday ism yo'q,
demak natija bo'sh.
Satr birlashtirish esa kiritmani kod qilib qo'yadi. So'rov shunday bo'lib qoladi:
SELECT * FROM foydalanuvchilar WHERE ism = '' OR '1'='1'
'1'='1' doim rost, shuning uchun butun jadval qaytdi. Parol
tekshiruvida bu hujumchini istalgan hisobga kiritib yuboradi.
Qoida mutlaq: foydalanuvchi kiritmasini hech qachon SQL ga
birlashtirmang. Har doim ? yoki nomlangan parametr.
Diqqat: bu faqat SQLite muammosi emas - hamma bazada bir xil.
Nomlangan parametrlar #
import { DatabaseSync } from "node:sqlite";
const baza = new DatabaseSync(":memory:");
baza.exec("CREATE TABLE talabalar (ism TEXT, ball INTEGER, guruh TEXT)");
const qoshish = baza.prepare(
"INSERT INTO talabalar VALUES (:ism, :ball, :guruh)",
);
qoshish.run({ ism: "Malika", ball: 88, guruh: "A1" });
qoshish.run({ ism: "Nodira", ball: 91, guruh: "A1" });
qoshish.run({ ism: "Kamola", ball: 64, guruh: "B2" });
const natija = baza
.prepare(`SELECT guruh, COUNT(*) AS soni, AVG(ball) AS ortacha
FROM talabalar GROUP BY guruh ORDER BY guruh`)
.all();
for (const q of natija) {
console.log(`${q.guruh}: ${q.soni} ta, o'rtacha ${q.ortacha}`);
}
baza.close();
A1: 2 ta, o'rtacha 89.5
B2: 1 ta, o'rtacha 64
Nomlangan parametrlar ko'p maydonli so'rovlarda ancha o'qishliroq: qaysi qiymat qayerga ketayotgani ravshan.
Tranzaksiyalar #
import { DatabaseSync } from "node:sqlite";
const baza = new DatabaseSync(":memory:");
baza.exec("CREATE TABLE hisoblar (ism TEXT PRIMARY KEY, mablag INTEGER)");
baza.prepare("INSERT INTO hisoblar VALUES (?, ?)").run("Malika", 100000);
baza.prepare("INSERT INTO hisoblar VALUES (?, ?)").run("Husanboy", 50000);
const yechish = baza.prepare(
"UPDATE hisoblar SET mablag = mablag - ? WHERE ism = ?",
);
const qoshish = baza.prepare(
"UPDATE hisoblar SET mablag = mablag + ? WHERE ism = ?",
);
function otkaz(kimdan, kimga, summa) {
baza.exec("BEGIN");
try {
yechish.run(summa, kimdan);
const holat = baza
.prepare("SELECT mablag FROM hisoblar WHERE ism = ?")
.get(kimdan);
if (holat.mablag < 0) {
throw new Error("mablag' yetarli emas");
}
qoshish.run(summa, kimga);
baza.exec("COMMIT");
return true;
} catch (xato) {
baza.exec("ROLLBACK");
return false;
}
}
console.log("30000 o'tkazildi:", otkaz("Malika", "Husanboy", 30000));
console.log("999999 o'tkazildi:", otkaz("Malika", "Husanboy", 999999));
const hammasi = baza.prepare("SELECT * FROM hisoblar ORDER BY ism").all();
console.log(hammasi.map((q) => ({ ...q })));
baza.close();
30000 o'tkazildi: true
999999 o'tkazildi: false
[
{ ism: 'Husanboy', mablag: 80000 },
{ ism: 'Malika', mablag: 70000 }
]
Ikkinchi o'tkazma muvaffaqiyatsiz tugadi, lekin Malikaning hisobidan pul yechilmadi.
Sababi - ROLLBACK: tranzaksiya ichida bajarilgan hamma
o'zgarish bekor qilindi.
Tasavvur qiling, tranzaksiyasiz yozilgan bo'lsa: pul yechildi, keyin xato chiqdi va qo'shilmadi. Pul yo'qoladi.
Qoida: bir necha yozuv birga o'zgarishi kerak bo'lsa, ular
tranzaksiya ichida bo'lsin. BEGIN, COMMIT, xatoda esa
ROLLBACK.
Express ga ulash #
import express from "express";
import { DatabaseSync } from "node:sqlite";
const baza = new DatabaseSync(":memory:");
baza.exec(`CREATE TABLE talabalar (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ism TEXT NOT NULL,
ball INTEGER NOT NULL
)`);
const soragichlar = {
hammasi: baza.prepare("SELECT * FROM talabalar ORDER BY id"),
bittasi: baza.prepare("SELECT * FROM talabalar WHERE id = ?"),
qoshish: baza.prepare("INSERT INTO talabalar (ism, ball) VALUES (?, ?)"),
};
const app = express();
app.use(express.json());
app.get("/talabalar", (sorov, javob) => {
javob.json({ malumot: soragichlar.hammasi.all() });
});
app.get("/talabalar/:id", (sorov, javob) => {
const talaba = soragichlar.bittasi.get(Number(sorov.params.id));
if (!talaba) {
javob.status(404).json({ xato: "topilmadi" });
return;
}
javob.json({ malumot: talaba });
});
app.post("/talabalar", (sorov, javob) => {
const { lastInsertRowid } = soragichlar.qoshish.run(
String(sorov.body.ism),
Number(sorov.body.ball),
);
const kitob = soragichlar.bittasi.get(lastInsertRowid);
javob.status(201).json({ malumot: kitob });
});
const server = app.listen(0);
const port = server.address().port;
const asos = `http://localhost:${port}`;
const yaratildi = await fetch(asos + "/talabalar", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ism: "Dilnoza", ball: 94 }),
});
console.log(yaratildi.status, await yaratildi.json());
console.log(await (await fetch(asos + "/talabalar")).json());
console.log((await fetch(asos + "/talabalar/99")).status);
server.close();
baza.close();
201 { malumot: { id: 1, ism: 'Dilnoza', ball: 94 } }
{ malumot: [ { id: 1, ism: 'Dilnoza', ball: 94 } ] }
404
E'tibor bering: prepare chaqiruvlari marshrutlar ichida emas,
tashqarisida - soragichlar obyektida.
Sababi: prepare SQL ni tahlil qilib, ichki shaklga aylantiradi.
Bu ish har so'rovda takrorlanishi shart emas.
Bir marta tayyorlangan so'rovni istalgancha ishlatish mumkin - har safar boshqa parametrlar bilan. Katta yuklamada bu sezilarli farq beradi.
Fayldagi baza #
import { DatabaseSync } from "node:sqlite";
import { rm } from "node:fs/promises";
const yol = "sinov-baza.db";
const birinchi = new DatabaseSync(yol);
birinchi.exec("CREATE TABLE IF NOT EXISTS qaydlar (matn TEXT)");
birinchi.prepare("INSERT INTO qaydlar VALUES (?)").run("saqlandi");
birinchi.close();
const ikkinchi = new DatabaseSync(yol);
const qator = ikkinchi.prepare("SELECT matn FROM qaydlar").get();
console.log("Qayta ochildi:", qator.matn);
ikkinchi.close();
await rm(yol);
console.log("Baza o'chirildi");
Qayta ochildi: saqlandi
Baza o'chirildi
Baza yopilgandan keyin ham ma'lumot faylda qoladi - aynan shu kerak edi.
SQLite - haqiqiy baza va u juda ko'p loyiha uchun yetarli. Bitta faylda ishlaydi, server kerak emas, o'qish juda tez.
U mos keladi: kichik va o'rta saytlar, ichki vositalar, buyruq qatori dasturlari, testlar, mobil ilovalar.
U mos kelmaydi: bir vaqtda ko'p yozish kerak bo'lganda. SQLite da yozish butun bazani qulflaydi, shuning uchun ko'p yozuvchili yuklamada u to'siqqa aylanadi. Bundan tashqari u bitta mashinada yashaydi - bir nechta server undan birga foydalana olmaydi.
Bunday holatda PostgreSQL yoki MySQL kerak bo'ladi. Ular uchun
pg va mysql2 paketlari ishlatiladi, lekin asosiy tushunchalar -
tayyorlangan so'rovlar, tranzaksiyalar - aynan bir xil.
- Xotirada baza yaratib, jadval qo'shing.
run,get,allmetodlarini sinang.lastInsertRowidni ishlating.- Qatorni
{ ...qator }bilan oddiy obyektga aylantiring. ' OR '1'='1ni ikki usulda sinab, farqni ko'ring.- Nomlangan parametrlar bilan so'rov yozing.
GROUP BYbilan guruh bo'yicha o'rtacha hisoblang.- Tranzaksiya yozib, xatoda
ROLLBACKqiling. - So'rovlarni marshrutdan tashqarida tayyorlang.
- Fayldagi bazani yopib, qayta oching.
Xulosa #
- Node 22 dan beri
node:sqlitetilning ichida - paket kerak emas. .runo'zgarishlarni,.getbitta qatorni,.allmassivni qaytaradi.- Qatorlar null prototipli; chiqarish uchun
{ ...qator }. - Foydalanuvchi kiritmasini SQL ga birlashtirmang - har doim
?yoki nomlangan parametr. - Bir necha yozuv birga o'zgarishi kerak bo'lsa - tranzaksiya
va xatoda
ROLLBACK. - So'rovlarni bir marta tayyorlab, qayta ishlating.
- SQLite ko'p loyiha uchun yetarli, lekin ko'p yozuvchili
yuklamada
PostgreSQLkerak bo'ladi.
Keyingi bo'limda autentifikatsiyani o'rganamiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.