Mundarija (31)
- Bu darsda
- 1. Nega bu kerak?
- 2. Birinchi server
- 2.1 Butun sikl bitta faylda
- 2.2 Jarayon qanday ketadi
- 3. req — IncomingMessage
- 4. res — ServerResponse
- 4.1 Holat va sarlavhalar
- 4.2 Tana: write va end
- 4.3 end unutilsa
- 4.4 Javob yasash — toza funksiya sifatida
- 5. listen: port va host
- 5.1 Host — kim ulana oladi
- 5.2 Port band bo'lsa
- 5.3 Port qayerdan keladi
- 6. Hujumchi nigohi: ochiq qolgan dev server
- 7. API qadami: vazifalar-api boshlanadi
- 7.1 Loyiha papkasi
- 7.2 Model va ombor
- 7.3 Ilova va server
- 7.4 Ishga tushiramiz
- 7.5 Birinchi testlar
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): bo'shliqlarni to'ldiring
- 2-mashq (o'rta): matn yoki JSON
- 3-mashq (qiyin): har so'rovni log qilish
- 4-mashq: API qadami
- 10. Real ishda
- Xulosa
- Manbalar
http.createServer: req va res bilan framework'siz Node server
Qisqacha:
http.createServer((req, res) => { … })— har so'rovda shu funksiya chaqiriladi.req(IncomingMessage) — kelgan so'rov:method,url,headersva o'qiladigan oqim sifatida tana.res(ServerResponse) — javob:statusCode,setHeader,writeHead,write,end.server.listen(port, host)portni tinglay boshlaydi. Qoida: har so'rov albattares.end()bilan tugashi kerak, sarlavhalar esa tanadan oldin yuboriladi.
Bu darsda
http.createServervalistenbilan server yozib, unifetchbilan o'zi tekshiradigan qilasiz.reqvaresning asosiy xususiyat va metodlarini ishlatasiz.ERR_HTTP_HEADERS_SENTva "javob kelmayapti" xatolarining sababini tushuntirasiz.- Port, host (
127.0.0.1,0.0.0.0,::) vaEADDRINUSEni ajratasiz. vazifalar-apiloyihasini ochasiz:GET /vazifalarishlaydigan birinchi versiya.
Oldin bilishingiz kerak: HTTP server tomonda, Stream nima, EventEmitter asoslari, TypeScript'ni Node'da to'g'ridan ishga tushirish.
1. Nega bu kerak?
O'tgan darsda Node serveri qanday matn olishini ko'rdik. Endi o'zimiz server yozamiz. Express 5 ni bir necha darsdan keyin o'rnatamiz — lekin avval usiz. Sabab oddiy: Express'dagi req va res — aynan shu darsdagi obyektlar. Express ularga faqat qo'shimcha metodlar qo'shgan. res.json() ichida nima bor? Nega "Cannot set headers after they are sent" xatosi chiqadi, nega so'rov "osilib" qoladi — bularni tagidan bilsangiz, har qanday framework'da tez topasiz.
O'xshatish: oshxonadagi buyurtma oynasi. Ofitsiant varaqcha uzatadi (req): nima, nechta, qaysi stol. Oshpaz javob tayyorlaydi (res): avval patnisga yorliq qo'yadi (sarlavhalar), keyin taomni (tana) va oxirida qo'ng'iroqni chaladi (end). Qo'ng'iroq chalinmasa, ofitsiant kutaveradi. Yorliqni taom ketgandan keyin yopishtirib bo'lmaydi.
2. Birinchi server
2.1 Butun sikl bitta faylda
import http from "node:http";
const server = http.createServer((req, res) => {
console.log("so'rov:", req.method, req.url);
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
res.end("Assalomu alaykum!");
});
server.listen(0, "127.0.0.1", async () => {
const { address, port } = server.address();
console.log("tinglayapti:", address, port > 0);
const res = await fetch(`http://127.0.0.1:${port}/salom`);
console.log(res.status, res.headers.get("content-type"));
console.log(await res.text());
server.close(() => console.log("server yopildi"));
});Konsolda:
tinglayapti: 127.0.0.1 true
so'rov: GET /salom
200 text/plain; charset=utf-8
Assalomu alaykum!
server yopildiQatorma-qator:
http.createServer(funksiya)— server obyektini yaratadi. Funksiya — so'rov ishlovchisi (request listener): har kelgan so'rovda chaqiriladi. Aslida server — EventEmitter, funksiya esa"request"hodisasining tinglovchisi.server.listen(0, "127.0.0.1", callback)— portni ochadi. Callback — "tinglash boshlandi" paytida. Port0— OS bo'sh port tanlaydi (net darsi), haqiqiysiserver.address().portda.- Shu jarayon ichida
fetchbilan o'ziga so'rov yubordik: server va mijoz bitta dasturda. Bu — keyingi darslardagi barcha misollar va testlarning naqshi. server.close(callback)— yangi ulanishlarni qabul qilishni to'xtatadi. Callback — hammasi yopilganda.
Oddiy dasturda esa server yopilmaydi: listen(3000) dan keyin jarayon to'xtamaydi, chunki event loop'da ochiq port bor (event loop darsi). Ctrl+C bilan to'xtatasiz.
2.2 Jarayon qanday ketadi
sequenceDiagram
participant M as Mijoz
participant N as Node (llhttp)
participant K as Sizning funksiya
M->>N: so'rov baytlari
N->>K: req (sarlavhalar tayyor)
K->>N: res.writeHead(200, ...)
K->>N: res.end("...")
N-->>M: holat, sarlavhalar, tanaDiagrammadagi muhim nuqta: funksiyangiz sarlavhalar kelishi bilan chaqiriladi. Tana bo'lsa, u hali yo'lda — req oqimidan keyin o'qiladi. Buni tana o'qish darsida qilamiz.
Xuddi shu yo'l jadvalda — bitta so'rovning hayoti qadamma-qadam:
| Qadam | Kim | Natija |
|---|---|---|
| 1 | Node (llhttp) | baytlardan so'rov qatori va sarlavhalar ajratiladi |
| 2 | Node | req va res obyektlari yaratiladi, funksiyangiz chaqiriladi |
| 3 | sizning funksiya | res.writeHead / setHeader — sarlavhalar tayyorlanadi |
| 4 | sizning funksiya | res.end(...) — tana va "javob tugadi" belgisi |
| 5 | Node | holat qatori, sarlavhalar va tana simga yoziladi; ulanish keep-alive bo'lib qoladi |
3-qadamni 4-qadamdan keyin qilib bo'lmaydi — buni pastda xato bilan ko'ramiz.
3. req — IncomingMessage
req — http.IncomingMessage klassining obyekti. Eng ko'p ishlatiladiganlari:
| Xususiyat | Misol | Izoh |
|---|---|---|
req.method |
"GET" |
doim katta harf |
req.url |
"/vazifalar?bajarildi=true" |
yo'l + query, domensiz |
req.headers |
{ host: "localhost" } |
nomlar kichik harfda |
req.rawHeaders |
["Host", "localhost"] |
asl ko'rinish, juft-juft |
req.httpVersion |
"1.1" |
|
req.socket.remoteAddress |
"127.0.0.1" |
mijoz IP'si |
Bir xil sarlavha ikki marta kelsa nima bo'ladi? Biz xom so'rovda X-Teg: uy va x-teg: ish yubordik. Server req.headers["x-teg"] va req.rawHeaders ni chiqardi (biz ishga tushirdik):
uy, ish
[
'Host', 'localhost',
'X-Teg', 'uy',
'x-teg', 'ish',
'Connection', 'close'
]req.headers takrorlarni vergul bilan birlashtirdi; rawHeaders esa hammasini asl harflari bilan saqladi. Ba'zi sarlavhalar birlashtirilmaydi: masalan, Host takrorlansa birinchisi qoladi, set-cookie esa massiv bo'ladi. Odatda req.headers yetadi.
req.url haqida ikki muhim gap. Birinchi — unda domen yo'q, faqat yo'l va query. Ikkinchi — u mijoz yozganicha keladi: /vazifalar/, /Vazifalar va /vazifalar?x=1 — uch xil satr. Ularni qanday ajratishni keyingi darsda URL bilan hal qilamiz.
req — o'qiladigan oqim (Readable). Tana shu oqimdan keladi. GET so'rovda tana yo'q, lekin oqim baribir bor — darhol tugaydi.
Tekshirib ko'ring: Mijoz
Content-Type: application/jsonyubordi.req.headers["Content-Type"]nima qaytaradi?
Javob
undefined. Node sarlavha nomlarini kichik harfga o'giradi — to'g'risi req.headers["content-type"]. Bu eng ko'p uchraydigan boshlovchi xatosi.
4. res — ServerResponse
4.1 Holat va sarlavhalar
Javobni ikki uslubda qurish mumkin:
- Bosqichma-bosqich:
res.statusCode = 201,res.setHeader("X-Kurs", "IlmHamroh"), keyinres.end(...). - Bir zarbada:
res.writeHead(201, { "Content-Type": "…" }).
Ikkalasini aralashtirsa-chi? Biz sinadik: server avval statusCode = 201, ikki setHeader qildi, keyin writeHead(200, "Hammasi joyida", {...}), end("tayyor") va oxirida yana setHeader ga urindi:
import http from "node:http";
const server = http.createServer((req, res) => {
res.statusCode = 201;
res.setHeader("X-Kurs", "IlmHamroh");
res.setHeader("Content-Type", "text/plain");
console.log(res.headersSent, res.getHeaderNames());
res.writeHead(200, "Hammasi joyida", {
"Content-Type": "text/plain; charset=utf-8",
});
console.log(res.headersSent);
res.end("tayyor");
try {
res.setHeader("X-Kech", "1");
} catch (e) {
console.log(`${e.code}: ${e.message}`);
}
});
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const res = await fetch(`http://127.0.0.1:${server.address().port}/`);
console.log(res.status, res.statusText);
const h = res.headers;
console.log(h.get("x-kurs"), h.get("content-type"));
server.closeAllConnections();
server.close();Konsolda:
false [ 'x-kurs', 'content-type' ]
true
ERR_HTTP_HEADERS_SENT: Cannot set headers after they are sent to the client
200 Hammasi joyida
IlmHamroh text/plain; charset=utf-8Nimani ko'rdik:
setHeadersarlavhalarni xotirada yig'adi, hali hech narsa yuborilmagan (headersSent—false).writeHeadyig'ilganlarni o'zinikilar bilan birlashtiradi: bir xil nomdawriteHeadniki ustun (Content-Type), qolgani saqlanadi (X-Kurs).statusCode = 201hamwriteHead(200)bilan almashdi.writeHeaddan keyinheadersSent—true. Ikkinchi argument — holat matni (status message). Odatda berilmaydi, NodeOK,Not Foundkabi standart matnni qo'yadi.- Yuborilgandan keyin sarlavha qo'shib bo'lmaydi:
ERR_HTTP_HEADERS_SENT— "Mijozga yuborilgandan keyin sarlavhalarni o'rnatib bo'lmaydi". Sarlavhalar tanadan oldin simdan ketgan — qaytarib bo'lmaydi.
Bu xato real loyihada ko'pincha shunday chiqadi: funksiya bir shartda javob yuboradi, lekin return unutilgan, keyingi qator yana javob yubormoqchi bo'ladi. Express'da ham aynan shu xabar.
4.2 Tana: write va end
res.write(bo'lak)— tananing bir qismi. Birinchiwritesarlavhalarni ham yuboradi.res.end(oxirgi?)— "javob tugadi". Ixtiyoriy oxirgi bo'lak bilan.
O'tgan darsdan eslang: faqat res.end(matn) bo'lsa, Node Content-Length qo'yadi; write bo'lsa — chunked. res — yoziladigan oqim (Writable), shuning uchun faylni pipeline(fayl, res) bilan to'g'ridan javobga ulash mumkin — buni static fayl darsida qilamiz.
4.3 end unutilsa
import http from "node:http";
const server = http.createServer((req, res) => {
if (req.url === "/unutdi") return; // ❌ res.end() yo'q
res.end("ok");
});
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const base = `http://127.0.0.1:${server.address().port}`;
console.log(await (await fetch(base + "/")).text());
try {
await fetch(base + "/unutdi", { signal: AbortSignal.timeout(300) });
} catch (e) {
console.log(e.name, e.message);
}
server.closeAllConnections();
server.close();Konsolda:
ok
TimeoutError The operation was aborted due to timeoutServer xato bermadi, log ham yo'q — so'rov shunchaki "osilib" qoldi. Mijoz bizning 300 ms chegaramiz bilan uzdi (AbortSignal.timeout — fetch darsi). Chegara bo'lmaganda brauzer daqiqalab kutardi. Serverda esa har osilgan so'rov ulanishni band qiladi. Shuning uchun har shoxchada (if/else, try/catch) javob tugashini tekshiring. Server o'zi qachon voz kechishini (requestTimeout) timeout'lar darsida sozlaymiz.
4.4 Javob yasash — toza funksiya sifatida
vazifalar-api hamma javobni JSON qiladi. Bu takrorlanadigan ish: JSON.stringify, Content-Type, baytlardagi uzunlik. Uni bitta yordamchi funksiyaga yig'amiz. Avval mantiqni serversiz ko'ramiz — bu blok saytda jonli ishlaydi:
function buildJson(status, data, extra = {}) {
const body = JSON.stringify(data);
const headers = {
"Content-Type": "application/json; charset=utf-8",
"Content-Length": new TextEncoder().encode(body).length,
...extra,
};
return { status, headers, body };
}
const answer = buildJson(201, { matn: "Non olish ✓" }, {
Location: "/vazifalar/1",
});
console.log(answer.headers["Content-Length"]); // 24
console.log(answer.body);Konsolda:
24
{"matn":"Non olish ✓"}body 22 belgi, lekin 24 bayt (✓ — 3 bayt). ...extra — qo'shimcha sarlavhalar sukutdagilar ustiga yoziladi (spread). Loyihadagi json(res, …) aynan shu, faqat oxirida res.writeHead va res.end chaqiradi va baytlarni Buffer.byteLength sanaydi.
5. listen: port va host
5.1 Host — kim ulana oladi
listen |
Kim ulana oladi |
|---|---|
listen(3000, "127.0.0.1") |
faqat shu kompyuter (IPv4) |
listen(3000, "0.0.0.0") |
tarmoqdagi hamma (IPv4) |
listen(3000) |
hamma; IPv4 ham, IPv6 ham (::) |
Host berilmasa nima bo'lishini tekshirdik — server.address():
{ address: '::', family: 'IPv6', port: 59440 }:: — IPv6 dagi "hamma manzillar". Node uni ikki stekli qilib ochadi: IPv4 ulanishlar ham qabul qilinadi. Ya'ni host'siz listen = server butun tarmoqqa ochiq.
5.2 Port band bo'lsa
Bitta portni ikki dastur tinglay olmaydi. Biz vazifalar-api ni 4210-portda ishga tushirib, ikkinchi nusxasini ham o'sha portda ochdik (biz ishga tushirdik, qisqartirilgan):
node:events:505
throw er; // Unhandled 'error' event
^
Error: listen EADDRINUSE: address already in use 127.0.0.1:4210"Manzil allaqachon ishlatilmoqda". Ko'pincha sababi — oldingi terminalda yopilmay qolgan server. Unhandled 'error' event — server ham EventEmitter va "error" tinglovchisi yo'q edi (EventEmitter asoslari). Port band bo'lsa jarayon yiqilishi to'g'ri: noto'g'ri portda "ishlayotgandek" turgan server yomonroq.
Windows'da portni kim band qilganini topish: netstat -ano | findstr :4210 — oxirgi ustun PID. Linux/macOS'da lsof -i :4210.
5.3 Port qayerdan keladi
Qattiq yozilgan 3000 o'rniga muhit o'zgaruvchisi: Number(process.env.PORT ?? 3000) (process darsi). Hosting platformalari portni o'zi beradi, testlar 0 ishlatadi. .env va config'ni env darsida tartibga solamiz.
Tekshirib ko'ring: Server
listen(3000)bilan ochilgan. Kafedagi Wi-Fi'da o'tirgan boshqa odam unga ulana oladimi?
Javob
Ha, agar firewall to'smasa. Host berilmagan — server :: da, ya'ni hamma tarmoq interfeyslarida tinglaydi. Faqat o'zingiz uchun bo'lsa "127.0.0.1" bering. Keyingi bo'lim — aynan shu haqida.
6. Hujumchi nigohi: ochiq qolgan dev server
Ishlab chiqish paytida serverda hali himoya yo'q: auth ham, hajm cheklovi ham. Uni listen(3000) bilan ochib, kafeda yoki yotoqxona Wi-Fi'sida o'tirsangiz, shu tarmoqdagi istalgan odam http://sizning-IP:3000/vazifalar ni ochadi. Tarmoqdagi ochiq portlarni avtomatik qidiradigan dasturlar bor — ular bir necha soniyada topadi. Ma'lumotlaringizni o'qishi, o'chirishi yoki serverga katta so'rovlar yuborib yiqitishi mumkin.
Himoya:
- Ishlab chiqishda doim
"127.0.0.1".vazifalar-apida host —process.env.HOST ?? "127.0.0.1": sukut bo'yicha yopiq, ochish uchun ataylab o'zgaruvchi berasiz. - Telefoningizdan sinash kerak bo'lsa — vaqtincha
HOST=0.0.0.0va faqat uy tarmog'ida. - Konteyner ichida
0.0.0.0kerak bo'ladi — u yerda tashqi himoyani Docker tarmog'i beradi. Buni deploy qismlarida ko'ramiz.
7. API qadami: vazifalar-api boshlanadi
7.1 Loyiha papkasi
vazifalar frontend'i yonida yangi papka oching (masalan C:/Users/…/vazifalar-api) va git init qiling. tsconfig.json va eslint.config.js — 24-qismdagi valyuta dan nusxa (NodeNext, strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes, erasableSyntaxOnly, strictTypeChecked). package.json (kanon):
{
"name": "vazifalar-api",
"version": "0.1.0",
"private": true,
"description": "vazifalar ilovasining backend'i — REST API",
"license": "MIT",
"type": "module",
"engines": { "node": ">=24" },
"scripts": {
"start": "node src/server.ts",
"test": "node --test \"test/**/*.test.ts\"",
"tip": "tsc --noEmit",
"lint": "eslint . --max-warnings=0",
"check": "npm run tip && npm run lint && npm test"
},
"devDependencies": {
"@eslint/js": "10.0.1",
"@types/node": "24.19.2",
"eslint": "10.13.0",
"typescript": "6.0.3",
"typescript-eslint": "8.71.2"
}
}Muhimlari: "start": "node src/server.ts" — build yo'q, Node .ts ni type stripping bilan bajaradi (24-qism va'dasi — server ham shu sxemada). Prod bog'liqlik — 0: hozircha faqat Node. @types/node 24.x — Node 24 ga mos turlar.
Tuzilma:
vazifalar-api/
src/
server.ts — kirish nuqtasi: createServer + listen
ilova.ts — so'rov ishlovchisi (routing shu yerda)
ombor.ts — vazifalar xotirada
vazifa.ts — Vazifa turi va teglar
test/
yordamchi.ts, api.test.tsNega server.ts va ilova.ts alohida? ilova.ts faqat (req, res) => … funksiyasini yasaydi va portni bilmaydi. Testlar uni listen(0) bilan, server.ts esa haqiqiy portda ishga tushiradi. Bitta ilova — ikki xil ishga tushirish.
7.2 Model va ombor
src/vazifa.ts — Vazifa turi. Teglarni (#uy, #ish) server o'zi matndan ajratadi — 22-qism frontend'idagi qoida bilan bir xil regex. Bugun faylda faqat tur va teglariniOl (kanon kodi; tekshiruv funksiyalari tana darsida qo'shiladi):
export type Vazifa = {
id: number;
matn: string;
bajarildi: boolean;
/** matndagi #teglar — server o'zi ajratadi */
teglar: string[];
/** ISO 8601, UTC (Z bilan) */
yaratildi: string;
yangilandi: string;
};
export const MAKS_MATN = 200;
const TEG = /(?<![\p{L}\p{N}_#])#[\p{L}\p{N}_]+(?:['‘’][\p{L}\p{N}_]+)*/gu;
export function teglariniOl(matn: string): string[] {
return [...new Set((matn.match(TEG) ?? [])
.map((t) => t.slice(1).toLowerCase()))];
}
export type YangiVazifa = { matn: string; bajarildi?: boolean };
export type VazifaOzgarishi = { matn?: string; bajarildi?: boolean };MAKS_MATN = 200 — holat 05 qiymati. 26-qism bazasiga moslash uchun keyinroq 100 ga tushadi — buni o'sha darsda ko'rasiz.
src/ombor.ts — vazifalar xotirada (kanon kodi; telefonda o'qish uchun uzun qatorlar bo'lingan, ma'nosi o'zgarmagan):
// ombor.ts — vazifalar xotirada (v0)
import { teglariniOl } from "./vazifa.ts";
import type {
Vazifa, VazifaOzgarishi, YangiVazifa,
} from "./vazifa.ts";
export class XotiraOmbori {
readonly #vazifalar = new Map<number, Vazifa>();
#keyingiId = 1;
readonly #soat: () => Date;
constructor(soat: () => Date = () => new Date()) {
this.#soat = soat;
}
royxat(): Vazifa[] {
return [...this.#vazifalar.values()]
.map((v) => structuredClone(v));
}
ol(id: number): Vazifa | undefined {
const v = this.#vazifalar.get(id);
return v && structuredClone(v);
}
yarat(yangi: YangiVazifa): Vazifa {
const hozir = this.#soat().toISOString();
const v: Vazifa = {
id: this.#keyingiId++,
matn: yangi.matn,
bajarildi: yangi.bajarildi ?? false,
teglar: teglariniOl(yangi.matn),
yaratildi: hozir,
yangilandi: hozir,
};
this.#vazifalar.set(v.id, v);
return structuredClone(v);
}
yangila(id: number, ozgarish: VazifaOzgarishi): Vazifa | undefined {
const v = this.#vazifalar.get(id);
if (!v) return undefined;
if (ozgarish.matn !== undefined) {
v.matn = ozgarish.matn;
v.teglar = teglariniOl(ozgarish.matn);
}
if (ozgarish.bajarildi !== undefined) {
v.bajarildi = ozgarish.bajarildi;
}
v.yangilandi = this.#soat().toISOString();
return structuredClone(v);
}
ochir(id: number): boolean {
return this.#vazifalar.delete(id);
}
}Ikki nozik joy. Har metod structuredClone bilan nusxa qaytaradi: tashqaridagi kod omborni sezdirmay o'zgartira olmaydi (chuqur nusxa). Konstruktor esa soat funksiyasini oladi — testda vaqtni boshqarish uchun.
7.3 Ilova va server
src/ilova.ts — bugun bitta marshrut. json — yuqoridagi buildJson ning loyihadagi shakli:
// ilova.ts — framework'siz so'rov ishlovchisi (birinchi qadam)
import type { IncomingMessage, ServerResponse } from "node:http";
import type { XotiraOmbori } from "./ombor.ts";
type Sozlama = { ombor: XotiraOmbori; publicPapka: string };
function json(res: ServerResponse, holat: number, malumot: unknown,
sarlavhalar: Record<string, string> = {}): void {
const tana = JSON.stringify(malumot);
res.writeHead(holat, {
"Content-Type": "application/json; charset=utf-8",
"Content-Length": Buffer.byteLength(tana),
...sarlavhalar,
});
res.end(tana);
}
export function ilovaYarat({ ombor }: Sozlama) {
return (req: IncomingMessage, res: ServerResponse): void => {
if (req.method === "GET" && req.url === "/vazifalar") {
json(res, 200, ombor.royxat());
return;
}
json(res, 404, { xato: `${req.url ?? "/"} topilmadi` });
};
}Xato javobi — { "xato": "…" }: 22-qismdagi frontend api.ts aynan shu shaklni o'qiydi. publicPapka — static fayllar uchun, static fayl darsida ishlatiladi.
src/server.ts (kanon, o'zgarmaydi):
// server.ts — kirish nuqtasi: http.createServer + listen
import { createServer } from "node:http";
import type { AddressInfo } from "node:net";
import { join } from "node:path";
import { ilovaYarat } from "./ilova.ts";
import { XotiraOmbori } from "./ombor.ts";
const port = Number(process.env.PORT ?? 3000);
const host = process.env.HOST ?? "127.0.0.1";
const server = createServer(ilovaYarat({
ombor: new XotiraOmbori(),
publicPapka: join(import.meta.dirname, "..", "public"),
}));
server.listen(port, host, () => {
// PORT=0 — OS bo'sh port beradi; haqiqiysi address() da
const { port: haqiqiy } = server.address() as AddressInfo;
console.log(`vazifalar-api: http://${host}:${String(haqiqiy)}`);
});server.address() turi string | AddressInfo | null — TCP serverda doim AddressInfo, shuning uchun as. String(haqiqiy) — strictTypeChecked shablon ichida sonni ochiq aylantirishni talab qiladi.
7.4 Ishga tushiramiz
npm install
PORT=4210 npm startIkkinchi terminalda (biz ishga tushirdik, Date qatori olib tashlangan). Chiqishda ikki javobning birinchi qatoriga — status kodga qarang:
curl -i http://127.0.0.1:4210/vazifalar
curl -i -X POST http://127.0.0.1:4210/vazifalarvazifalar-api: http://127.0.0.1:4210
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 2
Connection: keep-alive
Keep-Alive: timeout=5
[]
HTTP/1.1 404 Not Found
Content-Type: application/json; charset=utf-8
Content-Length: 31
Connection: keep-alive
Keep-Alive: timeout=5
{"xato":"/vazifalar topilmadi"}Birinchisi to'g'ri: ombor bo'sh — []. Ikkinchisi esa yolg'on: /vazifalar bor, faqat POST hali yo'q. To'g'ri javob — 405 va qaysi metodlar borligi. Bu — keyingi darsning boshlanish nuqtasi. (PowerShell'da PORT=4210 npm start ishlamaydi: $env:PORT=4210; npm start.)
7.5 Birinchi testlar
test/yordamchi.ts — ilovani tasodifiy portda ochadi (kanon kodi):
import { createServer } from "node:http";
import type { AddressInfo } from "node:net";
import { join } from "node:path";
import { ilovaYarat } from "../src/ilova.ts";
import { XotiraOmbori } from "../src/ombor.ts";
export async function ishgaTushir(): Promise<{
asos: string;
yop: () => Promise<void>;
}> {
const server = createServer(ilovaYarat({
ombor: new XotiraOmbori(),
publicPapka: join(import.meta.dirname, "..", "public"),
}));
await new Promise<void>((r) => server.listen(0, "127.0.0.1", r));
const { port } = server.address() as AddressInfo;
return {
asos: `http://127.0.0.1:${String(port)}`,
yop: () =>
new Promise<void>((r) => {
server.closeAllConnections();
server.close(() => { r(); });
}),
};
}
export function jsonYubor(metod: string, tana: unknown): RequestInit {
return {
method: metod,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(tana),
};
}yop da closeAllConnections() shart: aks holda keep-alive ulanishlari testni 5–6 soniya ushlab turadi. jsonYubor — POST/PATCH uchun, tana darsida kerak bo'ladi. test/api.test.ts boshida before/after bilan ishgaTushir() va yop() chaqiriladi (node:test), keyin ikkita test:
test("GET /vazifalar — [], boshqa yo'l — 404", async () => {
const r = await fetch(`${asos}/vazifalar`);
assert.equal(r.status, 200);
const tur = r.headers.get("content-type") ?? "";
assert.match(tur, /application\/json/);
assert.deepEqual(await r.json(), []);
assert.equal((await fetch(`${asos}/yoq`)).status, 404);
});
test("teglariniOl: frontend qoidasi", () => {
assert.deepEqual(teglariniOl("Non#uy ##x #o'quv #g‘isht #UY #uy"),
["o'quv", "g‘isht", "uy"]);
});npm run check (biz ishga tushirdik, qisqartirilgan): tsc 0 xato, eslint 0, testlar pass 2, fail 0. Ikkinchi test kanondagi bilan bir xil; birinchisi bugungi qadam uchun — keyingi darslarda kanondagi to'liq CRUD va xato testlari bilan almashadi.
Hali commit qilmang — holat 05 static fayl darsida yakunlanadi va bitta commit bo'ladi.
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Tuzatish |
|---|---|---|
ERR_HTTP_HEADERS_SENT |
javob ikki marta yuborildi, return yo'q |
har javobdan keyin return |
| so'rov osilib qoladi | biror shoxchada res.end() yo'q |
har yo'lda javobni tugating |
EADDRINUSE |
port band, eski server ishlayapti | eski jarayonni to'xtating yoki boshqa port |
req.headers["Content-Type"] — undefined |
nomlar kichik harfda | "content-type" |
| telefondan ochilmaydi | 127.0.0.1 da tinglayapti |
vaqtincha HOST=0.0.0.0 |
| test tugamaydi | keep-alive ulanish ochiq | closeAllConnections() keyin close() |
9. Mashqlar
Mashqlar kurs/mashqlar/25/02-createserver/ da.
1-mashq (oson): bo'shliqlarni to'ldiring
- Har so'rovda chaqiriladigan funksiya serverning
""hodisasi tinglovchisi. - Sarlavhalar yuborilganmi —
res.. - OS bo'sh port tanlasin:
listen(). - Faqat shu kompyuterdan ulanish: host
"".
Yechim
- request —
createServer(fn)aslidaserver.on("request", fn). - headersSent —
writeHeadyoki birinchiwritedan keyintrue. - 0 — haqiqiy port
server.address().portda. - 127.0.0.1 — loopback;
0.0.0.0yoki host'siz — tarmoqqa ochiq.
2-mashq (o'rta): matn yoki JSON
buildJson ga o'xshash buildText(status, text) yozing: Content-Type: text/plain; charset=utf-8 va baytlardagi Content-Length. Sinang: buildText(404, "Topilmadi ✗") — holat, uzunlik va tanani chiqaring.
Yechim
function buildText(status, text) {
return {
status,
headers: {
"Content-Type": "text/plain; charset=utf-8",
"Content-Length": new TextEncoder().encode(text).length,
},
body: text,
};
}
const a = buildText(404, "Topilmadi ✗");
console.log(a.status, a.headers["Content-Length"], a.body);Konsolda:
404 13 Topilmadi ✗11 belgi, 13 bayt: ✗ — 3 bayt.
3-mashq (qiyin): har so'rovni log qilish
Server yozing: har javob tugaganda konsolga METOD yo'l → holat (ms) chiqarsin. Ishora: res.on("finish", …) — javob to'liq yuborilganda chaqiriladi; holat — res.statusCode. Vaqt barqaror bo'lmagani uchun ms o'rniga faqat ok deb chiqaring. fetch bilan GET /vazifalar (200) va GET /yoq (404) yuboring.
Yechim
import http from "node:http";
const server = http.createServer((req, res) => {
res.on("finish", () => {
console.log(`${req.method} ${req.url} → ${res.statusCode}`);
});
if (req.url === "/vazifalar") {
res.end("[]");
return;
}
res.statusCode = 404;
res.end("topilmadi");
});
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const base = `http://127.0.0.1:${server.address().port}`;
for (const path of ["/vazifalar", "/yoq"]) {
await (await fetch(base + path)).text();
}
server.closeAllConnections();
server.close();Konsolda:
GET /vazifalar → 200
GET /yoq → 404"finish" — res Writable oqim hodisasi (Stream): oxirgi bayt OS'ga topshirildi. Log yozuvchi middleware'lar (logging darsi) aynan shu hodisani tinglaydi. Bu yerda return muhim: usiz ikkinchi res.end ham chaqirilardi.
4-mashq: API qadami
«API qadami» bo'limidagidek vazifalar-api ni oching: package.json, tsconfig.json, eslint.config.js, src/ ning to'rt fayli, test/ ning ikki fayli. npm run check toza bo'lsin, PORT=4210 npm start va ikki curl natijasi darsdagidek chiqsin. Commit qilmang.
Yechim
Kutilgan: npm run check — tip 0, lint 0, pass 2. curl -i …/vazifalar → 200 va []; curl -i -X POST …/vazifalar → 404 (hozircha). EADDRINUSE chiqsa — oldingi serverni Ctrl+C bilan to'xtating. Fayllar matni — «API qadami» bo'limida.
10. Real ishda
- Har framework ostida shu — Express
appham(req, res) => …funksiyasi:http.createServer(app)bilan ishga tushadi. NestJS, Fastify, Next.js ning Node serveri ham oxir-oqibathttp.Server. - Debug — serverni
node --inspect src/server.tsbilan ochib, VS Code'da so'rov ishlovchisiga breakpoint qo'yish (debug darsi). - Uzoq yashaydigan jarayon — CLI bir necha soniyada tugaydi, server haftalab ishlaydi. Har so'rovda o'sib boradigan
Map— xotira sizishi. - Intervyuda: "
setHeadervawriteHeadfarqi?", "Cannot set headers after they are sent — sababi?", "0.0.0.0va127.0.0.1farqi?".
Xulosa
http.createServer(fn)—fn(req, res)har so'rovda;listen(port, host, cb)— tinglash.req:method,url(domensiz),headers(kichik harf),rawHeaders; o'zi — Readable oqim.res:statusCode/setHeaderyig'adi,writeHeadyuboradi; sarlavhalar ketgach —ERR_HTTP_HEADERS_SENT.- Har yo'l
res.end()bilan tugaydi, aks holda so'rov osilib qoladi. - Host:
127.0.0.1— faqat siz; host'siz —::, butun tarmoq. Port band —EADDRINUSE. vazifalar-apiochildi:GET /vazifalar→[], testlar 2/2; commit — holat 05 da.
Keyingi dars: Qo'lda routing va query parametrlar — URL bilan yo'l va query, /vazifalar/:id, 404 va 405 + Allow.
Manbalar
- Node.js 24 hujjati — HTTP (
createServer,IncomingMessage,ServerResponse,writeHead,setHeader,headersSent,rawHeaders): https://nodejs.org/docs/latest-v24.x/api/http.html - Node.js 24 hujjati — Net (
server.listen,address(),EADDRINUSE): https://nodejs.org/docs/latest-v24.x/api/net.html - Node.js 24 hujjati — Errors (
ERR_HTTP_HEADERS_SENT): https://nodejs.org/docs/latest-v24.x/api/errors.html - Kanon:
vazifalar-apiholat 05 (package.json,server.ts,ombor.ts,vazifa.ts,yordamchi.ts). Bugungi oraliqilova.ts,curlchiqishlari,EADDRINUSE,npm run checkva kichik misollar — biz ishga tushirdik (Node 24.21.0, Windows 11, 2026-10-24).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!