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
1bo’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
.gitignoreda bo’lsa; - barcha o’zgaruvchi va funksiya nomlari inglizcha bo’lsa.
Kengaytirish yo’llari
Loyiha ishlaydi, o’sish uchun joy ko’p:
deletebuyrug’i — kitobni o’chirish vachangesni tekshirish;- Qidiruv — muallif bo’yicha,
WHERE author LIKE ?bilan; - Jadvallarni ajratish — mualliflarni alohida jadvalga chiqarib, JOIN bilan bog’lash;
- TypeScript’ga o’tkazish — turlar bilan
Bookshaklini aniqlash; - npm paketiga aylantirish — bin maydoni
orqali
booksbuyrug’ini yasash; - Qamrovni o’lchash —
--experimental-test-coveragebilan sinalmagan qatorlarni topish.
Bilimni tekshirish
O’zingizni sinang. Javobni eslay olmasangiz — tegishli darsga qaytish belgisi:
execvaexecFileorasidagi farq nimada va qaysi biri foydalanuvchi kiritmasi bilan ishlatiladi?- Nima uchun
awaitbilan fayl o’qish oqimni to’smaydi, oddiy sikl esa to’sadi? cluster.fork()niisPrimarysharti ichida yozish nega majburiy?- Parametrli so’rovda hujum uchun yozilgan matn nega hech nima topmaydi?
error.messageo’rnigaerror.codebo’yicha tekshirish nega ishonchliroq?strictEqualvadeepStrictEqualqaysi holatda farq qiladi?--inspectva--inspect-brkorasidagi farq nimada?package.jsondagifilesmaydoni yozilmasa nima bo’ladi?- Kesh hajmi cheklanmasa uzoq ishlaydigan serverda nima yuz beradi?
- 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.