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.

🕑 11 daqiqa o‘qish 📄 670 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Birinchi baza
  2. O'qish metodlari
  3. SQL in'yeksiyasi
  4. Nomlangan parametrlar
  5. Tranzaksiyalar
  6. Express ga ulash
  7. Fayldagi baza
  8. Xulosa

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 #

JavaScript
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();
Natija
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 #

JavaScript
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();
Natija
get: { id: 1, ism: 'Malika', ball: 88 }
topilmadi: undefined
all: [ { ism: 'Husanboy', ball: 92 }, { ism: 'Malika', ball: 88 } ]
MetodQaytaradi
.run(...){ changes, lastInsertRowid }
.get(...)Bitta qator yoki undefined
.all(...)Qatorlar massivi
Qatorlar oddiy obyekt emas

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:

  1. console.log ularni [Object: null prototype] { ... } deb chiqaradi - chiqish chiroyli bo'lmaydi.
  2. Ularda toString yoki hasOwnProperty kabi 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 #

JavaScript
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();
Natija
Tayyorlangan so'rov topdi: 0
Birlashtirilgan so'rov topdi: 2
Bu - eng mashhur veb-zaiflik

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:

SQL
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 #

JavaScript
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();
Natija
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 #

JavaScript
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();
Natija
30000 o'tkazildi: true
999999 o'tkazildi: false
[
  { ism: 'Husanboy', mablag: 80000 },
  { ism: 'Malika', mablag: 70000 }
]
Tranzaksiya - hammasi yoki hech narsa

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 #

JavaScript
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();
Natija
201 { malumot: { id: 1, ism: 'Dilnoza', ball: 94 } }
{ malumot: [ { id: 1, ism: 'Dilnoza', ball: 94 } ] }
404
So'rovlarni bir marta tayyorlang

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 #

JavaScript
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");
Natija
Qayta ochildi: saqlandi
Baza o'chirildi

Baza yopilgandan keyin ham ma'lumot faylda qoladi - aynan shu kerak edi.

SQLite qachon yetarli, qachon yetmaydi

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.

Amaliy topshiriq
  1. Xotirada baza yaratib, jadval qo'shing.
  2. run, get, all metodlarini sinang.
  3. lastInsertRowid ni ishlating.
  4. Qatorni { ...qator } bilan oddiy obyektga aylantiring.
  5. ' OR '1'='1 ni ikki usulda sinab, farqni ko'ring.
  6. Nomlangan parametrlar bilan so'rov yozing.
  7. GROUP BY bilan guruh bo'yicha o'rtacha hisoblang.
  8. Tranzaksiya yozib, xatoda ROLLBACK qiling.
  9. So'rovlarni marshrutdan tashqarida tayyorlang.
  10. Fayldagi bazani yopib, qayta oching.

Xulosa #

  • Node 22 dan beri node:sqlite tilning ichida - paket kerak emas.
  • .run o'zgarishlarni, .get bitta qatorni, .all massivni 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 PostgreSQL kerak bo'ladi.

Keyingi bo'limda autentifikatsiyani o'rganamiz.

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.