Ma'lumotlar bazalari

Node.js bilan bazaga ulanish

Endi eng amaliy qismga keldik: bazani Node.js dasturingizdan qanday ishlatish. Buning uchun maxsus kutubxona (driver) kerak. PostgreSQL uchun eng mashhuri โ€” pg. Ushbu darsda ulanish, so'rov yuborish, xavfsizlik va to'liq CRUD amallarini o'rganamiz.

pg kutubxonasini o'rnatish

PostgreSQL bilan ishlash uchun pg paketini npm orqali o'rnatamiz:

npm install pg

Bu kutubxona Node.js'dan PostgreSQL serveriga ulanish, so'rov yuborish va natijani JavaScript obyektlari sifatida olish imkonini beradi.

Oddiy ulanish: Client

Eng oddiy usul โ€” bitta Client yaratish. Unga ulanish ma'lumotlarini beramiz:

const { Client } = require("pg");

const client = new Client({
  host: "localhost",
  port: 5432,
  user: "postgres",
  password: "parol",
  database: "mydb"
});

async function main() {
  await client.connect();               // bazaga ulanish
  const res = await client.query("SELECT NOW()"); // so'rov
  console.log(res.rows);                // natija
  await client.end();                   // ulanishni yopish
}

main();

Bu yerda muhim jihatlar: connect() ulanadi, query() so'rov yuboradi, end() ulanishni yopadi. Barchasi async/await bilan ishlaydi, chunki baza bilan aloqa vaqt oladi (asinxron).

Haqiqiy loyihada parol va boshqa maxfiy ma'lumotlarni kodga to'g'ridan-to'g'ri yozmang! Ularni muhit o'zgaruvchilarida (process.env.DB_PASSWORD) yoki .env faylda saqlang. Aks holda parol kod tarixida (git) ochiq qoladi.

So'rov natijasi bilan ishlash

client.query() obyekt qaytaradi. Uning eng muhim xususiyati โ€” rows, ya'ni natija qatorlari massivi. Har bir qator oddiy JavaScript obyekti:

const res = await client.query("SELECT id, name FROM users");

console.log(res.rows);
// [ { id: 1, name: 'Ali' }, { id: 2, name: 'Vali' } ]

console.log(res.rows.length); // qatorlar soni
console.log(res.rows[0].name); // 'Ali'

Ko'rib turganingizdek, SQL natijasi to'g'ridan-to'g'ri JavaScript obyektlariga aylanadi โ€” ustun nomlari obyekt kalitlariga aylanadi. Bu bilan ishlash juda qulay.

SQL injection โ€” jiddiy xavf

Endi eng muhim mavzu โ€” xavfsizlik. Aytaylik, foydalanuvchidan kelgan ism bo'yicha qidirmoqchisiz. Vasvasaga tushib, so'rovni shunday yozishingiz mumkin:

// XAVFLI! Hech qachon bunday qilmang!
const name = req.query.name; // foydalanuvchidan kelgan
const res = await client.query(
  "SELECT * FROM users WHERE name = '" + name + "'"
);

Muammo shundaki, foydalanuvchi name o'rniga SQL kodini yuborishi mumkin. Masalan, u quyidagi qiymatni yuborsa:

'; DROP TABLE users; --

Unda so'rov shunday bo'ladi va butun jadval o'chib ketadi:

SELECT * FROM users WHERE name = ''; DROP TABLE users; --'
Bu โ€” SQL injection hujumi, veb-xavfsizlikdagi eng mashhur va xavfli zaifliklardan biri. Foydalanuvchi ma'lumotini hech qachon to'g'ridan-to'g'ri SQL matniga qo'shmang. Hujumchi ma'lumotni o'g'irlashi, o'zgartirishi yoki butun bazani yo'q qilishi mumkin.

Yechim: parametrlangan so'rovlar

To'g'ri yechim โ€” parametrlangan so'rovlar (parameterized queries). Qiymatlarni matnga qo'shish o'rniga, so'rovda $1, $2 kabi joy egallovchilar (placeholder) qo'yamiz, qiymatlarni esa alohida massivda beramiz:

// TO'G'RI va XAVFSIZ usul
const name = req.query.name;
const res = await client.query(
  "SELECT * FROM users WHERE name = $1",
  [name]
);

Bu yerda $1 โ€” birinchi parametr uchun joy, [name] esa uning qiymati. Kutubxona qiymatni xavfsiz tarzda joylashtiradi โ€” u har doim oddiy qiymat sifatida qaraladi, hech qachon SQL kod sifatida bajarilmaydi. SQL injection imkonsiz bo'ladi.

// Bir necha parametr bilan
const res = await client.query(
  "SELECT * FROM users WHERE age > $1 AND is_active = $2",
  [25, true]
);
Qoidani eslab qoling: foydalanuvchidan kelgan har qanday ma'lumot parametr orqali berilishi kerak, hech qachon satrni birlashtirish (+) orqali emas. Bu backend xavfsizligining eng asosiy qoidalaridan biri.

To'liq CRUD misoli

Endi to'rtta asosiy amalni (CREATE, READ, UPDATE, DELETE โ€” CRUD) parametrlangan so'rovlar bilan yozamiz. Bular real loyihada ishlatiladigan funksiyalar:

const { Client } = require("pg");
const client = new Client({ /* sozlamalar */ });

// CREATE โ€” yangi foydalanuvchi qo'shish
async function createUser(name, email, age) {
  const res = await client.query(
    "INSERT INTO users (name, email, age) VALUES ($1, $2, $3) RETURNING *",
    [name, email, age]
  );
  return res.rows[0]; // qo'shilgan qator
}

// READ โ€” id bo'yicha o'qish
async function getUser(id) {
  const res = await client.query(
    "SELECT * FROM users WHERE id = $1",
    [id]
  );
  return res.rows[0]; // topilgan qator (yoki undefined)
}

// UPDATE โ€” yoshni yangilash
async function updateAge(id, age) {
  const res = await client.query(
    "UPDATE users SET age = $1 WHERE id = $2 RETURNING *",
    [age, id]
  );
  return res.rows[0];
}

// DELETE โ€” o'chirish
async function deleteUser(id) {
  const res = await client.query(
    "DELETE FROM users WHERE id = $1",
    [id]
  );
  return res.rowCount; // o'chirilgan qatorlar soni
}
RETURNING * โ€” bu PostgreSQL'ning foydali imkoniyati. INSERT/UPDATE/DELETE'dan keyin o'zgargan qatorni qaytaradi. Masalan, INSERT ... RETURNING * avtomatik yaratilgan idni ham qaytaradi, shuning uchun uni alohida so'rov bilan olishga hojat yo'q.

Bu funksiyalarni ishlatish:

async function main() {
  await client.connect();

  const yangi = await createUser("Ali", "ali@mail.com", 25);
  console.log("Yaratildi:", yangi);

  const topilgan = await getUser(yangi.id);
  console.log("Topildi:", topilgan);

  await updateAge(yangi.id, 26);
  await deleteUser(yangi.id);

  await client.end();
}

main().catch(err => console.error("Xato:", err));
Har bir baza so'rovi xatolik berishi mumkin (ulanish uzilishi, noto'g'ri so'rov). Shuning uchun ularni try/catch yoki .catch() bilan o'rab, xatolarni to'g'ri qayta ishlash muhim. Yuqorida .catch() butun mainni himoya qiladi.

Connection Pool โ€” ulanishlar havzasi

Yuqorida bitta Client ishlatdik. Lekin veb-serverda bir vaqtda yuzlab so'rov keladi. Har so'rov uchun yangi ulanish ochish sekin va samarasiz. Yechim โ€” Pool (ulanishlar havzasi):

const { Pool } = require("pg");

const pool = new Pool({
  host: "localhost",
  user: "postgres",
  password: process.env.DB_PASSWORD,
  database: "mydb",
  max: 20 // eng ko'pi 20 ta ulanish
});

// query'ni to'g'ridan-to'g'ri pool'dan chaqiramiz
async function getUsers() {
  const res = await pool.query("SELECT * FROM users");
  return res.rows;
}

Pool oldindan bir nechta ulanishni ochib qo'yadi va ularni qayta ishlatadi. So'rov kelganda tayyor ulanishni beradi, tugagach uni havzaga qaytaradi. Bu ancha tez va samarali.

Real loyihalarda deyarli har doim Client emas, Pool ishlatiladi. pool.query() avtomatik ulanish oladi va qaytaradi โ€” siz connect/end haqida o'ylashingiz shart emas.

MongoDB uchun: mongoose (qisqacha)

Agar MongoDB ishlatsangiz, Node.js'da eng mashhur kutubxona โ€” mongoose. U ulanishni va ma'lumot bilan ishlashni osonlashtiradi:

npm install mongoose
const mongoose = require("mongoose");

await mongoose.connect("mongodb://localhost:27017/mydb");

// Sxema va model
const User = mongoose.model("User", new mongoose.Schema({
  name: String,
  email: String,
  age: Number
}));

// CRUD
const yangi = await User.create({ name: "Ali", age: 25 });
const hammasi = await User.find({ age: { $gt: 20 } });
await User.updateOne({ name: "Ali" }, { $set: { age: 26 } });
await User.deleteOne({ name: "Ali" });
Mongoose'da parametrlangan so'rovlar haqida qayg'urish shart emas โ€” siz filtrlarni obyekt sifatida berasiz, kutubxona ularni xavfsiz qayta ishlaydi. Bu MongoDB'ning ma'lumot bilan ishlash uslubining tabiiy afzalligi.

Xulosa