WisarWisar
Hamroh materiallar/Intervyu55 daqiqa

Node.js va Backend — Chuqur Intervyu Savollari

Full-stack dasturchi uchun Node.js va backend bo'yicha chuqur, to'liq texnik intervyu tayyorgarligi. Har bir savol quyidagi tuzilishda: Ta'rif Mexanizm (chuqur) Kod misol Keng tarqalgan xato Follow-up.


Qanday foydalanish

  • Avval savolni o'qing, ovoz chiqarib o'zingiz javob berib ko'ring, keyin tahlilni o'qing.
  • Belgilash: Junior · Middle · Senior.
  • Mexanizm qismini yodlamang — tushuning. Intervyuda "nega shunday" degan savolga javob bera olish asosiy farq.
  • Kod misollari qisqa, ammo ishlaydigan. Ularni terminalda sinab ko'ring.

Intervyuda backend savollari ko'pincha bitta joyga olib boradi: event loop, asinxronlik va xavfsizlik. Shu uch ustunni mustahkam biling.


1-bo'lim. Node.js asoslari va Event Loop

S: Node.js nima va u nimasi bilan brauzerdagi JavaScript'dan farq qiladi? (Junior)

J:

Ta'rif. Node.js — bu V8 JavaScript dvigatelini brauzerdan tashqarida, server tomonida ishga tushiruvchi runtime environment. U JavaScript'ga fayl tizimi, tarmoq (network), operatsion tizim bilan ishlash imkonini beradi.

Mexanizm (chuqur). Node.js uch qismdan iborat: (1) V8 — JS kodni mashina kodiga kompilyatsiya qiladi (JIT); (2) libuv — C tilida yozilgan kutubxona bo'lib, asinxron I/O, event loop va thread pool'ni ta'minlaydi; (3) Node API'lar (fs, http, net, crypto va h.k.) — C++ bilan yozilgan va JS'ga "bindings" orqali ochilgan. Brauzerda window, document, DOM API bor, lekin fayl tizimi yo'q; Node'da esa global, process, require/module bor, lekin DOM yo'q.

Kod misol:

js
// Node.js muhitida mavjud, brauzerda yo'q:
const fs = require('node:fs');
console.log(process.version);          // 'v20.11.0'
console.log(process.platform);         // 'win32' / 'linux'
fs.writeFileSync('salom.txt', 'Salom, dunyo');

Keng tarqalgan xato. Node.js'ni "til" deb atash. Node.js — til emas, runtime. Til — JavaScript. Yana bir xato: Node "brauzer JS'ni tez ishlatadi" deb tushunish — aslida ikkalasi ham bir xil V8'da ishlaydi, farq muhit (API) va vazifada.

Follow-up. "V8 va libuv o'rtasidagi mas'uliyat qanday taqsimlanadi?" — V8 JS kodni bajaradi va xotira (heap) boshqaradi; libuv esa asinxron operatsiyalarni (I/O, timer) va event loop'ni boshqaradi.


S: Node.js Event Loop qanday ishlaydi? Uning fazalarini (phases) tushuntiring. (Senior)

J:

Ta'rif. Event loop — bu libuv tomonidan boshqariladigan cheksiz sikl bo'lib, u Node'ning bitta asosiy oqimda (single main thread) bloklanmasdan asinxron operatsiyalarni bajarishini ta'minlaydi. Har bir aylanishda u navbatdagi callback'larni ma'lum tartibda (fazalarda) qayta ishlaydi.

Mexanizm (chuqur). Event loop 6 ta asosiy fazadan iborat va har biri o'z callback navbatiga (queue) ega:

  1. timers — muddati tugagan setTimeout / setInterval callback'lari.
  2. pending callbacks — ba'zi tizim operatsiyalari (masalan TCP xatolari) callback'lari.
  3. idle, prepare — ichki foydalanish uchun (libuv).
  4. poll — yangi I/O hodisalarini kutadi va I/O callback'larini bajaradi (event loop ko'p vaqtini shu yerda o'tkazadi).
  5. checksetImmediate callback'lari.
  6. close callbackssocket.on('close', ...) kabi yopilish callback'lari.

Muhim nozik jihat: har bir faza o'rtasida Node ikkita alohida "microtask" navbatini bo'shatadi: avval process.nextTick() navbati, keyin Promise microtask navbati. Ya'ni nextTick va Promise callback'lari faza ichidagi har bir makrotask'dan keyin darhol ishlaydi, kelgusi fazani kutmaydi.

Kod misol:

js
console.log('1: sync boshlandi');

setTimeout(() => console.log('4: setTimeout (timers)'), 0);
setImmediate(() => console.log('5: setImmediate (check)'));

Promise.resolve().then(() => console.log('3: Promise (microtask)'));
process.nextTick(() => console.log('2: nextTick (microtask, eng oldin)'));

console.log('1.5: sync tugadi');

// Tartib:
// 1: sync boshlandi
// 1.5: sync tugadi
// 2: nextTick (microtask, eng oldin)
// 3: Promise (microtask)
// 4: setTimeout (timers)   yoki  5: setImmediate — kontekstga bog'liq
// setTimeout va setImmediate tartibi main modulda aniqlanmagan (noaniq),
// lekin I/O callback ichida setImmediate DOIM setTimeout'dan oldin ishlaydi.

Keng tarqalgan xato. setTimeout(fn, 0) "darhol ishlaydi" deb o'ylash. Aslida u kamida bitta event loop aylanishini kutadi va process.nextTick hamda Promise'lardan keyin ishlaydi. Yana: nextTick'ni cheksiz rekursiv chaqirish event loop'ni butunlay bloklab qo'yadi ("nextTick starvation"), chunki u har fazada I/O'ga o'tishdan oldin to'liq bo'shatiladi.

Follow-up. "poll fazasi bo'sh bo'lsa nima bo'ladi?" — Agar setImmediate navbatida callback bo'lsa, poll tugab check fazasiga o'tadi; aks holda poll timer'lar muddati tugashini kutib turadi.


S: Node.js event loop bilan brauzer event loop o'rtasidagi farq nimada? (Senior)

J:

Ta'rif. Ikkalasi ham single-thread asinxron modelga asoslangan, lekin ular turli spetsifikatsiyalar asosida ishlaydi: brauzer — HTML/WHATWG event loop, Node — libuv fazalar modeli.

Mexanizm (chuqur). Asosiy farqlar:

  • Fazalar. Node'da 6 ta aniq faza (timers, poll, check va h.k.) bor. Brauzerda bunday fazalar yo'q — bitta makrotask navbati (task queue) va microtask navbati bor, plus render (rAF, layout, paint) qadamlari.
  • Microtask'larni bo'shatish vaqti. Brauzerda har bir makrotask'dan keyin microtask navbati to'liq bo'shatiladi. Eski Node (v11 gacha) microtask'larni faza oxirida bo'shatardi; Node v11+dan boshlab u brauzerga o'xshab har makrotask'dan keyin bo'shatadi — shu bilan xatti-harakat yaqinlashdi.
  • process.nextTick va setImmediate faqat Node'da bor. Brauzerda ular yo'q (o'rniga queueMicrotask, MessageChannel ishlatiladi).
  • requestAnimationFrame, render fazasi faqat brauzerda bor, chunki Node'da UI yo'q.

Kod misol:

js
// Node'da nextTick — Promise'dan ham oldin:
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));
// Chiqish: nextTick, promise

// Brauzerda bunday emas — nextTick umuman yo'q:
Promise.resolve().then(() => console.log('promise'));
queueMicrotask(() => console.log('microtask'));
// Chiqish: promise, microtask (navbat tartibida)

Keng tarqalgan xato. Brauzerdagi bilimni Node'ga to'g'ridan-to'g'ri ko'chirish — masalan setTimeout va setImmediate tartibi ikkalasida bir xil deb o'ylash. Node'da setImmediate alohida "check" fazasiga ega, brauzerda setImmediate standart emas.

Follow-up. "Nega Node v11'da microtask xatti-harakati o'zgartirildi?" — Brauzer va Node o'rtasidagi izchillikni ta'minlash va isomorphic (server + client) kodni bashoratli qilish uchun.


S: Node "single-threaded" bo'lsa, u qanday qilib minglab parallel so'rovlarni bir vaqtda bajaradi? (Middle)

J:

Ta'rif. Node'ning JavaScript kodi bitta oqimda (single main thread) ishlaydi, ammo I/O operatsiyalari (fayl, tarmoq, DNS) bloklanmasdan, operatsion tizim va libuv thread pool orqali fonda bajariladi. Bu non-blocking I/O modeli.

Mexanizm (chuqur). Node so'rovni "kutib turmaydi". Masalan bazadan ma'lumot so'ralganda: Node so'rovni yuboradi, callback'ni ro'yxatga oladi va darhol keyingi ishga o'tadi. Baza javob berganda, operatsion tizim libuv'ga xabar beradi, libuv callback'ni event loop navbatiga qo'yadi, va navbat kelganda u asosiy oqimda bajariladi. Tarmoq I/O odatda OS'ning epoll/kqueue/IOCP mexanizmi orqali, fayl I/O va CPU-og'ir operatsiyalar (crypto, zlib) esa libuv thread pool (standart 4 ta oqim) orqali bajariladi.

Kod misol:

js
const fs = require('node:fs');

console.log('1: so‘rov boshlandi');
fs.readFile('katta-fayl.txt', 'utf8', (err, data) => {
  console.log('3: fayl o‘qildi'); // fonda tugagach chaqiriladi
});
console.log('2: kod davom etdi'); // fayl o‘qilishini KUTMAYDI

// Chiqish: 1  2  3

Keng tarqalgan xato. "Single-thread" so'zini "bir vaqtda bitta ish qiladi, sekin" deb tushunish. Aslida I/O parallel ketadi; muammo faqat CPU-og'ir (sinxron hisob-kitob) kodda yuzaga keladi — u asosiy oqimni bloklaydi.

Follow-up. "Qaysi operatsiyalar libuv thread pool'ni ishlatadi?"fs, crypto (pbkdf2, randomBytes), zlib, dns.lookup. Tarmoq socket'lari esa thread pool'ni emas, OS event mexanizmini ishlatadi.


S: Worker Threads nima va qachon ishlatiladi? Cluster'dan farqi nima? (Senior)

J:

Ta'rif. Worker Threads (node:worker_threads) — bitta Node protsessi ichida bir nechta JavaScript oqimini ishga tushirish imkonini beradi, asosan CPU-og'ir vazifalarni asosiy oqimni bloklamasdan bajarish uchun. Cluster esa bir nechta alohida protsess (har biri o'z event loop'i bilan) yaratib, kirish trafigini ular orasida taqsimlaydi.

Mexanizm (chuqur). Worker Threads: har bir worker o'z V8 instansi, o'z event loop'i va o'z xotirasiga ega, lekin ular bitta protsess ichida. Ular MessageChannel orqali xabar almashadi va SharedArrayBuffer orqali xotirani ulashishi mumkin. Cluster: child_process.fork ustida qurilgan, har bir worker — to'liq alohida OS protsessi; ular xotirani ulashmaydi, faqat IPC orqali xabar almashadi. Cluster asosan I/O-og'ir ilovani bir nechta CPU yadrosiga tarqatib (horizontal scaling bitta mashinada) yuqori throughput olish uchun; Worker Threads esa hisob-kitob (rasm qayta ishlash, kriptografiya, katta JSON parse) uchun.

Kod misol:

js
// main.js
const { Worker } = require('node:worker_threads');

function ogirHisob(data) {
  return new Promise((resolve, reject) => {
    const worker = new Worker('./worker.js', { workerData: data });
    worker.on('message', resolve);
    worker.on('error', reject);
  });
}
ogirHisob(1e9).then((n) => console.log('Natija:', n));

// worker.js
const { workerData, parentPort } = require('node:worker_threads');
let sum = 0;
for (let i = 0; i < workerData; i++) sum += i; // asosiy oqimni bloklamaydi
parentPort.postMessage(sum);

Keng tarqalgan xato. Worker Threads'ni I/O ishlarini "tezlashtirish" uchun ishlatish. I/O allaqachon non-blocking — worker qo'shsangiz faqat overhead ortadi. Worker Threads faqat CPU-bound ish uchun foydali.

Follow-up. "SharedArrayBuffer'da poyga holati (race condition) muammosini qanday hal qilasiz?"Atomics API orqali atomik operatsiyalar va lock mexanizmlari bilan.


S: Process va Thread o'rtasidagi farq nima? (Middle)

J:

Ta'rif. Process — operatsion tizim ajratgan mustaqil bajarish birligi bo'lib, o'z alohida xotira maydoniga ega. Thread — protsess ichidagi yengilroq bajarish birligi bo'lib, o'sha protsessning xotirasini boshqa thread'lar bilan ulashadi.

Mexanizm (chuqur). Har bir protsess o'z manzil maydoni (address space), fayl deskriptorlari va resurslariga ega — ular izolyatsiya qilingan, biri qulasa boshqasiga ta'sir qilmaydi, lekin ular orasida aloqa qimmat (IPC, serializatsiya). Thread'lar esa bir protsess xotirasini ulashgani uchun ular orasida aloqa tez, lekin ulangan xotira poyga holati (race condition) xavfini keltirib chiqaradi va sinxronlash (lock, mutex) talab qiladi. Node'da: cluster/child_process = protsesslar, worker_threads = thread'lar.

Kod misol:

js
// Protsess (izolyatsiya, ulashilgan xotira yo'q):
const { fork } = require('node:child_process');
const child = fork('./bola.js');
child.send({ vazifa: 'hisobla' });     // faqat IPC orqali
child.on('message', (m) => console.log('Bola javobi:', m));

// Thread (ulashilgan xotira mumkin):
const { Worker } = require('node:worker_threads');
const shared = new SharedArrayBuffer(4);
new Worker('./ish.js', { workerData: shared }); // xotira ulashiladi

Keng tarqalgan xato. Thread'lar "har doim protsessdan tez" deb o'ylash. Kichik, izolyatsiya kerak bo'lgan vazifalar uchun protsess xavfsizroq; thread esa sinxronlash murakkabligini keltiradi.

Follow-up. "Nima uchun cluster xotira ulashmaydi, lekin baribir foydali?" — Chunki u stateless HTTP serverlarni barcha CPU yadrolariga tarqatib, izolyatsiya (bir worker qulasa boshqalari ishlayveradi) va parallellik beradi.


S: Cluster moduli qanday ishlaydi va u yuklama (load)ni qanday taqsimlaydi? (Senior)

J:

Ta'rif. node:cluster — bitta portda tinglaydigan bir nechta worker protsess yaratib, kelgan ulanishlarni ular orasida taqsimlash imkonini beradi. Bu Node'ning single-thread cheklovini bitta mashinadagi ko'p yadroli CPU'da yengib o'tadi.

Mexanizm (chuqur). Master (primary) protsess portni ochadi va fork() orqali worker'lar yaratadi. Ulanishlar (connection) master orqali yoki OS orqali worker'larga yo'naltiriladi. Standart taqsimlash strategiyasi: Windows'da OS o'zi hal qiladi; Linux'da Node round-robin (navbati bilan) strategiyasini ishlatadi. Barcha worker'lar bir xil server kodini ishga tushiradi va bir xil portni "ulashadi" — aslida master socket'ni ushlab turadi va deskriptorni worker'larga uzatadi.

Kod misol:

js
const cluster = require('node:cluster');
const http = require('node:http');
const os = require('node:os');

if (cluster.isPrimary) {
  const yadrolar = os.cpus().length;
  for (let i = 0; i < yadrolar; i++) cluster.fork();
  cluster.on('exit', (worker) => {
    console.log(`Worker ${worker.process.pid} qulaydi, qayta ishga tushiryapman`);
    cluster.fork(); // qulasa qayta tiklaymiz
  });
} else {
  http.createServer((req, res) => res.end(`Salom PID ${process.pid}`))
      .listen(3000);
}

Keng tarqalgan xato. Cluster ishlatib, xotirada holat (in-memory session, cache) saqlash. Har bir worker alohida xotiraga ega — foydalanuvchi bir so'rovda worker A'ga, keyingisida worker B'ga tushishi mumkin. Holatni Redis kabi tashqi tizimda saqlash kerak (stateless dizayn).

Follow-up. "Zamonaviy prodakshnda cluster o'rniga nima ishlatiladi?" — Ko'pincha PM2 (cluster mode'ni boshqaradi) yoki konteynerlar (Docker + Kubernetes / horizontal pod autoscaling), bunda har bir konteyner bitta protsess ishlatadi.


2-bo'lim. Asinxronlik, Streams va Buffer

S: Streams nima, uning 4 turi qaysi va Backpressure nima? (Senior)

J:

Ta'rif. Stream — ma'lumotni butunlay xotiraga yuklamasdan, bo'lak-bo'lak (chunk) qayta ishlash imkonini beruvchi abstraktsiya. Bu katta fayllar va tarmoq ma'lumotlari uchun xotira-samarali yechim.

Mexanizm (chuqur). 4 tur: (1) Readable — manbadan o'qish (fs.createReadStream, HTTP request); (2) Writable — manbaga yozish (fs.createWriteStream, HTTP response); (3) Duplex — ham o'qish ham yozish (TCP socket); (4) Transform — o'qib, o'zgartirib, yozadigan duplex (zlib.createGzip, shifrlash). Backpressure — Readable ma'lumotni Writable qabul qila oladiganidan tezroq ishlab chiqarganda yuzaga keladigan holat. .pipe() yoki pipeline() buni avtomatik boshqaradi: Writable buferi to'lganda (highWaterMark chegarasidan oshganda) write() false qaytaradi, Readable to'xtaydi (pause), Writable bo'shaganda drain hodisasi bilan qayta boshlanadi (resume).

Kod misol:

js
const fs = require('node:fs');
const zlib = require('node:zlib');
const { pipeline } = require('node:stream/promises');

// Backpressure'ni pipeline avtomatik boshqaradi:
await pipeline(
  fs.createReadStream('katta.log'),   // Readable
  zlib.createGzip(),                  // Transform
  fs.createWriteStream('katta.log.gz') // Writable
);
console.log('Siqildi — xotira to‘lmasdan');

Keng tarqalgan xato. Backpressure'ni e'tiborsiz qoldirib, readable.on('data', chunk => writable.write(chunk)) yozish. Agar Writable sekin bo'lsa, ma'lumot xotirada to'planib, xotira to'lib ketadi (OOM). Doim pipe() yoki pipeline() ishlating — ular backpressure'ni o'zi hal qiladi. Yana: pipe'da xatolarni ushlamaslik (leak); pipeline esa xatoni to'g'ri tarqatadi va resurslarni tozalaydi.

Follow-up. "pipe() va pipeline() orasidagi farq nima?"pipeline() xatolarni to'g'ri boshqaradi va oqimlarni avtomatik yopadi (cleanup), pipe() esa xato bo'lsa oqimlarni ochiq qoldirib, resurs sizishiga olib keladi.


S: Buffer nima va u qachon kerak bo'ladi? (Middle)

J:

Ta'rif. Buffer — Node'da ikkilik (binary) ma'lumotni saqlash uchun mo'ljallangan sinf. U V8 heap'idan tashqarida, sobit uzunlikdagi xom xotira bo'lagini ifodalaydi.

Mexanizm (chuqur). JavaScript o'zi matn (string, UTF-16) bilan ishlashga mo'ljallangan, lekin server fayl, tarmoq paketlar, rasm, shifrlash kabi xom baytlar bilan ishlaydi. Buffer — Uint8Array'ning kengaytmasi bo'lib, har bir element 0–255 orasidagi bayt. U yaratilganda xotira V8 heap'idan tashqarida ajratiladi (shu sababli katta buferlar GC bosimini kamaytiradi, lekin xotira sizishiga e'tibor kerak). Kodlash (encoding) — utf8, base64, hex — baytlarni matnga va aksiga aylantiradi.

Kod misol:

js
const buf = Buffer.from('Salom', 'utf8');
console.log(buf);              // <Buffer 53 61 6c 6f 6d>
console.log(buf.length);       // 5 bayt
console.log(buf.toString('hex'));    // '53616c6f6d'
console.log(buf.toString('base64')); // 'U2Fsb20='

// Xavfsiz ajratish (eski xotira qoldig‘isiz):
const bo'sh = Buffer.alloc(10);   // nol bilan to‘ldirilgan

Keng tarqalgan xato. Buffer.allocUnsafe() ishlatib, uni to'ldirmasdan ishlatish — u eski xotira qoldiqlarini (boshqa ma'lumot, hatto maxfiy) o'z ichiga olishi mumkin (xavfsizlik teshigi). Ishonchsiz holatda Buffer.alloc() (nol bilan to'ldirilgan) ishlating.

Follow-up. "Buffer va Uint8Array o'rtasidagi farq?" — Buffer — Uint8Array'ning meros oluvchisi, qo'shimcha metodlar (toString(encoding), write, readInt32BE va h.k.) bilan. Har bir Buffer — Uint8Array, lekin aksincha emas.


S: EventEmitter nima va u qanday ishlaydi? (Middle)

J:

Ta'rif. EventEmitter (node:events) — Node'ning hodisaga asoslangan (event-driven) arxitekturasining o'zagi. U obyektga hodisalarni "chiqarish" (emit) va ularga "tinglovchi" (listener) biriktirish imkonini beradi.

Mexanizm (chuqur). Ichida EventEmitter hodisa nomlarini (kalit) ularga bog'langan listener funksiyalar massivi (qiymat) bilan bog'laydi. emit('event', ...args) chaqirilganda, o'sha hodisa uchun ro'yxatga olingan barcha listener'lar sinxron, ro'yxatga olinish tartibida, birin-ketin chaqiriladi. Node'ning ko'p ichki qismlari (streams, HTTP server, process) EventEmitter'ga asoslangan. Standart bo'yicha bitta hodisaga 10 tadan ko'p listener qo'shilsa, xotira sizishidan ogohlantiruvchi warning chiqadi.

Kod misol:

js
const EventEmitter = require('node:events');

class Buyurtma extends EventEmitter {}
const buyurtma = new Buyurtma();

buyurtma.on('yaratildi', (id) => console.log(`Buyurtma ${id} yaratildi`));
buyurtma.once('to‘landi', (id) => console.log(`${id} to‘landi (bir marta)`));

buyurtma.emit('yaratildi', 42);  // sinxron chaqiriladi
buyurtma.emit('to‘landi', 42);
buyurtma.emit('to‘landi', 42);   // 'once' — ikkinchi marta ishlamaydi

Keng tarqalgan xato. 'error' hodisasiga listener qo'ymaslik. Agar EventEmitter 'error' chiqarsayu listener bo'lmasa, Node butun protsessni qulatib yuboradi (uncaught exception). Doim 'error' hodisasini tinglang. Yana: listener'larni off/removeListener bilan olib tashlamaslik — bu xotira sizishiga olib keladi.

Follow-up. "EventEmitter callback'lari sinxron mi asinxron mi?"emit sinxron ishlaydi — barcha listener'lar navbatdagi koddan oldin bajariladi. Asinxronlik kerak bo'lsa, listener ichida setImmediate/process.nextTick ishlating.


S: Callback, Promise va async/await o'rtasidagi farq nima? (Junior)

J:

Ta'rif. Uchalasi ham asinxron natijani boshqarishning usullari. Callback — natija tayyor bo'lganda chaqiriladigan funksiya. Promise — kelajakda tugaydigan operatsiyani ifodalovchi obyekt (pending fulfilled/rejected). async/await — Promise'lar ustidagi sintaktik "shakar" bo'lib, asinxron kodni sinxrondek o'qiladigan qiladi.

Mexanizm (chuqur). Callback: funksiyani argument sifatida uzatasiz, u tugaganda chaqiriladi — lekin ko'p bosqichli kodda "callback hell" (piramida) yuzaga keladi. Promise: .then()/.catch() bilan zanjir qilinadi, xatolar zanjir bo'ylab tarqaladi. async/await: async funksiya doim Promise qaytaradi; await Promise hal bo'lishini "kutadi" (lekin event loop'ni bloklamaydi — funksiya to'xtab, boshqa ish ketaveradi), xatolar try/catch bilan ushlanadi.

Kod misol:

js
const fs = require('node:fs');

// 1) Callback
fs.readFile('a.txt', 'utf8', (err, data) => {
  if (err) return console.error(err);
  console.log(data);
});

// 2) Promise
fs.promises.readFile('a.txt', 'utf8')
  .then((data) => console.log(data))
  .catch((err) => console.error(err));

// 3) async/await (eng o‘qiladigan)
async function o'qi() {
  try {
    const data = await fs.promises.readFile('a.txt', 'utf8');
    console.log(data);
  } catch (err) {
    console.error(err);
  }
}

Keng tarqalgan xato. async funksiya ichida awaitni unutib, Promise'ni "qiymat" deb ishlatish (const x = fn() — bu Promise, qiymat emas). Yana: ketma-ket awaitlar bilan mustaqil operatsiyalarni birin-ketin kutish (sekin) — ular Promise.all bilan parallel bajarilishi kerak.

Follow-up. "Promise.all, Promise.allSettled, Promise.race farqi?"all — barchasi muvaffaqiyatli bo'lsa natija, bittasi qulasa darhol reject; allSettled — barchasini (muvaffaqiyat/xato) kutadi; race — birinchi hal bo'lgan (yoki reject) natijani qaytaradi.


S: "Error-first callback" konvensiyasi nima? (Junior)

J:

Ta'rif. Node'ning an'anaviy callback uslubida, callback'ning birinchi argumenti doim xato (error) bo'ladi; agar xato bo'lmasa — null. Ikkinchi va keyingi argumentlar — muvaffaqiyatli natija.

Mexanizm (chuqur). Bu konvensiya Node ekotizimida izchillik beradi: har qanday callback'ni ishlatuvchi kod avval xatoni tekshiradi, keyin natija bilan ishlaydi. Bu naqsh util.promisify kabi vositalarga callback'larni avtomatik Promise'ga aylantirishga imkon beradi (chunki xato joyi standart).

Kod misol:

js
function ma'lumotOl(id, callback) {
  if (typeof id !== 'number') {
    return callback(new Error('id raqam bo‘lishi kerak')); // 1-arg: xato
  }
  callback(null, { id, nom: 'Ali' }); // 1-arg null, 2-arg natija
}

ma'lumotOl(5, (err, user) => {
  if (err) return console.error('Xato:', err.message); // avval xato
  console.log('Foydalanuvchi:', user);
});

Keng tarqalgan xato. Callback ichida xatoni tekshirmay, to'g'ridan-to'g'ri natija bilan ishlash — natija undefined bo'lsa, kod boshqa joyda qulaydi va asl sabab yashirinadi. Yana: xato bo'lganda return qilmay, callback davom etishi (ikki marta callback chaqirilishi).

Follow-up. "Callback-uslubdagi funksiyani qanday Promise'ga aylantirasiz?"util.promisify(fn) bilan (agar fn error-first konvensiyasiga amal qilsa), yoki qo'lda new Promise((resolve, reject) => fn(args, (err, res) => err ? reject(err) : resolve(res))).


S: "Callback hell" nima va uni qanday hal qilasiz? (Middle)

J:

Ta'rif. Callback hell (yoki "piramida of doom") — bir necha asinxron operatsiya ketma-ket bajarilishi kerak bo'lganda, callback'lar bir-birining ichiga chuqur joylashib, kod o'ngga "piramida" kabi cho'zilib ketishi. U o'qish, xato boshqarishni va qo'llab-quvvatlashni qiyinlashtiradi.

Mexanizm (chuqur). Har bir asinxron qadam keyingisining callback'i ichida yozilsa, kod chuqurlashadi. Yechim: (1) Promise zanjirlari (.then bilan yassilash); (2) async/await — eng toza yechim, kodni sinxrondek ko'rsatadi; (3) funksiyalarni nomlab ajratish (modularlik).

Kod misol:

js
//  Callback hell:
getUser(id, (e, user) => {
  getOrders(user.id, (e, orders) => {
    getDetails(orders[0].id, (e, details) => {
      console.log(details); // 3 daraja chuqur
    });
  });
});

//  async/await bilan yassi:
async function ko'r(id) {
  const user = await getUser(id);
  const orders = await getOrders(user.id);
  const details = await getDetails(orders[0].id);
  console.log(details);
}

Keng tarqalgan xato. async/await'ga o'tganda ham har bir awaitni alohida try/catchga o'rab, kodni yana murakkablashtirish. Ko'pincha bitta tashqi try/catch yetarli; yoki xatoni markaziy error handler'ga uzatish.

Follow-up. "Mustaqil operatsiyalarni callback hell'siz parallel qanday bajarasiz?"Promise.all([getA(), getB()]) bilan — ular bir vaqtda ishlaydi va natijalar massiv sifatida qaytadi.


S: Unhandled Promise Rejection nima va nega u xavfli? (Senior — tuzoq)

J:

Ta'rif. Unhandled promise rejection — reject bo'lgan (xato bergan) Promise'ga hech qanday .catch() yoki try/catch biriktirilmaganda yuzaga keladi. Ya'ni xato yuz berdi, lekin uni hech kim ushlamadi.

Mexanizm (chuqur). Node v15'dan boshlab, ushlanmagan rejection standart bo'yicha butun protsessni qulatadi (avval faqat warning chiqarardi). Bu ataylab: yashiringan xato ilovani noaniq holatda qoldirishdan ko'ra, protsessni to'xtatib (va process manager qayta ishga tushirib) izchil holat ta'minlash xavfsizrok. Sabablari: async funksiyani awaitsiz/.catchsiz chaqirish, .then bor lekin .catch yo'q, yoki await ichidagi xato tashqi try/catchdan tashqarida.

Kod misol:

js
//  Ushlanmagan rejection — protsess qulaydi (Node 15+):
async function ishla() {
  throw new Error('Baza ulanmadi');
}
ishla(); // .catch yo'q — UNHANDLED

//  Ushlangan:
ishla().catch((err) => console.error('Ushlandi:', err.message));

// Global "so‘nggi himoya" (log qilib, toza to‘xtash uchun):
process.on('unhandledRejection', (reason) => {
  console.error('Ushlanmagan rejection:', reason);
  process.exit(1); // process manager qayta tiklaydi
});

Keng tarqalgan xato. process.on('unhandledRejection')'ni ishlatib, xatoni shunchaki log qilib, protsessni davom ettirish. Ilova endi noaniq (inconsistent) holatda — bu yashiringan buglarga olib keladi. To'g'ri yondashuv: log qiling, keyin toza to'xtang (graceful shutdown) va process manager qayta tiklasin.

Follow-up. "unhandledRejection va uncaughtException farqi?" — Birinchisi ushlanmagan Promise rejection'lar uchun; ikkinchisi ushlanmagan sinxron exception'lar uchun. Ikkalasi ham "so'nggi himoya" — normal oqimda emas, faqat log + graceful exit uchun ishlatilishi kerak.


S: Event loop'ni nima bloklaydi va buni qanday oldini olasiz? (Senior — tuzoq)

J:

Ta'rif. Event loop bloklanishi — asosiy oqimda uzoq davom etuvchi sinxron (CPU-og'ir) kod ishlaganda yuzaga keladi. Bu davomida Node hech qanday boshqa so'rovni, timer'ni yoki I/O callback'ni qayta ishlay olmaydi — butun server "muzlab" qoladi.

Mexanizm (chuqur). Node bitta oqimda JS bajargani uchun, biror callback uzoq ishlasa (masalan katta massivni tartiblash, JSON.parse ulkan obyektni, bcryptni sinxron rejimda, cheksiz sikl, regex ReDoS), event loop keyingi fazaga o'ta olmaydi. Natijada barcha kutayotgan so'rovlar navbatda to'xtab qoladi, latency oshadi. Yechim: (1) CPU-og'ir ishni worker_threadsga ko'chirish; (2) katta vazifani kichik bo'laklarga bo'lib setImmediate bilan tarqatish; (3) sinxron API o'rniga asinxron variantni ishlatish (bcrypt.hash sinxron emas, callback/Promise variant).

Kod misol:

js
//  Event loop'ni bloklaydi — server muzlaydi:
app.get('/hisob', (req, res) => {
  let sum = 0;
  for (let i = 0; i < 1e10; i++) sum += i; // bir necha soniya bloklaydi
  res.json({ sum });
});

//  Worker thread'ga ko‘chirilgan:
const { Worker } = require('node:worker_threads');
app.get('/hisob', (req, res) => {
  const w = new Worker('./hisob-worker.js');
  w.on('message', (sum) => res.json({ sum })); // asosiy oqim bo‘sh qoladi
});

Keng tarqalgan xato. fs.readFileSync, crypto.pbkdf2Sync, JSON.parseni ulkan ma'lumotda so'rov ishlovchisi (request handler) ichida ishlatish. Bitta foydalanuvchining og'ir so'rovi barcha boshqa foydalanuvchilarni sekinlashtiradi.

Follow-up. "Event loop bloklanib qolganini qanday aniqlaysiz?"perf_hooks'dagi event loop latency monitoring (monitorEventLoopDelay), yoki APM vositalari (Clinic.js, --prof), yoki blocked-at kutubxonasi bilan.


3-bo'lim. Express, Middleware va Routing

S: Express'da middleware nima va u qanday ishlaydi? (Middle)

J:

Ta'rif. Middleware — so'rov (request) va javob (response) o'rtasidagi "quvur"da ketma-ket bajariladigan funksiya. Har bir middleware (req, res, next) argumentlarini oladi va so'rovni o'zgartirishi, javob bershi yoki next() chaqirib keyingisiga o'tkazishi mumkin.

Mexanizm (chuqur). Express so'rovni middleware'lar stackidan (ro'yxatga qo'shilish tartibida) o'tkazadi. Har bir middleware next() chaqirsa — navbatdagi middleware ishlaydi; res.send()/res.json() chaqirsa — zanjir to'xtab javob ketadi; next(err) chaqirsa — Express oddiy middleware'larni o'tkazib yuborib, error-handling middleware'iga (4 argument: err, req, res, next) o'tadi. Middleware turlari: application-level (app.use), router-level, built-in (express.json), third-party (cors, helmet), error-handling.

Kod misol:

js
const express = require('express');
const app = express();

app.use(express.json());                 // built-in: JSON body'ni parse qiladi

app.use((req, res, next) => {            // application-level: log
  console.log(`${req.method} ${req.url}`);
  next();                                // keyingisiga o‘tkazadi
});

function auth(req, res, next) {          // marshrut uchun middleware
  if (!req.headers.authorization) return res.status(401).json({ xato: 'Token yo‘q' });
  next();
}

app.get('/profil', auth, (req, res) => res.json({ nom: 'Ali' }));

app.use((err, req, res, next) => {       // error-handling (4 argument!)
  res.status(500).json({ xato: err.message });
});

Keng tarqalgan xato. Middleware ichida next()ni chaqirmaslik va javob ham bermaslik — so'rov "osilib" qoladi (hang), client timeout'ga uchraydi. Yana: error-handling middleware'ni 4 argument bilan yozmaslik — Express uni oddiy middleware deb qabul qiladi va xatolar ushlanmaydi.

Follow-up. "async middleware'da tashlangan xatoni Express qanday ushlaydi?" — Express 4'da avtomatik ushlamaydi — try/catch bilan next(err) chaqirish yoki wrapper (express-async-errors yoki Promise.resolve(fn).catch(next)) kerak. Express 5'da async xatolar avtomatik ushlanadi.


S: Express'da routing qanday tashkil qilinadi? (Junior)

J:

Ta'rif. Routing — kelgan so'rovning HTTP metodi va URL yo'liga (path) qarab, qaysi ishlovchi (handler) funksiya javob berishini aniqlash. Express'da har bir marshrut metod + yo'l + handler(lar)dan iborat.

Mexanizm (chuqur). Express marshrutlarni ro'yxatga olinish tartibida tekshiradi; birinchi mos kelgan marshrut ishlaydi. Yo'lda parametr (:id), wildcard va regex bo'lishi mumkin — parametrlar req.paramsda bo'ladi. Katta ilovada marshrutlar express.Router() bilan modullarga ajratiladi (masalan /users, /orders alohida fayl), bu tashkiliy tozalikni beradi.

Kod misol:

js
const express = require('express');
const router = express.Router();

router.get('/:id', (req, res) => {
  res.json({ id: req.params.id });     // /users/42  { id: "42" }
});
router.post('/', (req, res) => {
  res.status(201).json({ yaratildi: true });
});

// app.js
app.use('/users', router);              // barcha marshrutlar /users ostida

Keng tarqalgan xato. Marshrut tartibiga e'tibor bermaslik — masalan /users/newni /users/:iddan keyin qo'ysa, new :idga tushib qoladi. Aniqroq (statik) marshrutlar umumiylardan oldin turishi kerak.

Follow-up. "req.params, req.query, req.body farqi?"params — URL yo'lidagi parametrlar (/users/:id); query — URL'dagi ?key=value qismi; body — so'rov tanasi (POST/PUT'da, express.json() parse qiladi).


4-bo'lim. REST API dizayni

S: Yaxshi REST API'ni qanday loyihalaysiz? Resource, HTTP metod va status kodlar haqida gapiring. (Middle)

J:

Ta'rif. REST (Representational State Transfer) — resurslarni (ma'lumot obyektlari) URL orqali ifodalab, ular ustida HTTP metodlari bilan amal bajaruvchi arxitektura uslubi. Yaxshi REST API resurs-markazli, stateless va bashoratli bo'ladi.

Mexanizm (chuqur). Resurslar — otlar, fe'l emas: /users, /users/42/orders (/getUser emas). HTTP metodlar amalni bildiradi: GET (o'qish, xavfsiz, idempotent), POST (yaratish, idempotent emas), PUT (to'liq almashtirish, idempotent), PATCH (qisman yangilash), DELETE (o'chirish, idempotent). Status kodlar natijani bildiradi: 2xx muvaffaqiyat (200 OK, 201 Created, 204 No Content), 3xx yo'naltirish, 4xx client xatosi (400 noto'g'ri, 401 auth yo'q, 403 ruxsat yo'q, 404 topilmadi, 409 konflikt, 422 validatsiya), 5xx server xatosi (500, 503).

Kod misol:

js
// Resurs-markazly, to‘g‘ri metod va status:
app.get('/users',        (req, res) => res.status(200).json(users));
app.get('/users/:id',    (req, res) => user ? res.json(user) : res.status(404).json({ xato: 'topilmadi' }));
app.post('/users',       (req, res) => res.status(201).json(yangiUser));   // 201 Created
app.put('/users/:id',    (req, res) => res.status(200).json(yangilangan));
app.delete('/users/:id', (req, res) => res.status(204).end());             // 204 No Content

Keng tarqalgan xato. Hamma javobga 200 OK qaytarish — hatto xato bo'lganda ham ({ success: false } bilan). Bu REST'ni buzadi: client status kodga tayana olmaydi. Yana: URL'da fe'l ishlatish (/createUser, /deleteUser) — metod allaqachon amalni bildiradi.

Follow-up. "POST va PUT'ning idempotentlik farqi nima uchun muhim?" — Idempotent so'rovni (PUT, DELETE) tarmoq uzilib qayta yuborsa ham natija bir xil; POST esa har safar yangi resurs yaratadi — shuning uchun to'lov kabi joylarda Idempotency-Key ishlatiladi.


S: API'ni qanday versiyalaysiz? (Middle)

J:

Ta'rif. API versiyalash — mavjud clientlarni buzmasdan API'ni rivojlantirish imkonini beruvchi strategiya. Yangi, mos kelmaydigan (breaking) o'zgarish yangi versiyada chiqariladi.

Mexanizm (chuqur). Asosiy usullar: (1) URL yo'lida/api/v1/users, /api/v2/users (eng keng tarqalgan, oddiy, keshlash oson); (2) Header orqaliAccept: application/vnd.api.v2+json (URL toza, lekin ko'rish qiyinroq); (3) Query parametr/users?version=2 (kam tavsiya etiladi). Amalda URL versiyalash eng ko'p qo'llaniladi. Muhim: faqat breaking o'zgarishlarda versiya oshirish; qo'shimcha (additive) maydonlar eski versiyani buzmaydi.

Kod misol:

js
// URL orqali versiyalash (eng keng tarqalgan):
const v1 = express.Router();
const v2 = express.Router();

v1.get('/users', (req, res) => res.json({ ism: 'Ali' }));            // eski format
v2.get('/users', (req, res) => res.json({ firstName: 'Ali' }));     // yangi format

app.use('/api/v1', v1);
app.use('/api/v2', v2);

Keng tarqalgan xato. Har bir kichik o'zgarishda yangi versiya chiqarish — bu qo'llab-quvvatlash yukini oshiradi. Versiya faqat buzuvchi o'zgarishlar uchun. Yana: eski versiyani rejasiz "o'chirib" qo'yish — eski clientlar ishlamay qoladi; deprecation siyosati (muddat + ogohlantirish) kerak.

Follow-up. "Versiyalamasdan breaking o'zgarishdan qanday qochasiz?" — Additive o'zgarishlar (yangi ixtiyoriy maydonlar), eski maydonni saqlab yangisini qo'shish, feature flag'lar va soft deprecation orqali.


S: Pagination va filtering'ni qanday amalga oshirasiz? (Middle)

J:

Ta'rif. Pagination — katta ro'yxatni kichik "sahifa"larga bo'lib qaytarish (barchasini birdan emas). Filtering — client so'ragan shartlar bo'yicha natijani cheklash.

Mexanizm (chuqur). Ikki asosiy pagination strategiyasi: (1) Offset-based?page=2&limit=20 OFFSET 20 LIMIT 20. Oddiy, lekin katta offset'da sekin (baza avval barcha o'tkazilgan qatorlarni sanaydi) va ma'lumot o'zgarsa qatorlar "sakrab ketishi" mumkin. (2) Cursor-based?limit=20&after=<oxirgi_id> WHERE id > cursor LIMIT 20. Katta ma'lumotda tez va barqaror, lekin ixtiyoriy sahifaga sakrab bo'lmaydi. Filtering: ?status=active&minPrice=100 — bularni baza so'roviga parametrlangan (SQL injection'dan himoya) tarzda o'tkazish.

Kod misol:

js
// Offset-based pagination + filtering:
app.get('/products', async (req, res) => {
  const page  = Math.max(1, parseInt(req.query.page)  || 1);
  const limit = Math.min(100, parseInt(req.query.limit) || 20); // maksimal chek
  const offset = (page - 1) * limit;

  const where = {};
  if (req.query.status) where.status = req.query.status; // filter

  const [items, total] = await Promise.all([
    db.product.findMany({ where, skip: offset, take: limit }),
    db.product.count({ where }),
  ]);
  res.json({ items, page, limit, total, pages: Math.ceil(total / limit) });
});

Keng tarqalgan xato. limitga yuqori chegara qo'ymaslik — client ?limit=1000000 yuborib serverni yuklab yuborishi mumkin (DoS). Doim maksimal limit qo'ying. Yana: filter qiymatlarini to'g'ridan-to'g'ri SQL'ga qo'shish (injection); doim parametrlangan so'rov / ORM ishlating.

Follow-up. "Qachon cursor-based pagination offset'dan afzal?" — Katta, tez o'zgaruvchi ma'lumotda (masalan cheksiz scroll, feed), yuqori sahifa raqamlarida ishlash tezligi va barqarorlik kerak bo'lganda.


S: REST, GraphQL va gRPC o'rtasidagi farqni tushuntiring. Qaysi birini qachon tanlaysiz? (Senior)

J:

Ta'rif. Uchalasi ham client-server aloqa uslublari. REST — resurs va HTTP metodlarga asoslangan. GraphQL — client aynan kerakli maydonlarni so'raydigan so'rov tili. gRPC — Protocol Buffers va HTTP/2'ga asoslangan yuqori unumli RPC framework.

Mexanizm (chuqur). REST: har bir resurs o'z endpoint'iga ega; oddiy, keshlash oson (HTTP cache), lekin over-fetching (keraksiz maydonlar) va under-fetching (bir necha endpoint chaqirish) muammosi bor. GraphQL: bitta endpoint (/graphql), client so'rov sxemasi orqali aynan kerakli maydonlarni oladi; over/under-fetching yo'q, lekin keshlash murakkab, N+1 muammosi (DataLoader kerak) va so'rov murakkabligi (query complexity) nazorat qilinishi kerak. gRPC: binary (Protobuf) — REST'dagi JSON'dan kichik va tez, HTTP/2 (multiplexing, streaming), qat'iy kontrakt (.proto); lekin brauzer to'g'ridan-to'g'ri qo'llamaydi (gRPC-Web kerak), o'qish qiyinroq. Tanlov: ommaviy/public API va oddiy CRUD REST; murakkab, ko'p maydonli frontend (mobil, turli client) GraphQL; ichki mikroservis aloqasi, yuqori unumdorlik gRPC.

Kod misol:

graphql
# GraphQL: client aynan kerakli maydonlarni so‘raydi
query {
  user(id: 42) {
    ism
    orders(last: 3) { id summa }   # bitta so‘rovda user + orders
  }
}
protobuf
// gRPC: qat'iy kontrakt (.proto)
service UserService {
  rpc GetUser (UserRequest) returns (UserReply);
}
message UserRequest { int32 id = 1; }

Keng tarqalgan xato. GraphQL'ni "har doim yaxshiroq" deb tanlash — u keshlash murakkabligi, N+1 va xavfsizlik (chuqur/qimmat so'rovlar) yuk keltiradi. Oddiy CRUD uchun REST ko'pincha yetarli va soddaroq.

Follow-up. "GraphQL'dagi N+1 muammoni qanday hal qilasiz?"DataLoader bilan — u bir event loop tickdagi ko'p so'rovni birlashtirib (batch), bitta baza so'roviga aylantiradi va keshlaydi.


5-bo'lim. Autentifikatsiya va xavfsizlik

S: JWT nima? Uning tuzilishi, access/refresh token va xavfsizlik jihatlarini tushuntiring. (Senior)

J:

Ta'rif. JWT (JSON Web Token) — ikki tomon o'rtasida ma'lumotni xavfsiz uzatish uchun ochiq standart. U imzolangan (signed), o'zida ma'lumot saqlovchi (self-contained) token bo'lib, serverda holat saqlashni (stateless) talab qilmaydi.

Mexanizm (chuqur). JWT uchta qismdan iborat, nuqta bilan ajratilgan: Header (algoritm, masalan HS256/RS256) . Payload (claims — sub, exp, iat, foydalanuvchi id, rollar) . Signature (header+payload'ni maxfiy kalit bilan imzolash). Har qism base64url'da. Server tokenni tekshirganda faqat imzoni qayta hisoblab solishtiradi — bazaga murojaat kerak emas (stateless). Access token — qisqa umrli (masalan 15 daqiqa), har so'rovda yuboriladi. Refresh token — uzoq umrli (masalan 7 kun), faqat yangi access token olish uchun. Payload shifrlanmagan (faqat base64) — maxfiy ma'lumot qo'ymang.

Kod misol:

js
const jwt = require('jsonwebtoken');

// Ikki token chiqarish:
const accessToken  = jwt.sign({ sub: user.id, rol: user.rol },
                              process.env.JWT_SECRET, { expiresIn: '15m' });
const refreshToken = jwt.sign({ sub: user.id },
                              process.env.REFRESH_SECRET, { expiresIn: '7d' });

// Middleware'da tekshirish (bazaga murojaatsiz):
function auth(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  try {
    req.user = jwt.verify(token, process.env.JWT_SECRET);
    next();
  } catch {
    res.status(401).json({ xato: 'Token yaroqsiz yoki muddati o‘tgan' });
  }
}

Keng tarqalgan xato. (1) Payloadda parol/maxfiy ma'lumot saqlash — u shifrlanmagan. (2) alg: noneni qabul qilish yoki algoritmni tekshirmaslik (algorithm confusion hujumi). (3) Access token'ni juda uzoq muddatli qilish — o'g'rilganda uni bekor qilib bo'lmaydi (stateless). Qisqa access + refresh naqshi shu muammoni yumshatadi.

Follow-up. "Stateless JWT'ni qanday qilib "logout"/bekor qilasiz?" — Access qisqa umrli qilinadi; refresh token'lar serverda (Redis/baza) blocklist/allowlist orqali boshqariladi — logout'da refresh bekor qilinadi, token rotation qo'llaniladi.


J:

Ta'rif. Bu — xavfsizlik savoli. Ikki asosiy variant: localStorage (yoki sessionStorage) va HttpOnly cookie. Har birining o'z hujum yuzasi bor: XSS va CSRF.

Mexanizm (chuqur). localStorage: JavaScript orqali o'qiladi — bu qulay, lekin agar saytda XSS (zararli skript) bo'lsa, hujumchi tokenni o'qib o'g'rlaydi. HttpOnly cookie: JavaScript o'qiy olmaydi (HttpOnly bayrogi) — XSS token'ni o'g'rlay olmaydi. Lekin cookie har so'rovda avtomatik yuboriladi, bu CSRF xavfini keltiradi — buni SameSite=Strict/Lax va CSRF token bilan yumshatiladi. Umumiy eng yaxshi amaliyot: refresh token'ni HttpOnly + Secure + SameSite cookie'da, access token'ni xotirada (JS o'zgaruvchisida, localStorage emas) saqlash.

Kod misol:

js
// Refresh token'ni xavfsiz cookie'da:
res.cookie('refreshToken', refreshToken, {
  httpOnly: true,   // JS o‘qiy olmaydi  XSS himoyasi
  secure: true,     // faqat HTTPS
  sameSite: 'strict', // CSRF himoyasi
  maxAge: 7 * 24 * 60 * 60 * 1000,
});
// Access token esa javob tanasida — client uni xotirada ushlaydi (localStorage emas)
res.json({ accessToken });

Keng tarqalgan xato. Token'ni oddiy (HttpOnly'siz) cookie yoki localStorage'da saqlab, "xavfsiz" deb hisoblash. localStorage XSS'ga butunlay ochiq. Eng xavfsiz yondashuv: HttpOnly + Secure + SameSite cookie (refresh uchun) + qisqa umrli access token.

Follow-up. "SameSite=Strict va Lax farqi?"Strict — cookie faqat o'sha saytdan kelgan so'rovlarda yuboriladi (tashqi havolada ham yuborilmaydi); Lax — yuqori darajali navigatsiyada (havola bosganda) yuboriladi, lekin cross-site POST'da yo'q. Strict xavfsizrok, Lax qulayrok.


J:

Ta'rif. Session-based: server foydalanuvchi holatini (session) o'zida saqlaydi va client faqat session ID (cookie'da) yuboradi — stateful. JWT-based: barcha ma'lumot token ichida, server holat saqlamaydi — stateless.

Mexanizm (chuqur). Session: login'da server session yaratadi (xotirada yoki Redis'da), unga ID beradi, ID cookie'ga yoziladi. Har so'rovda server ID bo'yicha sessionni izlaydi (baza/Redis murojaati). Afzalligi: sessionni darhol bekor qilish oson (o'chirsangiz bo'ldi). Kamchiligi: server holat saqlashi kerak — horizontal scaling'da barcha serverlar shared session store (Redis) ishlatishi shart. JWT: server holat saqlamaydi, faqat imzoni tekshiradi — scaling oson, mikroservislarga qulay; lekin bekor qilish qiyin (token muddati tugamaguncha amal qiladi).

Kod misol:

js
// Session-based (stateful) — express-session:
app.use(session({
  store: new RedisStore({ client: redisClient }), // shared store
  secret: process.env.SESSION_SECRET,
  cookie: { httpOnly: true, secure: true, maxAge: 3600000 },
}));
app.post('/login', (req, res) => {
  req.session.userId = user.id;   // server holatni saqlaydi
  res.json({ ok: true });
});

Keng tarqalgan xato. JWT'ni "har doim yaxshiroq" deb tanlash. Agar sizga tez logout/bekor qilish va oddiy, xavfsiz holat kerak bo'lsa, session (Redis store bilan) ko'pincha soddaroq va xavfsizrok. JWT'ning stateless afzalligi mikroservis/ko'p domenda yorqin.

Follow-up. "Ko'p serverli (load-balanced) muhitda session qanday ishlaydi?" — Session store markazlashgan (Redis/Memcached) bo'lishi kerak; xotiradagi (in-memory) session ko'p serverda ishlamaydi, chunki foydalanuvchi turli serverga tushadi.


S: OAuth2 Authorization Code flow qanday ishlaydi? (Senior)

J:

Ta'rif. OAuth2 — foydalanuvchining parolini uchinchi tomonga bermasdan, unga resurslarga cheklangan ruxsat berish (delegated authorization) protokoli. Eng keng tarqalgan oqim — Authorization Code flow (masalan "Google bilan kirish").

Mexanizm (chuqur). Ishtirokchilar: Resource Owner (foydalanuvchi), Client (sizning ilova), Authorization Server (Google), Resource Server (Google API). Qadamlar: (1) Client foydalanuvchini Authorization Server'ga yo'naltiradi (client_id, redirect_uri, scope, state bilan). (2) Foydalanuvchi Google'da tizimga kirib, ruxsat beradi. (3) Google authorization codeni redirect_uriga qaytaradi. (4) Client bu codeni backend'dan o'z client_secreti bilan Google'ga yuborib, access token (va refresh token)ga almashtiradi. (5) Client access token bilan Resource Server'dan ma'lumot oladi. PKCE kengaytmasi (public client, mobil/SPA uchun) code'ni ushlab olishdan himoya qiladi. state parametri CSRF'dan himoya qiladi.

Kod misol:

js
// 1-qadam: foydalanuvchini yo‘naltirish
app.get('/auth/google', (req, res) => {
  const url = `https://accounts.google.com/o/oauth2/v2/auth?` +
    `client_id=${ID}&redirect_uri=${REDIRECT}&response_type=code` +
    `&scope=email profile&state=${csrfState}`;
  res.redirect(url);
});

// 4-qadam: code'ni token'ga almashtirish (backend, secret bilan):
app.get('/auth/google/callback', async (req, res) => {
  const { code, state } = req.query;
  // state'ni tekshirish (CSRF)...
  const { data } = await axios.post('https://oauth2.googleapis.com/token', {
    code, client_id: ID, client_secret: SECRET,
    redirect_uri: REDIRECT, grant_type: 'authorization_code',
  });
  // data.access_token bilan foydalanuvchi ma'lumotini olamiz
});

Keng tarqalgan xato. Code'ni token'ga frontend'da (client_secret bilan) almashtirish — client_secret hech qachon brauzerda bo'lmasligi kerak. Bu almashuv backend'da bajarilishi shart. SPA'lar uchun client_secretsiz PKCE oqimi ishlatiladi.

Follow-up. "state parametri nima uchun kerak?" — CSRF hujumidan himoya: client yuborgan tasodifiy stateni callback'da tekshiradi; mos kelmasa, so'rov soxta deb rad etiladi.


S: Parollarni qanday saqlash kerak? bcrypt nima uchun ishlatiladi? (Middle)

J:

Ta'rif. Parollar hech qachon ochiq matnda (yoki oddiy hash bilan) saqlanmaydi. Ular sekin, sho'r qo'shilgan (salted) hash algoritmi — masalan bcrypt, scrypt yoki Argon2 bilan hash qilinadi.

Mexanizm (chuqur). Oddiy hash (MD5, SHA-256) parol uchun noto'g'ri, chunki ular tez — hujumchi sekundiga milliardlab taxminni sinab (brute-force, rainbow table) parolni topadi. bcrypt ataylab sekin va ish faktori (cost/rounds) bilan sozlanadi — apparatura tezlashsa, cost'ni oshirasiz. Har parol uchun tasodifiy salt (sho'r) qo'shiladi — bu bir xil parollar turlicha hash berishini va rainbow table hujumini samarasiz qilishni ta'minlaydi. Salt hash'ning o'ziga kiritib saqlanadi.

Kod misol:

js
const bcrypt = require('bcrypt');

// Ro‘yxatdan o‘tishda (salt avtomatik qo‘shiladi):
const hash = await bcrypt.hash(parol, 12); // cost = 12
await db.user.create({ data: { email, parolHash: hash } });

// Kirishda solishtirish:
const mos = await bcrypt.compare(kiritilganParol, user.parolHash);
if (!mos) return res.status(401).json({ xato: 'Login yoki parol xato' });

Keng tarqalgan xato. (1) Parollarni SHA-256/MD5 bilan hash qilish — tez, brute-force'ga ochiq. (2) bcrypt.hashSyncni so'rov ishlovchisida ishlatish — u CPU-og'ir va event loop'ni bloklaydi; asinxron bcrypt.hash ishlating. (3) Login xatosida "email topilmadi" va "parol xato"ni alohida aytish — bu foydalanuvchi sanashga (enumeration) yordam beradi; umumiy xabar bering.

Follow-up. "bcrypt va Argon2 farqi?" — Argon2 (ayniqsa Argon2id) — zamonaviyrok, xotira-og'ir (memory-hard), GPU hujumlariga chidamli va OWASP tomonidan tavsiya etilgan. bcrypt ham hali xavfsiz, lekin parol 72 baytdan oshsa kesib tashlaydi.


S: RBAC (rollarga asoslangan ruxsat) nima va uni qanday amalga oshirasiz? (Middle)

J:

Ta'rif. RBAC (Role-Based Access Control) — foydalanuvchilarga rollar (admin, muharrir, foydalanuvchi) berib, ruxsatlarni (permissions) rollarga bog'lash orqali resurslarga kirishni boshqarish modeli.

Mexanizm (chuqur). Foydalanuvchi rol(lar) ruxsatlar. Autentifikatsiya (kim?) tugagandan keyin, avtorizatsiya (nima qila oladi?) tekshiriladi. Amaliyotda: token/session'da foydalanuvchi roli saqlanadi; middleware so'rovni handler'ga o'tkazishdan oldin rol/ruxsatni tekshiradi. Yirikrok tizimlarda ABAC (atributga asoslangan — kontekst, egalik, vaqt) ishlatiladi, lekin RBAC ko'p holatda yetarli.

Kod misol:

js
// Rol tekshiruvchi middleware fabrikasi:
function ruxsat(...rollar) {
  return (req, res, next) => {
    if (!rollar.includes(req.user.rol)) {
      return res.status(403).json({ xato: 'Ruxsat yo‘q' }); // 403, 401 emas
    }
    next();
  };
}

app.delete('/users/:id', auth, ruxsat('admin'), (req, res) => {
  // faqat admin bu yerga yetib keladi
  res.status(204).end();
});

Keng tarqalgan xato. (1) Avtorizatsiyani faqat frontend'da (tugmani yashirish bilan) qilish — hujumchi API'ga to'g'ridan-to'g'ri so'rov yuboradi; tekshiruv serverda bo'lishi shart. (2) 401 va 403'ni aralashtirish: 401 — kim ekanligingiz noaniq (auth yo'q); 403 — kim ekanligingiz aniq, lekin ruxsat yo'q.

Follow-up. "Foydalanuvchi faqat o'z resursini o'zgartira olishini qanday ta'minlaysiz?" — Bu ownership tekshiruvi (ABAC elementi): handler'da resurs egasi req.user.idga teng ekanligini tekshirish kerak — rol o'zi yetmaydi.


S: Rate limiting nima va nima uchun kerak? (Middle)

J:

Ta'rif. Rate limiting — ma'lum vaqt oynasida bir client (IP, foydalanuvchi, API kalit) yubora oladigan so'rovlar sonini cheklash. U DoS, brute-force va resurs suiiste'molidan himoya qiladi.

Mexanizm (chuqur). Keng tarqalgan algoritmlar: (1) Fixed window — sobit oynada (masalan daqiqada 100) sanash; oddiy, lekin oyna chegarasida "portlash" (burst) muammosi. (2) Sliding window — silliq, aniqroq. (3) Token bucket — vaqt bo'yicha to'ladigan "chelak"; qisqa portlashlarga ruxsat beradi. Ko'p serverli muhitda hisoblagich Redis'da markazlashgan bo'lishi kerak — aks holda har server alohida sanaydi. Chegaraga yetganda 429 Too Many Requests va Retry-After header qaytariladi.

Kod misol:

js
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');

const limiter = rateLimit({
  store: new RedisStore({ /* redis client */ }), // ko‘p serverli uchun
  windowMs: 15 * 60 * 1000,  // 15 daqiqa
  max: 100,                  // har IP uchun 100 so‘rov
  standardHeaders: true,     // RateLimit-* headerlar
  message: { xato: 'Juda ko‘p so‘rov, keyinroq urinib ko‘ring' },
});
app.use('/api', limiter);

// Loginga qattiqroq limit (brute-force himoyasi):
app.post('/login', rateLimit({ windowMs: 15*60*1000, max: 5 }), loginHandler);

Keng tarqalgan xato. Rate limit hisoblagichini xotirada (bitta server) saqlash — cluster/ko'p instansda har biri alohida sanaydi, chegara amalda N barobar oshadi. Redis kabi markaziy store kerak. Yana: reverse proxy ortida req.ipni to'g'ri olmaslik (trust proxy sozlamay), barcha so'rovlar bir IP'dan kelgandek ko'rinadi.

Follow-up. "Login uchun rate limit qanday bo'lishi kerak?" — Umumiy API'dan qattiqrok (masalan 5 urinish / 15 daqiqa), yaxshisi IP + hisob (account) bo'yicha, hisobni vaqtincha qulflash (lockout) bilan.


S: CORS nima va uni backend'da qanday to'g'ri sozlaysiz? (Middle)

J:

Ta'rif. CORS (Cross-Origin Resource Sharing) — brauzerning bir origin (domen+port+protokol)dan boshqa origin'dagi resursga JavaScript so'rovini standart bo'yicha bloklovchi xavfsizlik mexanizmini boshqaradigan HTTP header tizimi. Server maxsus header'lar orqali qaysi originlarga ruxsat berishini bildiradi.

Mexanizm (chuqur). Brauzer cross-origin so'rovda Origin header yuboradi; server Access-Control-Allow-Origin bilan javob beradi. "Oddiy bo'lmagan" so'rovlarda (masalan PUT, maxsus header, Content-Type: application/json ba'zan) brauzer avval preflight (OPTIONS) so'rovini yuboradi va serverdan ruxsat (Allow-Methods, Allow-Headers) so'raydi. Cookie/credentials yuborish uchun Access-Control-Allow-Credentials: true kerak va bu holda Allow-Origin * bo'la olmaydi — aniq origin ko'rsatilishi shart. Muhim: CORS — brauzer mexanizmi; u serverni himoya qilmaydi (Postman/curl CORS'ni e'tiborga olmaydi), balki foydalanuvchi brauzerini boshqa sayt nomidan qilingan so'rovlardan himoya qiladi.

Kod misol:

js
const cors = require('cors');

app.use(cors({
  origin: ['https://app.example.uz', 'https://admin.example.uz'], // aniq ro‘yxat
  credentials: true,              // cookie yuborish uchun
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
}));

Keng tarqalgan xato. origin: '*'ni credentials: true bilan birga ishlatish — brauzer buni rad etadi, va umuman * bilan cookie yuborib bo'lmaydi. Yana: CORS'ni "xavfsizlik devori" deb o'ylash — u faqat brauzerda ishlaydi; haqiqiy avtorizatsiya serverda bo'lishi kerak.

Follow-up. "Preflight so'rovini nima keltirib chiqaradi?" — "Oddiy bo'lmagan" so'rovlar: PUT/DELETE/PATCH metod, maxsus header'lar (Authorization, custom), yoki Content-Type: application/json. Oddiy GET/POST (form content-type bilan) preflight talab qilmaydi.


S: Helmet nima va backend'da yana qanday xavfsizlik choralarini ko'rasiz? (Middle)

J:

Ta'rif. Helmet — Express ilovasiga bir qator xavfsizlik HTTP header'larini avtomatik qo'shuvchi middleware. U keng tarqalgan hujumlarni (XSS, clickjacking, MIME-sniffing) yumshatadi.

Mexanizm (chuqur). Helmet bir necha header o'rnatadi: Content-Security-Policy (qaysi manbalardan skript/stil yuklashga ruxsat — XSS'ni cheklaydi), X-Frame-Options (clickjacking — saytni iframe'ga joylashni taqiqlaydi), Strict-Transport-Security (HSTS — faqat HTTPS), X-Content-Type-Options: nosniff (MIME-sniffing), X-Powered-Byni o'chirish (server texnologiyasini yashirish). Bulardan tashqari umumiy xavfsizlik choralari: kirishni validatsiya, parametrlangan so'rovlar (SQL injection), rate limiting, secret'larni env'da saqlash, dependency'larni yangilab turish (npm audit).

Kod misol:

js
const helmet = require('helmet');

app.use(helmet());  // barcha standart xavfsizlik headerlari

// CSP'ni moslashtirish:
app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", 'https://apis.google.com'],
    imgSrc: ["'self'", 'data:'],
  },
}));

Keng tarqalgan xato. Helmet'ni qo'shib, "xavfsizlik hal bo'ldi" deb hisoblash. Helmet faqat header'lar — u injection, avtorizatsiya xatolari yoki zaif parollarni tuzatmaydi. Xavfsizlik — ko'p qatlamli (defense in depth). Yana: CSP'ni unsafe-inline bilan yumshatib, XSS himoyasini yo'qqa chiqarish.

Follow-up. "OWASP Top 10'dan qaysi birini backend'da birinchi navbatda hisobga olasiz?" — Injection (SQL/NoSQL), buzilgan autentifikatsiya/avtorizatsiya (broken access control), va xavfsiz bo'lmagan dependency'lar — bular eng ko'p uchraydi.


S: Kirish ma'lumotini (input) nega va qanday validatsiya qilasiz? (Middle)

J:

Ta'rif. Validatsiya — client yuborgan ma'lumot kutilgan shakl, tur va chegaralarga mos kelishini serverda tekshirish. "Hech qachon clientga ishonma" tamoyili.

Mexanizm (chuqur). Validatsiya serverda bo'lishi shart (frontend validatsiya faqat UX uchun — uni chetlab o'tish oson). U ikki qismdan: validation (tur, majburiylik, format, uzunlik, oraliq) va sanitization (xavfli belgilarni tozalash/normalizatsiya). Bu injection, ma'lumot buzilishi va noaniq xatolarning oldini oladi. Amaliyotda sxema kutubxonalari — Zod, Joi, class-validator (Nest) — ishlatiladi; ular deklarativ va qayta ishlatiluvchi.

Kod misol:

js
const { z } = require('zod');

const RoyxatSxema = z.object({
  email: z.string().email(),
  parol: z.string().min(8).max(72),
  yosh:  z.number().int().min(13).max(120),
});

app.post('/register', (req, res) => {
  const natija = RoyxatSxema.safeParse(req.body);
  if (!natija.success) {
    return res.status(400).json({ xatolar: natija.error.flatten() });
  }
  const { email, parol, yosh } = natija.data; // endi ishonchli, tiplangan
  // ...
});

Keng tarqalgan xato. Faqat frontend'da validatsiya qilib, serverda ishonish. Hujumchi Postman/curl bilan istagan ma'lumotni yuboradi. Yana: qora ro'yxat (blacklist) bilan tozalash ("faqat yomon belgilarni olib tashlash") — u to'liq emas; oq ro'yxat (whitelist — faqat ruxsat etilganini qabul qilish) ishonchliroq.

Follow-up. "Validatsiyani marshrut handler'idan qanday ajratasiz?" — Sxema-asosli validation middleware yaratib (masalan validate(schema)), uni marshrutga biriktirish orqali — bu qayta ishlatish va tozalikni beradi.


6-bo'lim. Arxitektura va ma'lumotlar bazasi

S: Global error handling'ni Express'da qanday tashkil qilasiz? (Middle)

J:

Ta'rif. Global (markazlashgan) error handler — barcha marshrutlardagi xatolarni bitta joyda ushlab, izchil javob (status + xabar) qaytaruvchi maxsus middleware. Bu takrorlangan try/catchlarni kamaytiradi.

Mexanizm (chuqur). Express'da 4 argumentli (err, req, res, next) middleware — error handler. Marshrutda xato yuz bersa va next(err) chaqirilsa (yoki async wrapper orqali), Express oddiy middleware'larni o'tkazib yuborib, to'g'ridan-to'g'ri error handler'ga o'tadi. U yerda xato turiga qarab status kod tanlanadi (masalan validatsiya 400, topilmadi 404, aks holda 500), xato log qilinadi, va maxfiy ichki tafsilotsiz javob beriladi. Maxsus xato klasslari (AppError) statik va operatsion xatolarni ajratishga yordam beradi.

Kod misol:

js
// Async xatolarni next(err)'ga uzatuvchi wrapper:
const asyncWrap = (fn) => (req, res, next) =>
  Promise.resolve(fn(req, res, next)).catch(next);

app.get('/users/:id', asyncWrap(async (req, res) => {
  const user = await db.user.find(req.params.id);
  if (!user) throw new AppError('Foydalanuvchi topilmadi', 404);
  res.json(user);
}));

// Markazlashgan error handler (eng oxirida, 4 argument):
app.use((err, req, res, next) => {
  const status = err.statusCode || 500;
  logger.error({ err, url: req.url });          // ichki log
  res.status(status).json({
    xato: err.isOperational ? err.message : 'Ichki server xatosi', // maxfiylik
  });
});

Keng tarqalgan xato. (1) Error handler'ni 3 argument bilan yozib, Express uni oddiy middleware deb qabul qilishi. (2) Client'ga to'liq stack trace/baza xatosini qaytarish — bu ichki tuzilishni fosh qiladi (xavfsizlik). (3) Async xatolarni wrapper'siz qoldirib, ular ushlanmay qolishi (Express 4).

Follow-up. "Operatsion xato va dasturchilik xatosini qanday ajratasiz?" — Operatsion (kutilgan: validatsiya, 404) — client'ga aniq xabar bersa bo'ladi; dasturchilik (bug, kutilmagan) — umumiy 500 berib, log qilib, ehtimol protsessni qayta ishga tushirish (agar holat buzilgan bo'lsa).


S: Backend'da logging'ni qanday tashkil qilasiz? console.log nega yetarli emas? (Middle)

J:

Ta'rif. Logging — ilova ishlashi, xatolar va muhim hodisalarni tuzilgan (structured) tarzda yozib borish. Prodakshnda console.log o'rniga maxsus logger (Pino, Winston) ishlatiladi.

Mexanizm (chuqur). console.log muammolari: (1) sinxron (terminalga/faylga yozganda bloklashi mumkin); (2) daraja (level) yo'q — debug/info/error ajratib bo'lmaydi; (3) tuzilmagan matn — mashinada (log agregator: ELK, Loki, Datadog) qidirish qiyin. Yaxshi logger: darajalar (trace, debug, info, warn, error, fatal), JSON format (structured — maydonlar bo'yicha filter/qidiruv), kontekst (request ID, foydalanuvchi ID — correlation), va transport (fayl, stdout, tashqi tizim). Pino — Node'da eng tez, JSON logger.

Kod misol:

js
const pino = require('pino');
const logger = pino({ level: process.env.LOG_LEVEL || 'info' });

// Har so‘rovga request ID biriktirib, kuzatuvchanlik (traceability):
app.use((req, res, next) => {
  req.log = logger.child({ reqId: crypto.randomUUID() });
  next();
});

app.get('/order/:id', (req, res) => {
  req.log.info({ orderId: req.params.id }, 'Buyurtma so‘raldi'); // JSON log
  res.json({ ok: true });
});

Keng tarqalgan xato. (1) Log'ga maxfiy ma'lumot (parol, token, karta raqami) yozish — bu jiddiy xavfsizlik/maxfiylik buzilishi; maxfiy maydonlarni yashiring (redact). (2) Prodakshnda debug darajasini yoqib, log'ni to'ldirib yuborish. (3) Faqat matnli (unstructured) log — agregatorda qidirish imkonsiz.

Follow-up. "Distributed (mikroservis) tizimda bitta so'rovni qanday kuzatasiz?"Correlation/trace IDni so'rov boshida yaratib, barcha servislar bo'ylab (header orqali) uzatish; OpenTelemetry kabi distributed tracing tizimi bilan.


S: Environment o'zgaruvchilarni (config) qanday boshqarasiz? (Junior)

J:

Ta'rif. Environment o'zgaruvchilar — kod tashqarisida (muhit sozlamalarida) saqlanadigan konfiguratsiya: baza URL'i, maxfiy kalitlar, port, muhit turi. Ular kod bilan aralashmaydi va muhitga qarab o'zgaradi (dev/staging/prod).

Mexanizm (chuqur). The Twelve-Factor App tamoyili: config koddan ajratib, environment'da saqlanadi. Node'da ular process.env orqali o'qiladi. Lokal ishlashda .env fayli (dotenv bilan yuklanadi) ishlatiladi, lekin u hech qachon git'ga qo'shilmaslik kerak (.gitignore). Prodakshnda o'zgaruvchilar server/konteyner muhitida (yoki secret manager: Vault, AWS Secrets Manager) beriladi. Ishga tushishda majburiy o'zgaruvchilar borligi tekshirilishi (validatsiya) kerak — yo'q bo'lsa, tez qulash (fail fast) yashiringan xatodan yaxshiroq.

Kod misol:

js
require('dotenv').config();
const { z } = require('zod');

// Config'ni ishga tushishda validatsiya (fail fast):
const env = z.object({
  PORT: z.coerce.number().default(3000),
  DATABASE_URL: z.string().url(),
  JWT_SECRET: z.string().min(32),
  NODE_ENV: z.enum(['development', 'production', 'test']),
}).parse(process.env);

module.exports = env; // butun ilova shu tekshirilgan config'dan foydalanadi

Keng tarqalgan xato. (1) Maxfiy kalitlarni kodda yoki git'da saqlash (.envni commit qilish) — eng ko'p uchraydigan jiddiy tashki sizish sababi. (2) process.env.Xni butun kodda tarqatib ishlatish — o'rniga markaziy, validatsiya qilingan config moduli yaxshiroq.

Follow-up. "Prodakshnda .env fayli ishlatiladimi?" — Odatda yo'q; o'zgaruvchilar to'g'ridan-to'g'ri muhitga (Docker env, Kubernetes secrets, CI/CD) yoki secret manager orqali beriladi. .env asosan lokal rivojlantirish uchun.


S: Dependency Injection (DI) nima va NestJS'da u qanday ishlaydi? (Senior)

J:

Ta'rif. Dependency Injection — obyekt o'z bog'liqliklarini (dependencies) o'zi yaratmay, ular tashqaridan (konstruktor orqali) berilishi. Bu bog'liqlikni kamaytiradi (loose coupling) va testlashni osonlashtiradi.

Mexanizm (chuqur). DI'siz sinf o'z bog'liqligini ichida yaratadi (new UserRepository()) — bu qat'iy bog'lanish (tight coupling), testda mock qilib bo'lmaydi. DI bilan bog'liqlik tashqaridan uzatiladi. NestJS'da buni IoC (Inversion of Control) konteyner boshqaradi: @Injectable() bilan belgilangan sinflar konteynerga ro'yxatdan o'tadi (provider); Nest ularning bog'liqliklarini konstruktor tiplariga qarab avtomatik hal qiladi (resolve) va kiritadi. Standart bo'yicha provider'lar singleton (bitta instans butun ilova bo'ylab). Bu modulyar, testlasa bo'ladigan (mock provider) arxitektura beradi.

Kod misol:

ts
@Injectable()
export class UsersService {
  // Nest UsersRepository'ni avtomatik yaratib, konstruktorga kiritadi:
  constructor(private readonly repo: UsersRepository) {}

  findOne(id: number) {
    return this.repo.findById(id);
  }
}

@Module({
  providers: [UsersService, UsersRepository], // IoC konteynerga ro‘yxat
  controllers: [UsersController],
})
export class UsersModule {}

Keng tarqalgan xato. (1) Sinf ichida bog'liqlikni new bilan yaratib, DI'ni chetlab o'tish — testda mock qilib bo'lmaydi. (2) Provider'ni modulda ro'yxatdan o'tkazmay, "Nest can't resolve dependencies" xatosiga uchrash. (3) Aylanma (circular) bog'liqlik yaratib qolish — forwardRef kerak bo'ladi, lekin bu dizayn muammosiga ishora.

Follow-up. "DI testlashni qanday osonlashtiradi?" — Test'da haqiqiy bog'liqlik o'rniga mock/stub provider kiritiladi (masalan soxta repository) — shu bilan servisni baza yoki tarmoqsiz, izolyatsiyada test qilsa bo'ladi.


S: MVC arxitekturasini tushuntiring. Backend'da u qanday qo'llaniladi? (Middle)

J:

Ta'rif. MVC (Model-View-Controller) — ilovani uch mas'uliyat qatlamiga ajratuvchi naqsh: Model (ma'lumot va biznes qoidalar), View (taqdimot), Controller (so'rovni qabul qilib, Model'ni ishlatib, javob qaytarish).

Mexanizm (chuqur). API backend'da (View odatda JSON javob): Controller — HTTP so'rovni qabul qiladi, kirishni validatsiya qiladi, tegishli Service/Model'ni chaqiradi, natijani formatlab qaytaradi (biznes mantiq yozmaslik kerak). Service (ko'pincha qo'shimcha qatlam) — biznes mantiq shu yerda. Model/Repository — ma'lumotlar bazasi bilan ishlash. Bu ajratish (separation of concerns) kodni testlasa bo'ladigan, qo'llab-quvvatlasa qulay qiladi. Amaliyotda ko'pincha Controller Service Repository qatlamli arxitektura ishlatiladi.

Kod misol:

js
// Controller: faqat HTTP bilan ishlaydi
class UserController {
  async getUser(req, res, next) {
    try {
      const user = await userService.getById(req.params.id); // biznes mantiq emas
      res.json(user);
    } catch (e) { next(e); }
  }
}

// Service: biznes mantiq
class UserService {
  async getById(id) {
    const user = await userRepository.findById(id); // ma'lumotga murojaat
    if (!user) throw new AppError('topilmadi', 404);
    return user;
  }
}

Keng tarqalgan xato. "Fat controller" — biznes mantiq, baza so'rovlari, validatsiyani hammasini controller'ga tiqish. Bu testlashni qiyinlashtiradi va takrorlanishga olib keladi. Mantiqni Service qatlamiga ko'chiring.

Follow-up. "Service va Repository qatlamlari nima uchun alohida?" — Repository ma'lumot manbaini (baza turi) abstraktsiya qiladi — bazani almashtirish yoki test uchun mock qilish osonlashadi; Service esa manbaga bog'liqsiz biznes mantiqni saqlaydi.


S: ORM'da N+1 muammosi nima va uni qanday hal qilasiz? (Senior)

J:

Ta'rif. N+1 query muammosi — ORM ro'yxatni olib (1 so'rov), keyin har bir elementning bog'liq ma'lumotini alohida so'rov bilan olganda (N so'rov) yuzaga keladigan unumdorlik muammosi. Natijada 1 o'rniga 1+N so'rov ketadi.

Mexanizm (chuqur). ORM'lar (Prisma, TypeORM, Sequelize) bog'liq maydonlarni "yalqov" (lazy) yuklaydi. Masalan 100 ta postni olib, har birining muallifiga murojaat qilsa — ORM 1 (postlar) + 100 (har muallif) = 101 so'rov yuboradi. Bazaga 100 marta borib-kelish — juda sekin. Yechim: eager loading / join — bog'liq ma'lumotni bitta (yoki bir necha) so'rovda oldindan yuklash. Prisma'da include, TypeORM'da relations/leftJoinAndSelect, Sequelize'da include. GraphQL'da esa DataLoader batching ishlatiladi.

Kod misol:

js
//  N+1: 1 + 100 so‘rov
const posts = await prisma.post.findMany({ take: 100 });
for (const post of posts) {
  post.muallif = await prisma.user.findUnique({ where: { id: post.authorId } });
}

//  Eager loading: 1 (yoki 2) so‘rov
const posts = await prisma.post.findMany({
  take: 100,
  include: { muallif: true }, // bog‘liq ma'lumot bitta so‘rovda
});

Keng tarqalgan xato. N+1'ni sezmaslik — kichik ma'lumotda u tez ishlaydi, prodakshnda ma'lumot o'sganda to'satdan sekinlashadi. Buni so'rov loglarini yoqib (masalan Prisma log: ['query']) yoki APM bilan aniqlanadi. Yana: teskari — har doim hamma bog'liqlikni eager yuklab, keraksiz ma'lumotni tortib (over-fetching).

Follow-up. "Ko'p qatlamli bog'liqlikda (masalan post izohlar muallif) N+1'ni qanday hal qilasiz?" — Nested include/join bilan yoki DataLoader'larni har qatlam uchun batching qilib; ba'zan murakkab holatda to'g'ridan-to'g'ri optimallashgan raw SQL yaxshiroq.


S: Caching nima uchun kerak va Redis'ni qanday ishlatasiz? (Middle)

J:

Ta'rif. Caching — tez-tez so'raladigan yoki hisoblash qimmat ma'lumotni tezroq (odatda xotirada, masalan Redis) saqlab, takroriy so'rovlarda bazani aylanib o'tish. Bu latency va baza yukini kamaytiradi.

Mexanizm (chuqur). Eng keng tarqalgan naqsh — cache-aside (lazy loading): (1) so'rovda avval cache tekshiriladi; (2) topilsa (cache hit) — darhol qaytariladi; (3) topilmasa (cache miss) — bazadan olinadi, cache'ga yoziladi (TTL bilan) va qaytariladi. Muhim masalalar: TTL (ma'lumot necha vaqt yashaydi — eskirish), invalidation (ma'lumot o'zgarganda cache'ni yangilash/o'chirish — "kompyuter fanining eng qiyin ikki muammosidan biri"), va stale data (eskirgan ma'lumot) xavfi. Redis — xotirada ishlaydigan kalit-qiymat store, tez, TTL va turli struktura (string, hash, sorted set) qo'llaydi.

Kod misol:

js
const redis = require('redis');
const client = redis.createClient();

async function mahsulotOl(id) {
  const kalit = `mahsulot:${id}`;
  const kesh = await client.get(kalit);
  if (kesh) return JSON.parse(kesh);          // cache hit

  const mahsulot = await db.product.find(id); // cache miss  bazadan
  await client.set(kalit, JSON.stringify(mahsulot), { EX: 3600 }); // TTL 1 soat
  return mahsulot;
}

// Ma'lumot o‘zgarganda cache'ni bekor qilish:
async function yangilash(id, data) {
  await db.product.update(id, data);
  await client.del(`mahsulot:${id}`);         // invalidation
}

Keng tarqalgan xato. (1) Cache invalidation'ni unutish — ma'lumot o'zgarsa ham eski (stale) qiymat qaytariladi. (2) TTL qo'ymay, xotira to'lib ketishi. (3) Cache stampede — mashhur kalit muddati tugganda minglab so'rov birdan bazaga urilishi; buni "lock" yoki "early recompute" bilan yumshatiladi.

Follow-up. "Write-through va cache-aside farqi?" — Cache-aside'da ilova cache'ni o'zi boshqaradi (miss'da yuklaydi); write-through'da har yozish ham bazaga ham cache'ga birga boradi (cache doim yangi, lekin yozish sekinrok).


S: Message queue (BullMQ) nima uchun kerak va u qanday ishlaydi? (Senior)

J:

Ta'rif. Message queue — vazifalarni (job) darhol bajarmay, navbatga qo'yib, fonda (background) alohida "worker" tomonidan bajarish imkonini beruvchi tizim. BullMQ — Redis'ga asoslangan Node'dagi mashhur queue kutubxonasi.

Mexanizm (chuqur). Ba'zi ishlar (email yuborish, rasm qayta ishlash, hisobot yaratish, tashqi API chaqiruvi) sekin yoki ishonchsiz. Ularni HTTP so'rovi ichida bajarish foydalanuvchini kuttiradi va serverni bloklaydi. Queue bilan: so'rov ishni navbatga qo'shadi (produce) va darhol javob qaytaradi; alohida worker protsess(lar) navbatdan ishni olib (consume) bajaradi. BullMQ Redis'da vazifalarni saqlaydi va retry (qayta urinish), backoff (kutib qayta urinish), rate limiting, kechiktirilgan (delayed) va takroriy (repeatable/cron) job, priority va concurrency kabi imkonlarni beradi. Bu ishonchlik (job yo'qolmaydi) va gorizontal masshtablashni (worker'lar qo'shish) beradi.

Kod misol:

js
const { Queue, Worker } = require('bullmq');
const connection = { host: 'localhost', port: 6379 };

// Producer: HTTP so‘rovida ishni navbatga qo‘shamiz (tez javob):
const emailQueue = new Queue('email', { connection });
app.post('/register', async (req, res) => {
  await createUser(req.body);
  await emailQueue.add('welcome', { email: req.body.email },
                       { attempts: 3, backoff: { type: 'exponential', delay: 1000 } });
  res.status(201).json({ ok: true }); // email fonda ketadi
});

// Worker: alohida protsessda ishni bajaradi
new Worker('email', async (job) => {
  await sendEmail(job.data.email); // sekin ish, so‘rovni bloklamaydi
}, { connection, concurrency: 5 });

Keng tarqalgan xato. (1) Sekin/ishonchsiz ishlarni (email, tashqi API) to'g'ridan-to'g'ri so'rov ichida bajarish — foydalanuvchi kutadi, xato bo'lsa so'rov quladi. (2) Worker'ni idempotent qilmaslik — retry'da ish ikki marta bajarilib (masalan ikki email), muammo keltiradi. (3) Failed job'larni kuzatmaslik (dead-letter/monitoring yo'q).

Follow-up. "Job ikki marta bajarilmasligini qanday ta'minlaysiz?" — Worker'ni idempotent yozish (masalan jobId yoki biznes kalit bo'yicha "allaqachon bajarilgan"ni tekshirish), yoki BullMQ'ning unique job / deduplication imkonidan foydalanish.


7-bo'lim. Real-time, fayllar va prodakshnga tayyorlik

S: WebSocket nima va u HTTP'dan nimasi bilan farq qiladi? (Middle)

J:

Ta'rif. WebSocket — client va server o'rtasida ikki tomonlama (full-duplex), doimiy ochiq ulanish o'rnatuvchi protokol. U real-time (chat, bildirishnoma, o'yin, jonli narx) ilovalar uchun.

Mexanizm (chuqur). HTTP — so'rov-javob modeli: client so'raydi, server javob beradi, ulanish yopiladi (yoki keep-alive bilan qayta ishlatiladi), lekin server o'zi tashabbus bilan ma'lumot yubora olmaydi. WebSocket esa HTTP orqali handshake (Upgrade: websocket header) bilan boshlanadi, so'ng ulanish doimiy ochiq TCP kanalga aylanadi — har ikki tomon istagan vaqtda xabar yubora oladi, qayta so'rov (va header overhead)siz. Server bir necha clientga bir vaqtda push qila oladi. Node'da ws yoki Socket.IO (avtomatik qayta ulanish, fallback, xonalar bilan) ishlatiladi. Ko'p serverli muhitda xabarni barcha instans bo'ylab tarqatish uchun Redis pub/sub adapter kerak.

Kod misol:

js
const { Server } = require('socket.io');
const io = new Server(httpServer);

io.on('connection', (socket) => {
  console.log('Client ulandi:', socket.id);

  socket.on('xabar', (data) => {
    io.emit('xabar', data);   // barcha clientlarga push (broadcast)
  });

  socket.on('disconnect', () => console.log('Uzildi:', socket.id));
});

Keng tarqalgan xato. (1) Real-time kerak bo'lmaganda ham WebSocket ishlatish — oddiy holatda HTTP polling yoki SSE yetarli va soddaroq. (2) Ko'p serverda Redis adapter'siz ishlatib, faqat bir serverga ulangan clientlar xabarni olishi. (3) WebSocket ulanishlarida ham autentifikatsiya/avtorizatsiyani unutish (handshake'da token tekshirish kerak).

Follow-up. "WebSocket va Server-Sent Events (SSE) farqi?" — SSE — faqat bir tomonlama (server client), oddiy HTTP ustida, avtomatik qayta ulanish bilan; faqat serverdan push kerak bo'lsa (masalan bildirishnoma feed) yetarli va soddaroq. WebSocket — ikki tomonlama.


S: Fayl yuklashni (file upload) backend'da qanday boshqarasiz? (Middle)

J:

Ta'rif. Fayl yuklash — client (odatda multipart/form-data) orqali fayl yuborganda, uni serverda qabul qilib, saqlash (disk, xotira yoki bulut). Node'da ko'pincha Multer (Express uchun) ishlatiladi.

Mexanizm (chuqur). Fayllar multipart/form-data sifatida bo'laklarda keladi. Multer bu oqimni parse qilib, faylni diskka, xotiraga yoki to'g'ridan-to'g'ri bulutga (S3) yozadi. Muhim qarorlar: (1) katta fayllarni xotiraga emas, oqim (stream) orqali diskka/bulutga yozish (aks holda katta fayl RAM'ni to'ldiradi); (2) chegaralar — fayl hajmi, soni; (3) validatsiya — fayl turi (MIME + kengaytma), lekin haqiqiy tur kontentni tekshirib (magic bytes), faqat kengaytmaga ishonmay; (4) yuklangan fayl nomini tozalash (path traversal — ../../etc/passwd xavfi). Prodakshnda fayllar odatda to'g'ridan-to'g'ri object storage (S3) yoki presigned URL orqali yuklanadi — server orqali o'tkazmay.

Kod misol:

js
const multer = require('multer');
const upload = multer({
  dest: 'uploads/',
  limits: { fileSize: 5 * 1024 * 1024 },  // 5 MB chegara
  fileFilter: (req, file, cb) => {
    const ruxsat = ['image/jpeg', 'image/png'];
    cb(null, ruxsat.includes(file.mimetype)); // tur tekshiruvi
  },
});

app.post('/avatar', upload.single('rasm'), (req, res) => {
  res.json({ fayl: req.file.filename }); // req.file — yuklangan fayl
});

Keng tarqalgan xato. (1) Fayl hajmiga chegara qo'ymay — hujumchi ulkan fayl bilan diskni/xotirani to'ldiradi (DoS). (2) Faqat Content-Type header/kengaytmaga ishonib tur aniqlash — uni soxtalashtirish oson; kontentni (magic bytes) tekshirish kerak. (3) Foydalanuvchi bergan fayl nomini o'zgartirmasdan ishlatish — path traversal va ustidan yozish (overwrite) xavfi; nomni generatsiya qiling (UUID).

Follow-up. "Katta fayllarni serverni yuklamasdan qanday yuklaysiz?"Presigned URL bilan: server client'ga vaqtinchalik imzolangan S3 URL beradi, client faylni to'g'ridan-to'g'ri bulutga yuklaydi — server o'tkazuvchi (proxy) bo'lmaydi.


S: Graceful shutdown nima va uni nima uchun amalga oshirasiz? (Senior)

J:

Ta'rif. Graceful shutdown (muloyim to'xtash) — server to'xtatish signalini (SIGTERM) olganda, darhol o'lmasdan: yangi so'rovlarni qabul qilishni to'xtatib, joriy so'rovlarni tugatib, ulanishlarni (baza, Redis) toza yopib, so'ng chiqish.

Mexanizm (chuqur). Deploy, autoscaling yoki konteyner qayta ishga tushishida orkestrator (Kubernetes, PM2) protsessga SIGTERM yuboradi, so'ng ma'lum muddat (grace period)dan keyin SIGKILL (majburiy). Agar protsess signalni e'tiborga olmay darhol o'lsa: joriy so'rovlar uziladi (foydalanuvchi xato ko'radi), baza tranzaksiyasi yarim qoladi, ulanishlar to'g'ri yopilmaydi. Graceful shutdown qadamlari: (1) SIGTERM'ni tinglash; (2) server.close() — yangi ulanishni to'xtatib, joriylarni tugatishni kutish; (3) baza/queue/Redis ulanishlarini yopish; (4) muddat ichida tugamasa, majburiy chiqish (timeout). Bu zero-downtime deploy uchun zarur.

Kod misol:

js
const server = app.listen(3000);

async function gracefulShutdown(signal) {
  console.log(`${signal} qabul qilindi, muloyim to‘xtayapman...`);
  server.close(async () => {          // yangi so‘rov yo‘q, joriylarni kutadi
    await db.disconnect();            // ulanishlarni toza yopish
    await redis.quit();
    process.exit(0);
  });
  // Agar 10s ichida tugamasa — majburiy chiqish:
  setTimeout(() => process.exit(1), 10_000).unref();
}

process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT',  () => gracefulShutdown('SIGINT'));

Keng tarqalgan xato. (1) Signalni umuman boshqarmaslik — deploy'da joriy so'rovlar uziladi. (2) server.close() chaqirib, lekin baza/queue ulanishlarini yopmaslik — resurs sizishi. (3) Timeout qo'ymay — biror osilib qolgan so'rov protsessni abadiy tirik ushlab, orkestrator SIGKILL bilan qattiq o'ldiradi.

Follow-up. "Kubernetes'da graceful shutdown bilan qanday muvofiqlashtirasiz?"terminationGracePeriodSecondsni shutdown timeoutdan katta qo'yish; preStop hook va readiness probe'ni "not ready" qilib, load balancer yangi trafikni to'xtatishiga vaqt berish.


S: Node.js ilovasini qanday gorizontal masshtablaysiz? "Stateless" nima uchun muhim? (Senior)

J:

Ta'rif. Gorizontal masshtablash (scale out) — bitta serverni kuchaytirish (vertical) o'rniga, ko'p bir xil server instanslarini ishga tushirib, yukni ular orasida load balancer orqali taqsimlash. Bu Node'ning single-thread cheklovini va bitta mashina chegarasini yengib o'tadi.

Mexanizm (chuqur). Node bitta oqimda bo'lgani uchun bitta protsess bir CPU yadrosidan ko'pini ishlata olmaydi. Yechim: ko'p instans (cluster bitta mashinada, yoki ko'p konteyner/mashina) + load balancer (Nginx, HAProxy, cloud LB) yukni round-robin/least-connections bilan tarqatadi. Buning ishlashi uchun instanslar stateless bo'lishi shart: hech qanday so'rovga oid holat (session, cache, yuklangan fayl, WebSocket ulanishi) mahalliy xotirada saqlanmasligi kerak — chunki keyingi so'rov boshqa instansga tushishi mumkin. Holat tashqi umumiy tizimlarda saqlanadi: session/cache Redis, fayllar S3/object storage, ma'lumot umumiy baza, real-time Redis pub/sub adapter. Shunda istagan instans istagan so'rovga javob bera oladi.

Kod misol:

js
//  Stateful — masshtablanmaydi (holat mahalliy xotirada):
const sessions = {};
app.post('/login', (req, res) => { sessions[id] = user; }); // boshqa instans ko‘rmaydi

//  Stateless — holat Redis'da, istagan instans o‘qiy oladi:
app.use(session({ store: new RedisStore({ client: redis }) }));
// fayllar  S3, cache  Redis, ma'lumot  umumiy baza

Keng tarqalgan xato. (1) Holatni (session, in-memory cache, rate-limit hisoblagich, yuklangan fayl) mahalliy xotirada saqlab, keyin masshtablashga urinish — foydalanuvchilar tasodifiy "logout" bo'ladi, cache mos kelmaydi. (2) Sticky session (bir foydalanuvchini doim bir serverga bog'lash)ga tayanish — bu masshtablash va xatoga chidamlikni (bir server qulasa holat yo'qoladi) buzadi. To'g'ri yechim — haqiqiy statelesslik.

Follow-up. "Autoscaling qaysi metrikalar bo'yicha ishlaydi?" — Odatda CPU/memory foizi, so'rov soni (RPS), yoki latency/queue uzunligi bo'yicha; Kubernetes HPA yoki cloud autoscaler yangi instans qo'shadi/olib tashlaydi.


S: Node.js'da xotira sizishini (memory leak) qanday aniqlab, tuzatasiz? (Senior)

J:

Ta'rif. Memory leak (xotira sizishi) — dastur endi kerak bo'lmagan obyektlarga havolani (reference) ushlab turishi sababli, garbage collector (GC) ularni tozalay olmay, xotira iste'moli asta-sekin o'sib borishi. Oxiri OOM (out of memory) va qulash.

Mexanizm (chuqur). V8'da GC "yetib bo'lmaydigan" (unreachable) obyektlarni tozalaydi. Agar obyektga hali havola bo'lsa — u "tirik" hisoblanadi va tozalanmaydi. Node'da tez-tez uchraydigan sabablar: (1) cheksiz o'suvchi global massiv/obyekt yoki cache (TTL/chek yo'q); (2) olib tashlanmagan event listenerlar (emitter.on har safar qo'shilib, off qilinmay); (3) closurelar katta obyektni ushlab qolishi; (4) tugamagan timerlar. Aniqlash: xotira o'sishini kuzatish (process.memoryUsage().heapUsed, APM), heap snapshot olib (Chrome DevTools yoki --inspect), ikki snapshot orasidagi farqni (retained obyektlar) tahlil qilish, yoki --heap-prof / Clinic.js ishlatish. Tuzatish — havolani uzish (listener'ni olib tashlash, cache'ga chek/TTL qo'yish).

Kod misol:

js
//  Leak: har so‘rovda listener qo‘shiladi, hech qachon olinmaydi:
app.get('/x', (req, res) => {
  emitter.on('event', () => res.write('...')); // to‘planib boraveradi
});

//  Leak: cheksiz o‘suvchi cache (TTL/chek yo‘q):
const cache = {};
app.get('/item/:id', (req, res) => { cache[req.params.id] = kattaObyekt; });

//  Chekli cache (LRU) va listenerni tozalash:
const LRU = require('lru-cache');
const cache = new LRU({ max: 1000, ttl: 60_000 }); // chek + TTL

Keng tarqalgan xato. (1) Xotira "o'sdi" deb darhol leak deb xulosa qilish — GC hali ishlamagan bo'lishi mumkin; barqaror o'sish trendini (bir necha GC dan keyin ham tushmay) kuzatish kerak. (2) Global/modul darajasidagi massiv/obyektga ma'lumot to'plab, hech qachon tozalamaslik. (3) --max-old-space-sizeni oshirib, muammoni "yashirish" — bu sizishni tuzatmaydi, faqat kechiktiradi.

Follow-up. "Heap snapshot'da nimaga e'tibor berasiz?" — Vaqt o'tishi bilan soni yoki hajmi o'sib boruvchi obyekt turlari (retained size), ayniqsa kutilmagan "detached" DOM/obyekt, closure va massivlar — ularni ushlab turgan havola zanjirini (retainer path) topish.


8-bo'lim. Ekotizim va npm

S: package.json, npm va semantik versiyalash (semver) nima? (Junior)

J:

Ta'rif. package.json — Node loyihasining "pasporti": nomi, versiyasi, bog'liqliklari (dependencies), skriptlar va metama'lumot. npm — Node paket menejeri. Semver (semantic versioning) — MAJOR.MINOR.PATCH (masalan 2.4.1) ko'rinishidagi versiya standarti.

Mexanizm (chuqur). Semver qoidalari: MAJOR — mos kelmaydigan (breaking) o'zgarish; MINOR — orqaga mos, yangi funksiya; PATCH — orqaga mos, xato tuzatish. package.jsondagi oraliq belgilari: ^1.2.3 — MINOR va PATCH yangilanishlarga ruxsat (<2.0.0); ~1.2.3 — faqat PATCH (<1.3.0); 1.2.3 — aniq versiya. package-lock.json — o'rnatilgan aniq versiya daraxtini qulflaydi, shu bilan har bir muhitda (dev, CI, prod) bir xil paketlar o'rnatilishini (reproducible build) ta'minlaydi. dependencies — prodakshnda kerak; devDependencies — faqat rivojlantirish (test, linter, build).

Kod misol:

json
{
  "name": "mening-api",
  "version": "1.4.2",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js",
    "test": "jest"
  },
  "dependencies": { "express": "^4.19.0" },
  "devDependencies": { "jest": "^29.7.0", "nodemon": "^3.0.0" }
}

Keng tarqalgan xato. (1) package-lock.jsonni git'ga qo'shmaslik yoki e'tiborsiz qoldirish — turli muhitlarda turli versiya o'rnatilib, "menda ishlaydi" muammosiga olib keladi. Prodakshnda npm ci (lock'ga qat'iy amal qiladi) ishlating, npm install emas. (2) ^ bilan MAJOR yangilanishni kutish — ^ MAJOR'ni o'tkazmaydi.

Follow-up. "npm install va npm ci farqi?"npm install package.jsonga qarab o'rnatadi va lock'ni yangilashi mumkin; npm ci esa faqat package-lock.jsonga amal qiladi, node_modulesni tozalab qayta o'rnatadi — CI/prod uchun tez va aniq (reproducible).


Eng ko'p so'raladigan 10 savol

Vaqtingiz kam bo'lsa, avval shu 10 tasini mukammal o'zlashtiring — ular deyarli har bir Node.js intervyusida uchraydi.

  1. Event loop qanday ishlaydi va uning fazalari qaysi? — Node'ning yuragi; setTimeout vs setImmediate vs process.nextTick vs Promise tartibini tushuntira oling.
  2. Node "single-threaded" bo'lsa, ko'p so'rovni qanday bajaradi? — Non-blocking I/O + libuv thread pool.
  3. Ushlanmagan promise rejection nima va nega protsessni qulatadi? — Node 15+ xatti-harakati, unhandledRejection.
  4. Event loop'ni nima bloklaydi? — CPU-og'ir sinxron kod; worker_threads yechimi.
  5. JWT qanday ishlaydi, access/refresh token va uni qayerda saqlash? — Tuzilma, imzo, HttpOnly cookie vs localStorage.
  6. Session vs JWT — stateful vs stateless, qachon qaysini tanlash.
  7. Express middleware qanday ishlaydi?(req, res, next), stack, error-handling middleware (4 argument).
  8. REST API dizayni — resurs, HTTP metod, status kod, idempotentlik.
  9. ORM'da N+1 muammosi — sabab va eager loading/join bilan yechim.
  10. Ilovani qanday gorizontal masshtablaysiz? — stateless dizayn, load balancer, holatni Redis/S3'da saqlash.

So'nggi maslahat: Intervyuda har bir javobni "nega shunday?" bilan mustahkamlang. "JWT stateless" demang — "JWT stateless, chunki server holat saqlamaydi, faqat imzoni tekshiradi; bu masshtablashni osonlashtiradi, lekin tokenni darhol bekor qilishni qiyinlashtiradi" deng. Aynan shu chuqurlik sizni junior'dan ajratadi.


Muvaffaqiyat tilaymiz! Bu savollarni tushunib o'zlashtirsangiz, Node.js backend intervyusining katta qismini ishonch bilan yeng olasiz.

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Node.js va Backend — Chuqur Intervyu Savollari — Wisar