Yakuniy loyiha: baza va testli xizmat

O’rta daraja bo’limi yakunlandi. Alohida o’rganilgan narsalarni endi bitta ishlaydigan dasturga yig’amiz: ma’lumot bazada saqlanadi, xatolar turiga qarab ajratiladi, hodisalar jurnalga yoziladi va hammasi testlar bilan qoplanadi.

Natija — kitoblar ro’yxatini boshqaradigan terminal xizmati.

Bo’limni jamlash

Yo’lni orqaga qarab ko’ramiz. O’n ikki darsda quyidagilar o’rganildi:

Mavzu Dars Loyihada qayerda ishlaydi
Boshqa dasturni chaqirish child_process Xavfsizlik qoidasi sifatida
Og’ir hisobni ko’chirish worker_threads Kengaytirish g’oyasida
Ko’p yadroda ishlash cluster va PM2 Ishga tushirishda
Ma’lumotni bazada saqlash SQLite storage.js
Xato va jurnal xatolar va log errors.js, logger.js
Testlash node:test storage.test.js
Xatoni topish debugging Nosozlik chiqqanda
Paket qilish npm paket Tarqatish bosqichida
Turlar TypeScript Kengaytirish g’oyasida
Xavfsizlik xavfsizlik Kiritmani tekshirishda
O’lchash performance Sekinlik sezilganda

Yakuniy loyiha

Topshiriq: kitoblar ro’yxatini boshqaradigan CLI xizmat yozing.

Uchta buyruq bo’ladi:

Buyruq Vazifasi
node cli.js add "Sarlavha" "Muallif" Yangi kitob qo’shadi
node cli.js list Ro’yxatni chiqaradi
node cli.js read <id> O’qilgan deb belgilaydi

CLI dastur darsidagi vazifalar ro’yxatiga o’xshaydi, lekin uchta jiddiy farq bor: ma’lumot JSON faylida emas bazada, xatolar turlarga ajratilgan, va dastur testlar bilan qoplangan.

Fayllar tuzilishi

book-service/
  errors.js          — xato klasslari
  logger.js          — jurnal yozuvchi
  storage.js         — baza bilan ishlash
  cli.js             — buyruqlarni ulash
  storage.test.js    — testlar
  .gitignore

Har fayl bitta vazifani bajaradi. Bu modul tizimi darsidagi qoidaning amaliy qo’llanishi.

1-qadam: xato klasslari

errors.js:

class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = "ValidationError";
    this.field = field;
  }
}

class NotFoundError extends Error {
  constructor(message, id) {
    super(message);
    this.name = "NotFoundError";
    this.id = id;
  }
}

module.exports = { ValidationError, NotFoundError };

Ikki xato turi ajratildi: foydalanuvchi noto’g’ri ma’lumot berdi yoki so’ralgan yozuv topilmadi. Ularni ajratish cli.js da javobni to’g’ri tanlash imkonini beradi.

2-qadam: jurnal

logger.js:

const LEVELS = { debug: 10, info: 20, warn: 30, error: 40 };
const currentLevel = LEVELS[process.env.LOG_LEVEL] || LEVELS.info;

function log(level, message, details = {}) {
  if (LEVELS[level] < currentLevel) {
    return;
  }
  console.log(
    JSON.stringify({
      time: new Date().toISOString(),
      level,
      message,
      ...details,
    })
  );
}

module.exports = { log };

3-qadam: baza qatlami

storage.js — loyihaning yuragi:

const { DatabaseSync } = require("node:sqlite");
const { ValidationError, NotFoundError } = require("./errors");

function createStorage(filePath) {
  const database = new DatabaseSync(filePath);

  database.exec(`
    CREATE TABLE IF NOT EXISTS books (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      title TEXT NOT NULL,
      author TEXT NOT NULL,
      isRead INTEGER NOT NULL DEFAULT 0
    )
  `);

  function addBook(title, author) {
    if (!title || !author) {
      throw new ValidationError("Sarlavha va muallif majburiy", "title");
    }
    const result = database
      .prepare("INSERT INTO books (title, author) VALUES (?, ?)")
      .run(title, author);
    return { id: result.lastInsertRowid, title, author, isRead: 0 };
  }

  function listBooks() {
    return database.prepare("SELECT * FROM books ORDER BY id").all();
  }

  function markRead(id) {
    const result = database
      .prepare("UPDATE books SET isRead = 1 WHERE id = ?")
      .run(id);

    if (result.changes === 0) {
      throw new NotFoundError("Bunday kitob yo'q", id);
    }
    return result.changes;
  }

  function close() {
    database.close();
  }

  return { addBook, listBooks, markRead, close };
}

module.exports = { createStorage };

Uch qaror muhim:

Birinchidan, createStorage fayl yo’lini parametr sifatida oladi. Shuning uchun testda ":memory:" berish mumkin.

Ikkinchidan, barcha so’rovlar ? parametri bilan yozilgan — SQL injection dan himoya.

Uchinchidan, markRead da changes === 0 tekshiriladi. Usiz dastur mavjud bo’lmagan kitobni “belgilandi” deb xabar berardi.

4-qadam: buyruqlarni ulash

cli.js:

const path = require("node:path");
const { createStorage } = require("./storage");
const { log } = require("./logger");
const { ValidationError, NotFoundError } = require("./errors");

const databasePath = path.join(__dirname, "books.db");
const storage = createStorage(databasePath);

const args = process.argv.slice(2);
const command = args[0];

try {
  if (command === "add") {
    const book = storage.addBook(args[1], args[2]);
    log("info", "Kitob qo'shildi", { id: book.id });
    console.log(`Qo'shildi [${book.id}]: ${book.title}`);
  } else if (command === "list") {
    const books = storage.listBooks();
    if (books.length === 0) {
      console.log("Ro'yxat bo'sh");
    } else {
      for (const book of books) {
        const mark = book.isRead ? "[x]" : "[ ]";
        console.log(`${mark} ${book.id}. ${book.title} — ${book.author}`);
      }
    }
  } else if (command === "read") {
    storage.markRead(Number(args[1]));
    log("info", "Kitob o'qilgan deb belgilandi", { id: Number(args[1]) });
    console.log("Belgilandi");
  } else {
    console.log("Buyruqlar:");
    console.log('  node cli.js add "Sarlavha" "Muallif"');
    console.log("  node cli.js list");
    console.log("  node cli.js read <id>");
  }
} catch (error) {
  if (error instanceof ValidationError) {
    log("warn", "Kiritishda xato", { field: error.field });
    console.log("Xato:", error.message);
    process.exitCode = 1;
  } else if (error instanceof NotFoundError) {
    log("warn", "Kitob topilmadi", { id: error.id });
    console.log("Xato:", error.message);
    process.exitCode = 1;
  } else {
    log("error", "Kutilmagan xato", { errorMessage: error.message });
    process.exitCode = 1;
  }
} finally {
  storage.close();
}

finally bloki muhim: xato chiqsa ham baza yopiladi.

process.exit() o’rniga process.exitCode ishlatilgani ham bejiz emas — process darsida aytilganidek, exit() dasturni shu zahoti to’xtatadi va finally bajarilmay qolishi mumkin.

Ishlatib ko’ramiz

Buyruqsiz:

node cli.js
Buyruqlar:
  node cli.js add "Sarlavha" "Muallif"
  node cli.js list
  node cli.js read <id>

Ikkita kitob qo’shamiz:

node cli.js add "O'tkan kunlar" "Abdulla Qodiriy"
node cli.js add "Shum bola" "G'afur G'ulom"
{"time":"2026-09-12T13:25:27.736Z","level":"info","message":"Kitob qo'shildi","id":1}
Qo'shildi [1]: O'tkan kunlar
{"time":"2026-09-12T13:25:27.841Z","level":"info","message":"Kitob qo'shildi","id":2}
Qo'shildi [2]: Shum bola

Ikki xil chiqishga e’tibor bering: JSON qatori — jurnal, oddiy matn — foydalanuvchiga javob.

Ro’yxat:

node cli.js list
[ ] 1. O'tkan kunlar — Abdulla Qodiriy
[ ] 2. Shum bola — G'afur G'ulom

Ikkinchisini o’qilgan deb belgilaymiz:

node cli.js read 2
node cli.js list
[ ] 1. O'tkan kunlar — Abdulla Qodiriy
[x] 2. Shum bola — G'afur G'ulom

Xato holatlari

Bo’sh sarlavha:

node cli.js add "" ""
{"time":"2026-09-12T13:25:28.277Z","level":"warn","message":"Kiritishda xato","field":"title"}
Xato: Sarlavha va muallif majburiy

Mavjud bo’lmagan kitob:

node cli.js read 999
{"time":"2026-09-12T13:25:28.372Z","level":"warn","message":"Kitob topilmadi","id":999}
Xato: Bunday kitob yo'q

Ikkalasida ham chiqish kodi 1 bo’ladi.

5-qadam: testlar

storage.test.js:

const { describe, it, beforeEach, afterEach } = require("node:test");
const assert = require("node:assert");
const { createStorage } = require("./storage");
const { ValidationError, NotFoundError } = require("./errors");

let storage;

describe("createStorage", () => {
  // Har test uchun yangi, bo'sh baza
  beforeEach(() => {
    storage = createStorage(":memory:");
  });

  afterEach(() => {
    storage.close();
  });

  it("yangi baza bo'sh bo'ladi", () => {
    assert.deepStrictEqual(storage.listBooks(), []);
  });

  it("kitob qo'shadi va id qaytaradi", () => {
    const book = storage.addBook("O'tkan kunlar", "Abdulla Qodiriy");
    assert.strictEqual(book.id, 1);
    assert.strictEqual(storage.listBooks().length, 1);
  });

  it("sarlavhasiz kitobda ValidationError beradi", () => {
    assert.throws(() => storage.addBook("", "Muallif"), ValidationError);
  });

  it("o'qilgan deb belgilaydi", () => {
    const book = storage.addBook("Shum bola", "G'afur G'ulom");
    storage.markRead(book.id);
    assert.strictEqual(storage.listBooks()[0].isRead, 1);
  });

  it("yo'q kitobda NotFoundError beradi", () => {
    assert.throws(() => storage.markRead(999), NotFoundError);
  });
});
node --test
▶ createStorage
  ✔ yangi baza bo'sh bo'ladi (3.0511ms)
  ✔ kitob qo'shadi va id qaytaradi (2.1066ms)
  ✔ sarlavhasiz kitobda ValidationError beradi (1.3573ms)
  ✔ o'qilgan deb belgilaydi (0.9579ms)
  ✔ yo'q kitobda NotFoundError beradi (0.6904ms)
✔ createStorage (9.6374ms)
ℹ tests 5
ℹ suites 1
ℹ pass 5
ℹ fail 0

beforeEach har test oldidan yangi baza ochgani uchun testlar bir-biriga ta’sir qilmaydi. ":memory:" tanlangani esa haqiqiy books.db faylini himoyalaydi.

6-qadam: .gitignore

node_modules/
.env
*.db

Baza fayli omborga tushmasligi kerak: u har kompyuterda boshqacha bo’ladi va ichida haqiqiy ma’lumot turadi. Bu xavfsizlik darsidagi qoida.

Loyihani baholash

Ishingiz tayyor deb hisoblanadi, agar:

  • uchala buyruq (add, list, read) ishlasa;
  • ma’lumot dastur yopilgandan keyin ham saqlanib qolsa;
  • noto’g’ri kiritmada tushunarli xabar chiqsa va chiqish kodi 1 bo’lsa;
  • mavjud bo’lmagan id “belgilandi” deb xabar bermasa;
  • barcha SQL so’rovlari ? parametri bilan yozilgan bo’lsa;
  • jurnal JSON shaklida, foydalanuvchi javobi oddiy matnda chiqsa;
  • kamida beshta test o’tsa va ular ":memory:" bazada ishlasa;
  • baza fayli .gitignore da bo’lsa;
  • barcha o’zgaruvchi va funksiya nomlari inglizcha bo’lsa.

Kengaytirish yo’llari

Loyiha ishlaydi, o’sish uchun joy ko’p:

  1. delete buyrug’i — kitobni o’chirish va changes ni tekshirish;
  2. Qidiruv — muallif bo’yicha, WHERE author LIKE ? bilan;
  3. Jadvallarni ajratish — mualliflarni alohida jadvalga chiqarib, JOIN bilan bog’lash;
  4. TypeScript’ga o’tkazishturlar bilan Book shaklini aniqlash;
  5. npm paketiga aylantirishbin maydoni orqali books buyrug’ini yasash;
  6. Qamrovni o’lchash--experimental-test-coverage bilan sinalmagan qatorlarni topish.

Bilimni tekshirish

O’zingizni sinang. Javobni eslay olmasangiz — tegishli darsga qaytish belgisi:

  1. exec va execFile orasidagi farq nimada va qaysi biri foydalanuvchi kiritmasi bilan ishlatiladi?
  2. Nima uchun await bilan fayl o’qish oqimni to’smaydi, oddiy sikl esa to’sadi?
  3. cluster.fork() ni isPrimary sharti ichida yozish nega majburiy?
  4. Parametrli so’rovda hujum uchun yozilgan matn nega hech nima topmaydi?
  5. error.message o’rniga error.code bo’yicha tekshirish nega ishonchliroq?
  6. strictEqual va deepStrictEqual qaysi holatda farq qiladi?
  7. --inspect va --inspect-brk orasidagi farq nimada?
  8. package.json dagi files maydoni yozilmasa nima bo’ladi?
  9. Kesh hajmi cheklanmasa uzoq ishlaydigan serverda nima yuz beradi?
  10. Foydalanuvchi bergan fayl yo’lini qaysi bosqichda tekshirish kerak?

Bundan keyin qayerga

O’rta daraja bo’limini yakunladingiz. Endi qo’lingizda bazaga yozadigan, xatolarni ajratadigan, jurnal yuritadigan va testlari bor xizmat turibdi — bu haqiqiy loyihalarda kutiladigan minimal to’plam.

Node.js bo’limining boshida yozgan birinchi skriptni eslang: u faylni o’qib, ekranga chiqarardi. Oradagi farq katta, lekin u bir necha o’nlab kichik qadamdan yig’ilgan.

Keyingi yo’nalish tanlovi sizda. Web tomonga qiziqsangiz, Express bo’limida shu bilimlar ustiga HTTP qatlami qo’shiladi. Serverga chiqarish va ishlatish masalalari deploy va operatsiyalar darsida ko’rilgan. Xavfsizlikni chuqurroq o’rganmoqchi bo’lsangiz, amaliy web xavfsizligi darsi shu bo’limdagi qoidalarni web kontekstida davom ettiradi.

Qaysi yo’lni tanlamang, bitta odat qoladi: yangi kodni test bilan qoplash va xatoni taxmin qilmasdan o’lchash.

Bu dars foydali bo'ldimi?

marta ko'rildi