11 β HTTP moduli: native server (sehrsiz)¶
β¬ οΈ Oldingi: 10 β process, os, CLI va child_process Β· π README Β· Keyingi: 12 β Express asoslari β‘οΈ
Bu bobda: Node'ning o'z
node:httpmoduli bilan veb-server quramiz β hech qanday tashqi paketsiz, "sehrsiz". HTTP'ni qisqa takrorlaymiz (so'rov/javob, metodlar, status kodlar, sarlavhalar);http.createServerbilan server yaratiblistenqilamiz; so'rov (req) obyektidan metod, URL va sarlavhalarni o'qiymiz, tanani oqim (stream) sifatida yig'amiz va JSON'ni parse qilamiz; javob (res) obyektiga status, sarlavha va tana yozamiz; metod va URL bo'yicha qo'lda routing qilib 404 qaytaramiz; JSON API beramiz; query string'niURL/URLSearchParamsbilan ajratamiz; statik faylni stream bilan uzatamiz. Yakunda REAL KEYS sifatida kichik "Vazifalar" REST API (GET/POST/vazifalar) quramiz β keyingi bobda buni Express bilan solishtiramiz va nega har narsani qo'lda yozish charchatishini his qilamiz.
Nega native HTTP'dan boshlaymiz?¶
Ko'pchilik Node'ni o'rganishni to'g'ridan-to'g'ri Express'dan boshlaydi. Express ajoyib, lekin u β abstraksiya: u sizning o'rningizga ko'p ishni "sehrli" qiladi. Sehrning ostida nima borligini bilmasangiz, biror narsa buzilganda boshingiz qotadi: "Bu req.body qayerdan keldi? Nega res.json() ishlaydi-yu, res.end() ham bor?"
Shuning uchun avval sehrsiz qatlamni β Node'ning o'z node:http modulini β qo'lda ishlatamiz. Bu modul Express, Fastify, Koa va boshqa hamma freymvorklarning poydevori. Ularning ostida o'sha o'sha http.createServer turadi. Bir marta o'zingiz qo'lda yozsangiz, Express'ning har bir "sehri" oddiy funksiya chaqiruviga aylanadi.
Bob oxiriga kelib siz native HTTP bilan to'liq ishlaydigan JSON API yozasiz. Keyin 12-bobda xuddi shu APIni Express bilan qayta yozib, "qancha kod tejaldi" degan savolga o'zingiz javob berasiz.
π Bu bobda DB yo'q β ma'lumotlarni oddiy massivda (xotirada) saqlaymiz. Haqiqiy ma'lumotlar bazasi 14-bobdan boshlanadi. Hozir e'tibor β HTTP qatlamida.
1. HTTP β 60 soniyalik takror¶
HTTP (HyperText Transfer Protocol) β bu mijoz (brauzer, mobil ilova, fetch) bilan server o'rtasidagi suhbat tili. Suhbat juda oddiy qoidada boradi: mijoz so'rov yuboradi, server bitta javob qaytaradi. Boshqasi yo'q β server o'zicha gap boshlamaydi.
Har bir so'rov (request) quyidagilardan iborat:
- Metod β niyatni bildiradi:
GET(o'qish),POST(yangi yaratish),PUT/PATCH(o'zgartirish),DELETE(o'chirish). - URL β qaysi resurs:
/vazifalar,/vazifalar/5?bajarildi=true. - Sarlavhalar (headers) β qo'shimcha ma'lumot:
Content-Type: application/json,Authorization: ...,Host: .... - Tana (body) β ixtiyoriy yuk (asosan
POST/PUT'da): masalan, yangi vazifaning JSON'i.
Har bir javob (response) ham shunga o'xshash:
- Status kod β natija:
200(OK),201(yaratildi),404(topilmadi),422(yaroqsiz ma'lumot),500(server xatosi). - Sarlavhalar β masalan,
Content-Type: application/json. - Tana β javob mazmuni (HTML, JSON, rasm, ...).
Status kodlarni guruh bo'yicha eslab qolish oson:
| Guruh | Ma'no | Misollar |
|---|---|---|
2xx |
Muvaffaqiyat | 200 OK, 201 Created, 204 No Content |
3xx |
Yo'naltirish | 301 Moved, 304 Not Modified |
4xx |
Mijoz xatosi | 400 Bad Request, 404 Not Found, 422 Unprocessable |
5xx |
Server xatosi | 500 Internal Error, 503 Unavailable |
Asosiy farqni yodda tuting: 4xx β aybdor mijoz (noto'g'ri so'rov yubordi), 5xx β aybdor server (so'rov to'g'ri edi, lekin kod yiqildi).
2. Birinchi server: http.createServer¶
Eng kichik ishlaydigan server β atigi bir necha qator. Yangi papka oching, package.json ga "type": "module" qo'shing (ESM uchun) va server.mjs yarating:
// server.mjs
import { createServer } from 'node:http';
const server = createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.end('Salom, dunyo!');
});
server.listen(3000, () => {
console.log('Server http://localhost:3000 da tinglayapti');
});
Ishga tushiramiz:
Endi brauzerda http://localhost:3000 ni oching β "Salom, dunyo!" chiqadi. To'xtatish uchun terminalda Ctrl + C.
Nima sodir bo'ldi, qatorma-qator:
createServer(callback)β server obyekti yaratadi.callbackβ bu har bir so'rovda chaqiriladigan funksiya. Unga ikkita argument keladi:req(kelgan so'rov) vares(biz to'ldiradigan javob).res.statusCode = 200β javob statusini belgilaymiz.res.setHeader(...)β javob sarlavhasini qo'shamiz.charset=utf-8β o'zbekcha/kirill harflar to'g'ri ko'rinishi uchun muhim.res.end('...')β tanani yozamiz va javobni yopamiz.end()chaqirilmasa, mijoz "muzlab" qoladi β javob hech tugamaydi.server.listen(3000, ...)β serverni3000-portda tinglashga qo'yamiz. Bu qator bo'lmasa, server hech kimni eshitmaydi.
π‘ CommonJS farqi: agar
"type": "module"yo'q bo'lsa (eski uslub), import o'rnigaconst http = require('node:http');deb yozasiz vahttp.createServer(...)ishlatasiz. Qolgan hammasi bir xil. Bu kitobda asosiy uslub β ESM (import).
Kodni o'zimiz sinaymiz (global fetch bilan)¶
Node 24'da fetch global β brauzerdagidek, hech narsa o'rnatmasdan ishlaydi. Shuni ishlatib, serverni ishga tushirib, unga o'zimiz so'rov yuborib, javobni tekshiramiz β hammasi bitta faylda:
// test-server.mjs
import { createServer } from 'node:http';
const server = createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.end('Salom, dunyo!');
});
server.listen(3000, () => {
console.log('Server http://localhost:3000 da tinglayapti');
});
// 300ms dan keyin o'zimizga so'rov yuboramiz, javobni tekshiramiz, serverni yopamiz
setTimeout(async () => {
const javob = await fetch('http://localhost:3000/');
console.log('Status:', javob.status);
console.log('Content-Type:', javob.headers.get('content-type'));
console.log('Tana:', await javob.text());
server.close(); // server.close() β yangi ulanishlarni to'xtatadi
}, 300);
Natija:
Server http://localhost:3000 da tinglayapti
Status: 200
Content-Type: text/plain; charset=utf-8
Tana: Salom, dunyo!
server.close() chaqirilgani uchun dastur o'zi tugaydi β endi Ctrl + C shart emas. Bu uslubni butun bobda ishlatamiz: server + uni sinaydigan fetch bir faylda, shuning uchun har misolni shunchaki node fayl.mjs deb tekshira olasiz.
3. req β so'rov obyekti¶
req (aslida IncomingMessage klassi) β bu mijoz yuborgan so'rovning hamma ma'lumoti. Eng ko'p kerak bo'ladiganlari:
req.methodβ"GET","POST"va h.k. (har doim katta harfda).req.urlβ yo'l va query string:"/vazifalar?bajarildi=true".req.headersβ sarlavhalar obyekti (kalitlar kichik harfda):req.headers['content-type'],req.headers.host.
import { createServer } from 'node:http';
const server = createServer((req, res) => {
console.log('Metod:', req.method);
console.log('URL:', req.url);
console.log('Host:', req.headers.host);
console.log('User-Agent:', req.headers['user-agent']);
res.end('OK');
});
server.listen(3000, () => console.log('tinglayapti'));
Lekin diqqat: req.url bu butun yo'l + query β uni hali parse qilish kerak (4-bo'limda ko'ramiz). Va req.headers β bu shunchaki tayyor obyekt. Eng qiyini esa tana β chunki u darrov tayyor turmaydi.
Tanani oqim (stream) sifatida o'qish¶
Mana bu yerda Node'ning "sehrsiz"ligi seziladi. req.body degan tayyor narsa yo'q! Tana β bu oqim (stream): mijoz uni bo'lak-bo'lak (chunk) yuboradi, ayniqsa katta bo'lsa. Node sizga tananing kelishini kuzatib, bo'laklarni o'zingiz yig'ish imkonini beradi.
Eng zamonaviy va o'qishga oson usul β for await (async iteratsiya):
import { createServer } from 'node:http';
const server = createServer(async (req, res) => {
// Tanani oqim sifatida bo'lak-bo'lak yig'amiz
let xom = '';
for await (const bolak of req) {
xom += bolak; // bolak β Buffer; satrga qo'shilsa avtomatik matnga aylanadi
}
console.log('Xom tana:', xom);
res.end('Qabul qilindi');
});
server.listen(3000, () => console.log('tinglayapti'));
req obyekti async iterable β ya'ni uni for await bilan aylantirsa bo'ladi. Har bolak β Buffer (xom baytlar), lekin matnga (+=) qo'shilganda Node uni avtomatik UTF-8 satrga aylantiradi.
Klassik (eski) usul β data va end hodisalari orqali. Buni ham bilib qo'yish kerak, chunki ko'p eski koddan uchraydi:
// 'data' / 'end' hodisalari bilan tanani yig'ish (klassik usul)
function tanaOqi(req) {
return new Promise((resolve, reject) => {
const bolaklar = [];
req.on('data', (bolak) => bolaklar.push(bolak)); // har bo'lakni saqlaymiz
req.on('end', () => resolve(Buffer.concat(bolaklar).toString('utf-8'))); // tugadi
req.on('error', reject);
});
}
req.on('data', ...) β har bo'lak kelganda chaqiriladi. req.on('end', ...) β hammasi kelib bo'lganda. Biz bo'laklarni massivga yig'ib, oxirida Buffer.concat(...) bilan birlashtiramiz. Ikkala usul ham bir xil natija beradi; for await qisqaroq va xatosi kamroq.
JSON tanani parse qilish¶
API'lar odatda JSON jo'natadi. Yig'ilgan xom matn β bu shunchaki satr ('{"matn":"..."}'). Uni JavaScript obyektiga aylantirish uchun JSON.parse kerak. Lekin mijoz buzuq JSON yuborishi mumkin β shuning uchun try/catch shart:
import { createServer } from 'node:http';
const server = createServer(async (req, res) => {
let xom = '';
for await (const bolak of req) xom += bolak;
let tana = null;
if (xom) {
try {
tana = JSON.parse(xom);
} catch {
// Buzuq JSON kelsa β mijozning xatosi (4xx)
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(JSON.stringify({ xato: 'Yaroqsiz JSON' }));
return; // muhim: javobdan keyin chiqib ketamiz
}
}
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(JSON.stringify({ qabulQilindi: tana, metod: req.method }));
});
server.listen(3000, () => console.log('tinglayapti'));
// Sinov:
setTimeout(async () => {
const javob = await fetch('http://localhost:3000/echo', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ism: 'Olim', yosh: 25 }),
});
console.log('Javob:', await javob.json());
const yomon = await fetch('http://localhost:3000/echo', { method: 'POST', body: '{buzuq' });
console.log('Buzuq status:', yomon.status, await yomon.json());
server.close();
}, 300);
Natija:
tinglayapti
Javob: { qabulQilindi: { ism: 'Olim', yosh: 25 }, metod: 'POST' }
Buzuq status: 400 { xato: 'Yaroqsiz JSON' }
E'tibor bering: bu uch qator g'oya β oqimni o'qi, JSON.parse qil, xatoni ushla β bu Express'da bitta express.json() chaqiruviga sig'adi. Hozir hammasini qo'lda qilyapmiz. Buni har endpoint uchun takrorlash kerak bo'ladi β mana shu yerda charchoq boshlanadi.
4. res β javob obyekti¶
res (ServerResponse klassi) β javobni quradigan obyekt. Asosiy metodlari:
res.statusCode = 200yokires.writeHead(200, { ... })β status kod (va birvarakay sarlavhalar).res.setHeader('Content-Type', '...')β bitta sarlavha qo'shish.res.write('...')β tanaga bo'lak qo'shish (bir necha marta chaqirilsa bo'ladi).res.end('...')β tanani tugatib, javobni yopish (oxirgi bo'lakni ham yozadi).
writeHead β statusCode + sarlavhalarni bitta chaqiruvda beradi, shuning uchun qisqaroq:
// Ikkala variant bir xil natija beradi:
// Variant A β alohida
res.statusCode = 201;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({ ok: true }));
// Variant B β writeHead bilan (qisqaroq)
res.writeHead(201, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(JSON.stringify({ ok: true }));
β οΈ Muhim qoida: sarlavhalarni faqat tana yozilmasdan oldin o'rnatish mumkin.
res.write()yokires.end()chaqirilgandan keyinsetHeader/writeHeadqilsangiz βERR_HTTP_HEADERS_SENTxatosi chiqadi. Avval sarlavha, keyin tana β tartibi shu.
write() β tanani bo'lak-bo'lak yuborishni ko'rsatish uchun:
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
res.write('Salom, '); // birinchi bo'lak
res.write('dunyo!'); // ikkinchi bo'lak
res.end(); // bo'sh end β yakunlaydi (qo'shimcha bo'lak yo'q)
Mijozga "Salom, dunyo!" yetib boradi β Node bo'laklarni birlashtiradi. Amalda kichik javoblar uchun to'g'ridan-to'g'ri res.end('Salom, dunyo!') qulayroq; write() katta yoki oqimli (streaming) javoblar uchun foydali.
5. Qo'lda routing: metod + URL bo'yicha¶
Hozirgacha serverimiz hamma so'rovga bir xil javob berardi. Haqiqiy ilovada esa /vazifalar boshqacha, /salom boshqacha javob berishi kerak. Bu β routing (yo'naltirish). Express'da bu uchun maxsus sintaksis bor; native'da esa biz oddiy if/switch bilan o'zimiz ajratamiz.
if bilan eng tushunarli:
import { createServer } from 'node:http';
const server = createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const yol = url.pathname; // query'siz toza yo'l: "/salom"
if (req.method === 'GET' && yol === '/') {
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('Bosh sahifa');
return;
}
if (req.method === 'GET' && yol === '/salom') {
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('Salom!');
return;
}
// Hech qaysiga mos kelmadi β 404
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('404 β bunday sahifa yo\'q');
});
server.listen(3000, () => console.log('tinglayapti'));
switch bilan ham yozsa bo'ladi β metod va yo'lni bitta kalitga birlashtirib:
import { createServer } from 'node:http';
const server = createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const kalit = `${req.method} ${url.pathname}`; // "GET /salom"
switch (kalit) {
case 'GET /':
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('Bosh sahifa');
break;
case 'GET /salom':
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
res.write('Salom, ');
res.write('dunyo!');
res.end();
break;
default:
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('404 β bunday sahifa yo\'q');
}
});
server.listen(3000, () => console.log('tinglayapti'));
// Sinov:
setTimeout(async () => {
for (const p of ['/', '/salom', '/yoq']) {
const r = await fetch('http://localhost:3000' + p);
console.log(p, '=>', r.status, JSON.stringify(await r.text()));
}
server.close();
}, 300);
Natija:
tinglayapti
/ => 200 "Bosh sahifa"
/salom => 200 "Salom, dunyo!"
/yoq => 404 "404 β bunday sahifa yo'q"
Ikkala usulning ham default/oxirgi tarmog'i β 404: agar so'rov hech qaysi shartga mos kelmasa, "topilmadi" deb javob beramiz. Bu majburiy β aks holda mos kelmagan so'rov javobsiz "muzlab" qoladi.
π Sezdingizmi? Har yangi sahifa = yana bitta
if/case+ har birida o'shawriteHead+Content-Typeni qayta yozish. 5 ta yo'lda chidasa bo'ladi, 50 ta yo'lda esa bu jahannam. Express aynan shu takrorni yo'qotadi.
6. Query string'ni ajratish: URL va URLSearchParams¶
req.url β "/qidir?q=node&sahifa=2" kabi keladi. Bizga ikki narsa kerak: toza yo'l (/qidir) va parametrlar (q=node, sahifa=2). Buni qo'lda split('?') qilish mumkin, lekin xatosi ko'p (kodlangan belgilar, bir nechta = va h.k.). Node'ning o'rnatilgan URL klassi buni to'g'ri qiladi:
import { createServer } from 'node:http';
const server = createServer((req, res) => {
// req.url nisbiy bo'lgani uchun ikkinchi argument (asos) kerak
const url = new URL(req.url, `http://${req.headers.host}`);
const yol = url.pathname; // "/qidir"
const q = url.searchParams.get('q'); // "node"
const sahifa = url.searchParams.get('sahifa'); // "2" (har doim SATR!)
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(JSON.stringify({ yol, q, sahifa }));
});
server.listen(3000, () => console.log('tinglayapti'));
setTimeout(async () => {
const javob = await fetch('http://localhost:3000/qidir?q=node&sahifa=2');
console.log(await javob.json());
server.close();
}, 300);
Natija:
Muhim nuqtalar:
new URL(req.url, asos)βreq.urlnisbiy (/qidir?...), shuning uchunURLga to'liq asos (http://host) kerak.req.headers.hostshuni beradi.url.pathnameβ query'siz toza yo'l. Routing'da shuni ishlatamiz (yuqorida ko'rdik).url.searchParamsβ buURLSearchParamsobyekti..get('kalit')bilan qiymat olamiz.- β οΈ Query qiymatlari har doim satr.
sahifaβ"2"(satr),2(son) emas. Songa kerak bo'lsaNumber(sahifa)qiling.
URLSearchParamsni mustaqil ham ishlatsa bo'ladi (masalan, POST da kelgan form-data uchun):
const params = new URLSearchParams('q=node&sahifa=2&teg=js&teg=web');
console.log(params.get('q')); // "node" β birinchisi
console.log(params.getAll('teg')); // ["js", "web"] β takroriy kalitlar
console.log(params.has('sahifa')); // true
7. Statik fayl serve qilish (qisqacha)¶
Ko'pincha serverdan oddiy fayllarni β index.html, style.css, rasm β berish kerak bo'ladi. Ularni fs bilan oqim (stream) qilib uzatamiz, shunda 100 MB lik fayl ham RAM ni to'ldirmaydi (oqimlar haqida 9-bobda gaplashgandik):
import { createServer } from 'node:http';
import { createReadStream } from 'node:fs';
import { stat } from 'node:fs/promises';
import { extname, join, normalize } from 'node:path';
// Kengaytma -> Content-Type jadvali
const turlar = {
'.html': 'text/html; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.js': 'text/javascript; charset=utf-8',
'.json': 'application/json; charset=utf-8',
'.png': 'image/png',
'.svg': 'image/svg+xml',
};
const ILDIZ = process.cwd(); // fayllar shu papkadan beriladi
const server = createServer(async (req, res) => {
// "/" so'ralsa index.html ga aylantiramiz
const yol = req.url === '/' ? '/index.html' : req.url;
// XAVFSIZLIK: "../" bilan papkadan chiqib ketishni to'sish
const toliqYol = normalize(join(ILDIZ, yol));
if (!toliqYol.startsWith(ILDIZ)) {
res.writeHead(403).end('Taqiqlangan');
return;
}
try {
const info = await stat(toliqYol);
if (!info.isFile()) throw new Error('katalog');
const tur = turlar[extname(toliqYol)] ?? 'application/octet-stream';
res.writeHead(200, { 'Content-Type': tur });
// Faylni OQIM bilan uzatamiz β pipe res ga to'g'ridan-to'g'ri yozadi
createReadStream(toliqYol).pipe(res);
} catch {
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('404 β fayl topilmadi');
}
});
server.listen(3000, () => console.log('http://localhost:3000'));
Diqqat qiladigan joylar:
- Content-Type kengaytmadan aniqlanadi. Bu juda muhim: agar
.cssfaylnitext/plaindeb yuborsangiz, brauzer uni stil sifatida qo'llamaydi. MosContent-Typeβ brauzerga "bu nima ekanini" aytadi. createReadStream(fayl).pipe(res)β fayl oqimini to'g'ridan-to'g'ri javob oqimiga ulaydi. Faylni butun o'qib, keyin yozish o'rniga, u bo'lak-bo'lak oqib o'tadi.../himoyasi majburiy. Foydalanuvchi/../../secret.txtso'rasa,normalizeuni hisoblab chiqaradi; bizstartsWith(ILDIZ)bilan papkadan tashqariga chiqishni bloklaymiz. Bu βpath traversalhujumiga qarshi eng asosiy himoya.
π Bu β Express'da
express.static('public')bitta qatori. Yana bir "qo'lda qilsangiz murakkab, freymvork bilan oson" misol.
8. REAL KEYS β native http bilan "Vazifalar" REST API¶
Endi hamma o'rgangan narsamizni bitta amaliy mini-loyihaga jamlaymiz: kichik Vazifalar (to-do) REST API. Bu β hayotiy holat: deyarli har bir backend developer karyerasida shunday CRUD endpoint yozadi. Ikki yo'l qo'yamiz:
GET /vazifalarβ barcha vazifalar ro'yxatini JSON qaytaradi.POST /vazifalarβ tanada kelgan yangi vazifani qo'shadi (validatsiya bilan).
Ma'lumotni xotiradagi massivda saqlaymiz (DB 14-bobda). Hamma o'rgangan qismlar shu yerda jam: routing, oqimdan tana o'qish, JSON parse, validatsiya, status kodlar, 404.
// vazifalar.mjs β native http bilan kichik REST API
import { createServer } from 'node:http';
// Vaqtinchalik "ma'lumotlar bazasi" β oddiy massiv (xotirada)
let vazifalar = [
{ id: 1, matn: 'Node o\'rganish', bajarildi: false },
{ id: 2, matn: 'HTTP modulni tushunish', bajarildi: true },
];
let keyingiId = 3;
// Yordamchi: JSON javob yuborish (takrorni kamaytirish uchun)
function jsonYubor(res, status, data) {
res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(JSON.stringify(data));
}
// Yordamchi: so'rov tanasini JSON sifatida o'qish
async function jsonOqi(req) {
let xom = '';
for await (const bolak of req) xom += bolak;
if (!xom) return null;
return JSON.parse(xom); // buzuq bo'lsa β chaqiruvchi yerda ushlaymiz
}
const server = createServer(async (req, res) => {
const { method } = req;
const url = new URL(req.url, `http://${req.headers.host}`);
const yol = url.pathname;
// --- ROUTING ---
if (method === 'GET' && yol === '/vazifalar') {
return jsonYubor(res, 200, vazifalar);
}
if (method === 'POST' && yol === '/vazifalar') {
let tana;
try {
tana = await jsonOqi(req);
} catch {
return jsonYubor(res, 400, { xato: 'Yaroqsiz JSON' });
}
// Validatsiya: matn bo'lishi va bo'sh bo'lmasligi shart
if (!tana || typeof tana.matn !== 'string' || tana.matn.trim() === '') {
return jsonYubor(res, 422, { xato: '"matn" maydoni majburiy' });
}
const yangi = { id: keyingiId++, matn: tana.matn.trim(), bajarildi: false };
vazifalar.push(yangi);
return jsonYubor(res, 201, yangi); // 201 = yaratildi
}
// Hech qaysi yo'lga mos kelmadi
return jsonYubor(res, 404, { xato: 'Topilmadi', yol });
});
server.listen(3000, () => console.log('Vazifalar API http://localhost:3000'));
// --- O'z-o'zini test (global fetch) ---
setTimeout(async () => {
const baza = 'http://localhost:3000';
let r = await fetch(`${baza}/vazifalar`);
console.log('GET status:', r.status, '| ro\'yxat uzunligi:', (await r.json()).length);
r = await fetch(`${baza}/vazifalar`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ matn: 'Express ga o\'tish' }),
});
console.log('POST status:', r.status, '| yangi:', await r.json());
r = await fetch(`${baza}/vazifalar`, { method: 'POST', body: JSON.stringify({}) });
console.log('Bo\'sh matn status:', r.status, '| xato:', await r.json());
r = await fetch(`${baza}/yoq`);
console.log('404 status:', r.status, '| javob:', await r.json());
server.close();
}, 300);
Natija:
Vazifalar API http://localhost:3000
GET status: 200 | ro'yxat uzunligi: 2
POST status: 201 | yangi: { id: 3, matn: "Express ga o'tish", bajarildi: false }
Bo'sh matn status: 422 | xato: { xato: '"matn" maydoni majburiy' }
404 status: 404 | javob: { xato: 'Topilmadi', yol: '/yoq' }
Ishladi! Bu β to'liq ishlaydigan REST API, hech qanday tashqi paketsiz. E'tibor bering:
jsonYuborvajsonOqiyordamchilarini ajratmaganimizda, har endpoint'dawriteHead+JSON.stringifyva oqim o'qishni qayta-qayta yozardik. Demak takrorni kamaytirish uchun o'zimiz mini-freymvork yozyapmiz β bu Express'ning aynan qilgan ishi.- Validatsiya, status kod tanlash (
201vs422vs404), buzuq JSON ushlash β hammasi qo'lda. Yangi endpoint qo'shilsa, bu naqsh yana takrorlanadi.
9. Nega bu charchatadi β va Express keraklligi¶
Yuqoridagi API ishlaydi, lekin endigina ikkita yo'l bor. Endi tasavvur qiling, haqiqiy loyihada:
GET /vazifalar,POST /vazifalar,GET /vazifalar/:id,PUT /vazifalar/:id,PATCH /vazifalar/:id,DELETE /vazifalar/:idβ bitta resurs uchun 6 ta yo'l.- Yana foydalanuvchilar, izohlar, autentifikatsiya... β o'nlab yo'l.
Native'da har bir yo'lda bir xil narsa takrorlanadi:
| Muammo | Native'da | Express'da |
|---|---|---|
URL'dan parametr (/vazifalar/5) |
pathname.split('/') ni qo'lda parse |
app.get('/vazifalar/:id') β req.params.id |
| Tanani o'qish (JSON) | har safar oqimni yig', JSON.parse, try/catch |
express.json() bir marta, req.body tayyor |
| 404 | oxirida qo'lda if qoldig'i |
avtomatik |
| JSON javob | writeHead + JSON.stringify har joyda |
res.json(...) |
| Routing | uzun if/switch zanjiri |
har yo'l alohida, toza |
| Xatolarni boshqarish | har joyda qo'lda | markazlashgan middleware |
Ya'ni native HTTP bilan siz asta-sekin o'zingizning Express'ingizni yozib chiqasiz β yomonroq va xatosi ko'proq variantini. Aynan shu sababdan dunyo Express (va undan keyingilarni) yaratdi: takrorlanadigan ishni bir marta yechib, sizga toza, deklarativ API berish.
Lekin endi siz sehrning ostida nima borligini bilasiz. 12-bobda xuddi shu "Vazifalar" API'ni Express bilan qayta yozganda, har bir Express funksiyasi sizning hozir qo'lda yozgan kodingizning qaysi qismini almashtirayotganini aniq ko'rasiz. Bu β eng kuchli o'rganish: avval qiyin yo'l, keyin oson yo'l, va orasidagi farqni his qilish.
π‘ Native HTTP behuda emas: ba'zan juda yengil, bog'liqliksiz mikroservis yoki health-check endpoint uchun aynan shu yetarli. Lekin to'laqonli ilova β Express (yoki Fastify, Hono) bilan.
Mashqlar¶
Oson¶
server.mjsni o'zgartiring: javobtext/plaino'rniga HTML qaytarsin.Content-Typenitext/htmlqiling va tanada<h1>Salom!</h1>yuboring. Brauzerda farqi ko'rinadimi?- Serverga
GET /vaqtyo'lini qo'shing: u joriy vaqtni (new Date().toISOString()) matn sifatida qaytarsin. Boshqa yo'llar 404 bersin. - "Vazifalar" API'siga
GET /soniyo'lini qo'shing: u{ "soni": N }JSON qaytarsin, bu yerdaNβ hozirgi vazifalar soni.
O'rta¶
GET /vazifalarga filter qo'shing:?bajarildi=trueso'ralsa faqat bajarilgan,?bajarildi=falseso'ralsa faqat bajarilmagan vazifalar qaytsin. Filter bo'lmasa β hammasi. (URLSearchParamsishlating.)jsonOqiyordamchisiga hajm chegarasi qo'shing: agar tana 1 MB dan oshsa, yig'ishni to'xtatib413 Payload Too Largeqaytaring. (Maslahat:for awaitichida yig'ilgan uzunlikni hisoblang.)- Har bir so'rov uchun konsolga oddiy log chiqaring:
[vaqt] METOD url -> status. Buning uchun javob yuborilgandan keyinres.statusCodeni o'qing (res.on('finish', ...)hodisasi yordam beradi).
Qiyin¶
- "Vazifalar" API'sini to'liq CRUD qiling:
GET /vazifalar/:id(bitta vazifa),PATCH /vazifalar/:id(bajarildini o'zgartirish) vaDELETE /vazifalar/:id(o'chirish).:idni URL'dan o'zingiz ajrating (pathname.split('/')). Mavjud bo'lmaganiduchun404, muvaffaqiyatliDELETEuchun204(tanasiz) qaytaring. Hammasinifetchbilan sinab ko'ring.
Yechim β 4
GET /vazifalar handleri ichida URLSearchParams'dan bajarildi ni o'qib, massivni filtrlaymiz. Qiymat har doim satr ekanini unutmang ("true", "false").
import { createServer } from 'node:http';
const vazifalar = [
{ id: 1, matn: 'A', bajarildi: false },
{ id: 2, matn: 'B', bajarildi: true },
{ id: 3, matn: 'C', bajarildi: true },
];
const server = createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
if (req.method === 'GET' && url.pathname === '/vazifalar') {
let natija = vazifalar;
const f = url.searchParams.get('bajarildi'); // "true" / "false" / null
if (f === 'true') natija = vazifalar.filter((v) => v.bajarildi);
if (f === 'false') natija = vazifalar.filter((v) => !v.bajarildi);
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(JSON.stringify(natija));
return;
}
res.writeHead(404).end();
});
server.listen(3000, () => console.log('tinglayapti'));
setTimeout(async () => {
let r = await fetch('http://localhost:3000/vazifalar?bajarildi=true');
console.log('bajarildi=true:', (await r.json()).map((v) => v.id)); // [2, 3]
r = await fetch('http://localhost:3000/vazifalar?bajarildi=false');
console.log('bajarildi=false:', (await r.json()).map((v) => v.id)); // [1]
server.close();
}, 300);
Asosiy g'oya: searchParams.get('bajarildi') qaytargan qiymat "true"/"false"/null bo'ladi β boolean emas. Shuning uchun satr bilan solishtiramiz.
Yechim β 5
data hodisasida yig'ilgan baytlar sonini kuzatamiz. Chegaradan oshsa, darrov 413 javobini yuboramiz va tugadi bayrog'i bilan ikki marta javob bermaslikni ta'minlaymiz. Muhim nuance: chegara oshganda ulanishni req.destroy() bilan uzib tashlasak, mijoz toza 413 javobini olmasdan ECONNRESET (uzilish) ko'radi. Shuning uchun avval javobni yozamiz, keyin qolgan ma'lumotni req.resume() bilan "bo'sh" o'qib tashlaymiz.
import { createServer } from 'node:http';
const CHEGARA = 1024 * 1024; // 1 MB
const server = createServer((req, res) => {
let xom = '';
let uzunlik = 0;
let tugadi = false; // ikki marta javob bermaslik uchun
req.on('data', (bolak) => {
if (tugadi) return;
uzunlik += bolak.length; // bolak β Buffer, .length baytlarda
if (uzunlik > CHEGARA) {
tugadi = true;
res.writeHead(413, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(JSON.stringify({ xato: 'Tana juda katta (max 1 MB)' }));
req.resume(); // qolgan ma'lumotni o'qib tashlaymiz (javob allaqachon ketdi)
return;
}
xom += bolak;
});
req.on('end', () => {
if (tugadi) return; // 413 yuborilgan bo'lsa, chiqamiz
const tana = xom ? JSON.parse(xom) : null;
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(JSON.stringify({ qabul: tana }));
});
});
server.listen(3000, () => console.log('tinglayapti'));
setTimeout(async () => {
// Kichik tana β o'tadi
let r = await fetch('http://localhost:3000/', { method: 'POST', body: JSON.stringify({ a: 1 }) });
console.log('Kichik:', r.status, await r.json()); // 200
// Katta tana β 2 MB
r = await fetch('http://localhost:3000/', { method: 'POST', body: 'x'.repeat(2 * 1024 * 1024) });
console.log('Katta:', r.status, await r.json()); // 413
server.close();
}, 300);
Natija:
tugadi bayrog'i β javobni faqat bir marta yuborilishini kafolatlaydi; aks holda end da yana yozib ERR_HTTP_HEADERS_SENT olardik.
Yechim β 6
res.on('finish', ...) β javob to'liq yuborilgandan keyin chaqiriladi. O'shanda res.statusCode allaqachon belgilangan bo'ladi, shuning uchun loglash uchun ideal joy.
import { createServer } from 'node:http';
const server = createServer((req, res) => {
const boshlandi = Date.now();
// Javob tugaganda loglaymiz
res.on('finish', () => {
const vaqt = new Date().toISOString();
const davom = Date.now() - boshlandi;
console.log(`[${vaqt}] ${req.method} ${req.url} -> ${res.statusCode} (${davom}ms)`);
});
// Oddiy javob
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('OK');
});
server.listen(3000, () => console.log('tinglayapti'));
setTimeout(async () => {
await fetch('http://localhost:3000/salom');
await fetch('http://localhost:3000/vazifalar');
server.close();
}, 300);
Natija (vaqt har xil bo'ladi):
Bu β Express'dagi morgan logging middleware'ning eng oddiy versiyasi. Yana bir "freymvork bizning o'rnimizga qiladigan ish".
Yechim β 7 (to'liq CRUD)
Asosiy g'oya: yo'lni split('/').filter(Boolean) bilan bo'laklarga ajratamiz. ['vazifalar'] β kolleksiya, ['vazifalar', '5'] β bitta element. Keyin metod bo'yicha taqsimlaymiz.
// crud.mjs
import { createServer } from 'node:http';
let vazifalar = [{ id: 1, matn: 'Test', bajarildi: false }];
let keyingiId = 2;
function jsonYubor(res, status, data) {
res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(data === null ? '' : JSON.stringify(data)); // 204 uchun tanasiz
}
async function jsonOqi(req) {
let xom = '';
for await (const b of req) xom += b;
return xom ? JSON.parse(xom) : null;
}
const server = createServer(async (req, res) => {
const { method } = req;
const url = new URL(req.url, `http://${req.headers.host}`);
// "/vazifalar/5" -> ["vazifalar", "5"]
const qismlar = url.pathname.split('/').filter(Boolean);
// --- /vazifalar (kolleksiya) ---
if (qismlar.length === 1 && qismlar[0] === 'vazifalar') {
if (method === 'GET') return jsonYubor(res, 200, vazifalar);
if (method === 'POST') {
let tana;
try { tana = await jsonOqi(req); }
catch { return jsonYubor(res, 400, { xato: 'Yaroqsiz JSON' }); }
if (!tana?.matn?.trim()) return jsonYubor(res, 422, { xato: '"matn" majburiy' });
const yangi = { id: keyingiId++, matn: tana.matn.trim(), bajarildi: false };
vazifalar.push(yangi);
return jsonYubor(res, 201, yangi);
}
}
// --- /vazifalar/:id (bitta element) ---
if (qismlar.length === 2 && qismlar[0] === 'vazifalar') {
const id = Number(qismlar[1]);
const idx = vazifalar.findIndex((v) => v.id === id);
if (idx === -1) return jsonYubor(res, 404, { xato: 'Vazifa topilmadi' });
if (method === 'GET') return jsonYubor(res, 200, vazifalar[idx]);
if (method === 'DELETE') {
vazifalar.splice(idx, 1);
return jsonYubor(res, 204, null); // 204 = muvaffaqiyat, tanasiz
}
if (method === 'PATCH') {
let tana;
try { tana = await jsonOqi(req); }
catch { return jsonYubor(res, 400, { xato: 'Yaroqsiz JSON' }); }
if (typeof tana?.bajarildi === 'boolean') vazifalar[idx].bajarildi = tana.bajarildi;
return jsonYubor(res, 200, vazifalar[idx]);
}
}
return jsonYubor(res, 404, { xato: 'Topilmadi' });
});
server.listen(3000, () => console.log('tinglayapti'));
// --- Sinov ---
setTimeout(async () => {
const b = 'http://localhost:3000';
let r = await fetch(`${b}/vazifalar/1`, { method: 'PATCH', body: JSON.stringify({ bajarildi: true }) });
console.log('PATCH:', r.status, await r.json()); // 200 { ..., bajarildi: true }
r = await fetch(`${b}/vazifalar/1`, { method: 'DELETE' });
console.log('DELETE status:', r.status, '(204 = tanasiz)'); // 204
r = await fetch(`${b}/vazifalar`);
console.log('Qolgan:', (await r.json()).length, 'ta'); // 0 ta
r = await fetch(`${b}/vazifalar/99`, { method: 'DELETE' });
console.log('Yo\'q id:', r.status, await r.json()); // 404
server.close();
}, 300);
Natija:
tinglayapti
PATCH: 200 { id: 1, matn: 'Test', bajarildi: true }
DELETE status: 204 (204 = tanasiz)
Qolgan: 0 ta
Yo'q id: 404 { xato: 'Vazifa topilmadi' }
E'tibor bering β atigi bitta resurs uchun if/metod zanjiri allaqachon shuncha uzaydi. Endi tasavvur qiling, 5 ta resurs bo'lsa. Aynan shu murakkablik 12-bobda Express'ga o'tish sababimiz: app.get('/vazifalar/:id', ...) bilan :id o'zi ajraladi, req.body o'zi parse bo'ladi, kod esa toza qoladi.
β¬ οΈ Oldingi: 10 β process, os, CLI va child_process Β· π README Β· Keyingi: 12 β Express asoslari β‘οΈ