Mundarija (24)
- Bu darsda
- 1. Nega bu kerak?
- 2. Tipli ishlovchi
- 2.1 Generiklar tartibi
- 2.2 Kanon tajribasi: tsc nima deydi
- 2.3 Biz sinagan yana uch holat
- 2.4 req.body — nega unknown
- 3. Declaration merging: req.sorovId
- 3.1 G'oya
- 3.2 Express'ning Request iga qo'shish
- 3.3 Middleware: X-Request-Id
- 3.4 Tipli middleware va res.locals
- 3.5 Type guard: httpErrorsmi
- 4. Build kerakmi?
- 5. API qadami: tipli ishlovchi, merging va commit (holat 17)
- 6. Ko'p uchraydigan xatolar
- 7. Mashqlar
- 1-mashq (oson): to'ldiring
- 2-mashq (o'rta): so'rov ID tanlovchi
- 3-mashq (qiyin): tipli javob
- 4-mashq: API qadami
- 8. Real ishda
- Xulosa
- Manbalar
Express TypeScript bilan: tipli ishlovchi (handler), req.body va declaration merging
Qisqacha:
@types/express5 daRequest<Params, ResBody, ReqBody, Query>vaResponse<ResBody>generiklari bor. Marshrut satridanreq.paramstipini TypeScript o'zi chiqaradi.req.bodyga tip yozish — yolg'on: tana tekshirilmagan, shuning uchun uniunknowndeb oling. O'z maydoningizni (req.sorovId) qo'shish uchun declaration merging —declare global { namespace Express { interface Request { … } } }.vazifalar-apibuild qilmaydi: Node 24.tsni o'zi ishga tushiradi, farq ~100 ms.
Bu darsda
RequestvaResponsegeneriklarini to'g'ri yozasiz vaRequestHandler,ErrorRequestHandlertiplarini ishlatasiz.req.paramstipi marshrut satridan qanday chiqishini bilasiz.- Nega
req.body—unknownbo'lishi kerakligini tushuntira olasiz. - Declaration merging bilan
req.sorovIdqo'shasiz vaX-Request-Idmiddleware'ini yozasiz. - "Build kerakmi?" savoliga o'lchov bilan javob berasiz.
Oldin bilishingiz kerak: Declaration merging va modul kengaytirish, TypeScript'ni Node'da to'g'ridan ishga tushirish, Type guard'lar, Middleware va request lifecycle.
1. Nega bu kerak?
vazifalar-api boshidan TypeScript'da yozilgan. Lekin Express — JavaScript kutubxonasi, uning tiplari alohida paketda: @types/express. Bu tiplar ikki xil xato qilishga imkon beradi:
- Ortiqcha ishonch.
req.body.matngastringtipini yozasiz, TypeScript rozi bo'ladi. Lekin mijoz{"matn": 42}yuborsa, tip baribirstringdeydi — bu yolg'on. - Ortiqcha
any.req.sorovId— Express'da yo'q maydon. Xatoni o'chirish uchun(req as any).sorovIdyoziladi va tip tekshiruvi butunlay o'chadi.
Ikkalasi ham "tip bor, lekin himoya yo'q" holati. Tip — mahsulot yorlig'iga o'xshaydi. Yorliqda "shakar" deb yozib qo'yish qopdagi narsani shakarga aylantirmaydi. Yorliq to'g'ri bo'lishi uchun qopni ochib tekshirish kerak — kodda bu validatsiya.
2. Tipli ishlovchi
2.1 Generiklar tartibi
@types/express paketidagi asosiy tiplar (versiya 5.0.6):
Request<Params, ResBody, ReqBody, Query, Locals>
Response<ResBody, Locals>
RequestHandler<Params, ResBody, ReqBody, Query, Locals>
ErrorRequestHandler // (err, req, res, next)| Generik | Nima | Sukut |
|---|---|---|
Params |
req.params |
ParamsDictionary (string lar) |
ResBody |
res.json(...) qabul qiladigan tip |
any |
ReqBody |
req.body |
any |
Query |
req.query |
ParsedQs |
Locals |
res.locals |
Record<string, any> |
Ikki marta any ga qarang: sukut bo'yicha req.body va javob tanasi tekshirilmaydi. Shu sababli kanon ishlovchida ularni aniq yozadi.
2.2 Kanon tajribasi: tsc nima deydi
Kanon holat 17 kodi ustida vaqtinchalik fayl yozib, haqiqiy tsc chiqishini olgan (t17-tipler, tsc 6.0.3, @types/express 5.0.6):
import type { Request, Response } from "express";
import type { Vazifa } from "./vazifalar/vazifa.ts";
export function h(
req: Request<{ id: string }, Vazifa, unknown>,
res: Response<Vazifa>,
): void {
const id: number = req.params.id; // 1
req.body.matn; // 2
res.json({ xato: "x" }); // 3
req.sorovId.toUpperCase(); // 4
req.foydalanuvchi; // 5
}error TS2322: Type 'string' is not assignable to type 'number'.
error TS18046: 'req.body' is of type 'unknown'.
error TS2353: Object literal may only specify known properties, and 'xato' does not exist in type 'Vazifa'.
error TS2339: Property 'foydalanuvchi' does not exist on type 'Request<{ id: string; }, Vazifa, unknown, ParsedQs, Record<string, any>>'.Har xatoni tarjima qilamiz:
- TS2322 — "
stringtipininumberga berib bo'lmaydi". URL'dan kelgan hamma narsa satr./vazifalar/5daid—"5", son emas. Son kerak bo'lsa, o'zingiz o'girasiz va tekshirasiz (idniOl). - TS18046 — "
req.bodytipiunknown". Bu xato emas, himoya.unknownbilan avval tekshirmasdan hech narsa qilib bo'lmaydi. - TS2353 — "obyekt literali faqat ma'lum xususiyatlarni ko'rsata oladi,
Vazifadaxatoyo'q".Response<Vazifa>— bu ishlovchi faqatVazifaqaytaradi. Xato javobni xato ishlovchi beradi. - 4-qatorda xato yo'q.
req.sorovIdExpress'da yo'q, lekin biz uni declaration merging bilan qo'shganmiz. Bu haqda — keyingi bo'limda. - TS2339 — "
foydalanuvchixususiyatiRequest<…>tipida yo'q". Merging qilinmagan maydon — xato. Xabar oxirida to'liq tip ko'rinadi: beshinchi generik sukut bo'yichaRecord<string, any>.
2.3 Biz sinagan yana uch holat
Biz ham holat 17 da vaqtinchalik fayl bilan tsc --noEmit ishlatdik (tsc 6.0.3):
r.get("/kurs/:kod{/:sana}", (req, res) => {
const kod: string = req.params.kod; // xatosiz
const sana: string = req.params.sana; // 6-qator
req.params.yoq; // 7-qator
});
r.get("/q", (req, res) => {
const til: string = req.query.til; // 11-qator
});src/_sinov.ts(6,9): error TS2322: Type 'string | undefined' is not assignable to type 'string'.
src/_sinov.ts(7,14): error TS2339: Property 'yoq' does not exist on type '{ kod: string; } & Partial<{ sana: string; }>'.
src/_sinov.ts(11,9): error TS2322: Type 'string | ParsedQs | (string | ParsedQs)[] | undefined' is not assignable to type 'string'.Bu juda foydali natija:
req.paramstipi marshrut satridan chiqadi."/kurs/:kod{/:sana}"dan TypeScript{ kod: string } & Partial<{ sana: string }>yasadi:kodmajburiy,{/:sana}ixtiyoriy qism bo'lgani uchunsana—string | undefined(yangi route sintaksisi). Bu faqat ishlovchi marshrut bilan bitta chaqiruvda yozilganda ishlaydi. Ishlovchi alohida funksiya bo'lsa,Paramsni o'zingiz berasiz.req.query— to'rt xil bo'lishi mumkin.ParsedQstipi?til=uz(satr),?til=uz&til=ru(massiv) va?til[a]=1(obyekt) holatlarini qamraydi. Shuning uchunreq.query.tilni to'g'ridanstringga berib bo'lmaydi. Kanon har parametrnibitta()kabi funksiya bilan tekshiradi (Filtrlash darsida).
2.4 req.body — nega unknown
Biz Request<…, { matn: string }> deb yozib ham ko'rdik — req.body.matn xatosiz o'tdi. TypeScript sizga ishondi. Lekin express.json() istalgan JSON'ni qo'yadi: {"matn": 42}, [], null. Kompilyator buni bilmaydi — tip ishga tushganda tekshirilmaydi (TypeScript nima).
Shuning uchun kanon qoida: tana — unknown, keyin tekshiruv funksiyasi Tekshiruv<T> qaytaradi:
.patch(jsonKerak, async (
req: Request<{ id: string }, Vazifa, unknown>,
res: Response<Vazifa>,
) => {
const id = idniOl(req.params.id);
const t = ozgarishniTekshir(req.body); // unknown → Tekshiruv
if (!t.ok) throw new HttpXato(422, t.xato);
res.json(await xizmat.yangila(id, t.qiymat)); // faqat Vazifa
})Tekshiruvdan keyin t.qiymat — haqiqatan VazifaOzgarishi. Bu yerda tip va haqiqat mos. Qo'lda tekshiruv Zod darsida kutubxonaga almashadi, g'oya o'zgarmaydi.
Tekshirib ko'ring: Hamkasbingiz
req.body as YangiVazifadeb yozdi va "TypeScript xato bermayapti, demak to'g'ri" dedi. Nima deysiz?
Javob
as — kompilyatorga "menga ishon" deyish, tekshiruv emas (type assertion). Ishga tushganda hech narsa tekshirilmaydi: {"matn": 42} kelsa, kod matn.trim() da TypeError bilan yiqiladi. To'g'ri yo'l — unknown va tekshiruv funksiyasi (yoki Zod).
3. Declaration merging: req.sorovId
3.1 G'oya
Declaration merging darsida ko'rgansiz: bir xil nomli ikki interface bitta bo'lib birlashadi. Avval oddiy misol — «Ishga tushir» bilan sinang:
interface Sorov {
url: string;
}
interface Sorov {
sorovId: string; // birinchisiga qo'shiladi
}
const s: Sorov = { url: "/vazifalar", sorovId: "kanon-17-0001" };
console.log(Object.keys(s).join(", "));Konsolda:
url, sorovIdIkkinchi e'londan keyin Sorov da ikkala maydon ham majburiy. Birini tushirib qoldirsangiz:
interface Sorov {
url: string;
}
interface Sorov {
sorovId: string;
}
const s: Sorov = { url: "/vazifalar" };Xato:
TS2741: Property 'sorovId' is missing in type '{ url: string; }' but required in type 'Sorov'.3.2 Express'ning Request iga qo'shish
@types/express Request ni global Express nomlar fazosi (namespace) ichidagi bo'sh interface Request dan kengaytiradi. Aynan shu interfeysga qo'shsak, hamma joydagi req da maydon paydo bo'ladi. Kanon fayli src/express.d.ts:
// express.d.ts — declaration merging: Express'ning Request turiga
// o'z maydonimizni qo'shamiz
export {};
declare global {
namespace Express {
interface Request {
/** sorovId middleware'i qo'yadi */
sorovId: string;
}
}
}Uch tafsilot:
export {}— faylni modulga aylantiradi.declare globalfaqat modul ichida ishlaydi.namespace— bu yerda faqat tip e'loni, kod yaratmaydi.tsconfigdagierasableSyntaxOnly(type stripping darsi).d.tsdagi e'longa qarshi emas.- Maydon majburiy (
sorovId: string,?siz). Bu va'da: middleware har so'rovda uni qo'yadi. Va'da buzilsa — masalan middleware ulanmasa — tip yolg'on bo'ladi. Shuning uchunsorovId()ilovada birinchi ulanadi.
3.3 Middleware: X-Request-Id
src/umumiy/sorov-id.ts (kanon):
import { randomUUID } from "node:crypto";
import type { RequestHandler } from "express";
// Mijozdan kelgan qiymat — ishonchsiz: qisqa, xavfsiz belgilar
const YAROQLI = /^[\w.-]{8,128}$/;
export function sorovId(
yangiId: () => string = randomUUID,
): RequestHandler {
return (req, res, next) => {
const kelgan = req.get("x-request-id");
req.sorovId = kelgan !== undefined && YAROQLI.test(kelgan)
? kelgan : yangiId();
res.set("X-Request-Id", req.sorovId);
next();
};
}So'rov ID (request ID) — bitta so'rovni loglarda kuzatish uchun yorliq. Frontend yoki proxy uni X-Request-Id sarlavhasida yuborishi mumkin. Bo'lmasa, server yangi UUID yaratadi. Javobda ID qaytariladi: foydalanuvchi "xato chiqdi" desa, ID bo'yicha logdan topasiz. Loglar bilan bog'lash — Correlation ID darsida.
Mijoz qiymati regex bilan tekshiriladi. Sabab: u logga yoziladi. Ichida qator ko'chishi bo'lsa, hujumchi logga soxta qator qo'sha oladi (log injection). Kanon testi buni tekshiradi: "a b" (bo'shliq bilan) → yangi UUID.
yangiId parametri — testda o'rniga soxta funksiya berish uchun (DI framework'siz naqshi).
Haqiqiy javob (kanon curl -i, holat 17):
HTTP/1.1 201 Created
X-Request-Id: kanon-17-0001
Location: /vazifalar/13.4 Tipli middleware va res.locals
RequestHandler va ErrorRequestHandler — o'zgaruvchiga tip berish uchun: parametrlar tipi o'zi chiqadi. res.locals ga tip berish uchun beshinchi generik — Locals. Biz sinadik:
type Mahalliy = { boshlandi: number };
export const vaqt: RequestHandler<
Record<string, string>, unknown, unknown, unknown, Mahalliy
> = (_req, res, next) => {
res.locals.boshlandi = performance.now(); // xatosiz
res.locals.boshqa = 1; // 17-qator
next();
};src/_sinov.ts(17,14): error TS2339: Property 'boshqa' does not exist on type 'Mahalliy & Locals'.res.locals — bitta so'rov davomida middleware'lar o'rtasida ma'lumot uzatish joyi. req ga yangi maydon qo'shish (merging) va res.locals — ikki yo'l. Kanon req.sorovId ni tanladi: u har ishlovchida kerak, merging esa bir marta yoziladi.
3.5 Type guard: httpErrorsmi
Xato ishlovchida xato — unknown (Express unga istalgan narsa uzatishi mumkin). Boshqa kutubxonalar xatosini tanish uchun kanon type guard yozgan (Type guard'lar). Bu toza TypeScript — «Ishga tushir» bilan sinang:
function httpErrorsmi(
x: unknown,
): x is Error & { status: number; expose: boolean } {
return x instanceof Error && "status" in x &&
typeof x.status === "number" && "expose" in x &&
typeof x.expose === "boolean";
}
const oddiy = new Error("bu oddiy xato");
const range = Object.assign(new Error("Range Not Satisfiable"),
{ status: 416, expose: true });
for (const x of [oddiy, range, "satr", null]) {
if (httpErrorsmi(x) && x.status < 500 && x.expose) {
console.log(x.status, x.message); // bu yerda tip toraygan
} else {
console.log("500");
}
}Konsolda:
500
416 Range Not Satisfiable
500
500if ichida TypeScript x ni Error & { status: number; expose: boolean } deb biladi — x.status xatosiz. serve-static ning 416 xatosi aynan shu shaklda keladi (Mashhur middleware'lar).
4. Build kerakmi?
Node 24 .ts faylni o'zi ishga tushiradi — type stripping (TypeScript'ni Node'da darsi). vazifalar-api ning start skripti: node src/server.ts. Lekin klassik yo'l — tsc bilan dist/ ga JS yasash. Qaysi biri?
Kanon o'lchadi (t17-build, 15 marta, ikki tur, mashina yuklangan paytda; sovuq start — jarayon boshidan http:// qatorigacha):
- src/server.ts, 1-tur435,7 ms
- dist/server.js, 1-tur329,5 ms
- src/server.ts, 2-tur482,8 ms
- dist/server.js, 2-tur377,1 ms
Manba: IlmHamroh kanon o'lchovi t17-build, 2026-10-24: Node 24.21.0, tsc 6.0.3, Windows 11, i5-12500H; mediana n=15; band mashina
Grafikda ikki juft ustunga qarang: har turda build qilingan JS ~100 ms tezroq. Lekin ikki tur orasidagi farq ham ~50 ms — mashina band edi. tsc build'ning o'zi 3.5 s oladi.
Qaror: build yo'q. Server bir marta ishga tushadi va soatlab ishlaydi — 100 ms farq ahamiyatsiz. Build qadami esa xato manbai: dist/ eskirib qolishi, source map kerakligi. Docker image uchun qaror Docker darsida qayta ko'riladi. Type stripping ogohlantirish ham chiqarmadi.
Kanondagi kichik topilma: tsconfig.build.json loyiha papkasidan tashqarida bo'lganda tsc TS2688 Cannot find type definition file for 'node' xatosini berdi — lekin baribir JS fayllarni yozdi (exit kod 2). Ya'ni "JS paydo bo'ldi" — "build muvaffaqiyatli" degani emas. CI'da exit kodga qarang.
Diqqat: Type stripping tiplarni tekshirmaydi — faqat o'chiradi. Tekshiruv alohida:
npm run tip(tsc --noEmit). Kanonnpm run checkuchalasini birga ishlatadi: tip, lint, test.
5. API qadami: tipli ishlovchi, merging va commit (holat 17)
Holat 17 ning oxirgi qismi. O'zgarishlar:
- Yangi
src/express.d.ts— «Express'ning Request iga qo'shish» bo'limidagi fayl. - Yangi
src/umumiy/sorov-id.tsvailova.tsda birinchi middleware:
+import { sorovId } from "./umumiy/sorov-id.ts";
…
app.disable("x-powered-by");
+ app.use(sorovId());
app.use(helmet());- Xato ishlovchi logga ID yozadi. Uzun
status < 500 && exposesharti esa «Type guard:httpErrorsmi» bo'limidagi funksiyaga ajratiladi (to'liq faylda ko'rasiz):
const xatoIshlovchi: ErrorRequestHandler =
- (xato: unknown, _req, res, next) => {
+ (xato: unknown, req, res, next) => {
…
- console.error(xato);
+ console.error(`[${req.sorovId}]`, xato);corssozlamasidagiexposedHeaders: ["Location", "X-Request-Id"](Mashhur middleware'lar darsida qo'shilgan) endi ma'noga ega: frontend ID'ni o'qiy oladi.marshrut.tsda PATCH ishlovchi — tipli (Request<{ id: string }, Vazifa, unknown>,Response<Vazifa>).
Uch darsdagi o'zgarishlardan keyin src/ilova.ts to'liq (holat 17; telefon uchun uzun qatorlar bo'lingan):
// ilova.ts — Express ilovasi: middleware tartibi → routerlar → 404 →
// xato ishlovchisi. listen() YO'Q — server ishga-tushir.ts da,
// testlar ilovani portsiz ham ishlata oladi
import express from "express";
import type { ErrorRequestHandler } from "express";
import cors from "cors";
import helmet from "helmet";
import { join } from "node:path";
import { HttpXato } from "./umumiy/http-xato.ts";
import { sorovId } from "./umumiy/sorov-id.ts";
import { vazifaXizmati } from "./vazifalar/xizmat.ts";
import { vazifalarRouteri } from "./vazifalar/marshrut.ts";
import type { VazifaOmbori } from "./vazifalar/ombor.ts";
export type Bogliqliklar = {
ombor: VazifaOmbori;
/** CORS ruxsat etilgan origin'lar (frontend: Vite dev, Pages) —
* bo'sh massiv: CORS sarlavhasi yo'q */
corsOriginlar?: readonly string[];
};
function httpErrorsmi(
x: unknown,
): x is Error & { status: number; expose: boolean } {
return x instanceof Error && "status" in x &&
typeof x.status === "number" && "expose" in x &&
typeof x.expose === "boolean";
}
const xatoIshlovchi: ErrorRequestHandler =
(xato: unknown, req, res, next) => {
if (res.headersSent) {
next(xato); // javob yarmida — Express o'zi ulanishni uzadi
return;
}
if (xato instanceof HttpXato) {
res.status(xato.holat).json({ xato: xato.message });
return;
}
// body-parser xatolari: `type` maydoni bilan (http-errors)
const tur = typeof xato === "object" && xato !== null &&
"type" in xato ? xato.type : undefined;
if (tur === "entity.parse.failed") {
res.status(400).json({ xato: "Tana yaroqli JSON emas" });
return;
}
if (tur === "entity.too.large") {
res.status(413).json({ xato: "Tana 16 KB dan katta" });
return;
}
// Boshqa kutubxonalar (serve-static: 416, 403...) http-errors
// tashlaydi: status + expose
if (httpErrorsmi(xato) && xato.status < 500 && xato.expose) {
res.status(xato.status).json({ xato: xato.message });
return;
}
console.error(`[${req.sorovId}]`, xato);
res.status(500).json({ xato: "Server xatosi" });
};
export function ilovaYarat(
{ ombor, corsOriginlar = [] }: Bogliqliklar,
): express.Express {
const app = express();
app.disable("x-powered-by"); // "Express" ekanini oshkor qilmaymiz
app.use(sorovId());
// helmet: nosniff, HSTS, CSP va h.k. — JSON API uchun ham arzon
// (t14: 1 paket, import 2 ms)
app.use(helmet());
// CORS: frontend boshqa origin'dan (GitHub Pages) — faqat
// ro'yxatdagilar; preflight (OPTIONS) shu yerda javob oladi
app.use(cors({ origin: [...corsOriginlar],
exposedHeaders: ["Location", "X-Request-Id"], maxAge: 600 }));
app.use(express.static(join(import.meta.dirname, "..", "public")));
app.use(express.json({ limit: "16kb" }));
app.use("/vazifalar", vazifalarRouteri(vazifaXizmati(ombor)));
app.use((req) => {
throw new HttpXato(404, `${req.method} ${req.path} topilmadi`);
});
app.use(xatoIshlovchi);
return app;
}Uchinchi test — X-Request-Id (kanon test/middleware.test.ts, qatorlar bo'lingan). Testlar 9 → 10:
test(
"X-Request-Id: yaroqlisi qaytariladi, "
+ "yo'q yoki yaroqsiz — yangi UUID",
async () => {
const bor = await xomSorov(`${asos}/vazifalar`, {
sarlavhalar: { "X-Request-Id": "mijoz-123456" },
});
assert.equal(bor.sarlavhalar["x-request-id"], "mijoz-123456");
const yoq = await xomSorov(`${asos}/vazifalar`);
assert.match(
String(yoq.sarlavhalar["x-request-id"]), /^[0-9a-f-]{36}$/);
const yomon = await xomSorov(`${asos}/vazifalar`, {
sarlavhalar: { "X-Request-Id": "a b" },
});
assert.match(
String(yomon.sarlavhalar["x-request-id"]), /^[0-9a-f-]{36}$/);
});Kanon tekshiruvi (tekshiruv-17): tip 0, lint 0, testlar 10/10; prod bog'liqliklar 3 (68 paket). Sovuq start 412 ms — mashina yuklangan paytdagi o'lchov, holat 13 dagi 289 ms bilan solishtirib bo'lmaydi. Endi commit (kanon xabari):
npm run check
git add -A
git commit -m "helmet, CORS (ruxsat ro'yxati), X-Request-Id; tipli handler va declaration merging"Kanondan bitta eslatma: holat papkalari node_modules siz saqlanadi. Bunday papkada tsc TS7016 ("cors uchun tip e'loni topilmadi") deydi — bu soxta xato. npm ci dan keyin tip tekshiruvi toza (@types/cors devDependencies da bor).
6. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
Property 'sorovId' does not exist on type 'Request…' |
express.d.ts yo'q yoki include ga kirmagan |
Fayl src/ da, export {} bilan |
| Merging ishlamaydi | export {} yo'q — fayl modul emas |
export {} qo'shing |
'req.body' is of type 'unknown' |
Tana tekshirilmagan | Tekshiruv funksiyasi yoki Zod |
Type 'string' is not assignable to type 'number' |
req.params — doim satr |
O'giring va tekshiring |
ParsedQs ni string ga berib bo'lmaydi |
Query massiv yoki obyekt bo'lishi mumkin | typeof x === "string" tekshiruvi |
TS7016 cors uchun |
@types/cors yo'q yoki npm ci qilinmagan |
npm i -D @types/cors |
7. Mashqlar
Yechimlar kurs/mashqlar/25/17-express-ts/ da.
1-mashq (oson): to'ldiring
Requestning uchinchi generigi —req.tipi- Tekshirilmagan tana uchun to'g'ri tip —
declare globalishlashi uchun faylda bo'lishi kerak —export- So'rov ID sarlavhasi —
X-Request-
Yechim
- body — tartib: Params, ResBody, ReqBody, Query, Locals.
- unknown —
anytekshiruvni o'chiradi,unknownmajbur qiladi. - {} — fayl modulga aylanadi.
- Id —
X-Request-Id; javobda ham qaytariladi.
2-mashq (o'rta): so'rov ID tanlovchi
idTanla(kelgan, yangiId) funksiyasini TypeScript'da yozing: kelgan (string | undefined) /^[\w.-]{8,128}$/ ga mos bo'lsa — o'zi, aks holda yangiId(). To'rt holatni sinang: yaroqli, undefined, bo'shliqli "a b", qator ko'chishli "abcdefgh\nSOXTA LOG". yangiId o'rniga hisoblagich bering — natija deterministik bo'lsin. Yordam: funksiya tipini parametr sifatida berish — funksiyalarni tiplash.
Yechim
const YAROQLI = /^[\w.-]{8,128}$/;
function idTanla(
kelgan: string | undefined,
yangiId: () => string,
): string {
return kelgan !== undefined && YAROQLI.test(kelgan)
? kelgan : yangiId();
}
let n = 0;
const soxtaId = (): string => `yangi-${String(++n)}`;
const holatlar = [
"mijoz-123456", undefined, "a b", "abcdefgh\nSOXTA LOG",
];
for (const h of holatlar) console.log(idTanla(h, soxtaId));Konsolda:
mijoz-123456
yangi-1
yangi-2
yangi-3To'rtinchi holat — log injection urinishi: \n regex'dagi [\w.-] ga kirmaydi, $ esa satr oxirini talab qiladi (m bayrog'isiz).
3-mashq (qiyin): tipli javob
Javob<T> tipini yozing: { ok: true; qiymat: T } | { ok: false; xato: string }. Keyin tananiTekshir(x: unknown): Javob<{ matn: string }> — x obyekt bo'lsa va matn 1–200 belgili satr (holat 17 dagi MAKS_MATN kabi) bo'lsa ok: true. Uch holat: { matn: "Non olish" }, { matn: 42 }, null. ok bo'yicha toraytirib, qiymat.matn.length va xato ni chiqaring. Yordam: discriminated union.
Yechim
type Javob<T> = { ok: true; qiymat: T } | { ok: false; xato: string };
function tananiTekshir(x: unknown): Javob<{ matn: string }> {
if (typeof x !== "object" || x === null || !("matn" in x)) {
return { ok: false, xato: "tana — obyekt, matn bilan" };
}
const { matn } = x;
if (typeof matn !== "string" || matn.length < 1 ||
matn.length > 200) {
return { ok: false, xato: "matn 1–200 belgi" };
}
return { ok: true, qiymat: { matn } };
}
for (const tana of [{ matn: "Non olish" }, { matn: 42 }, null]) {
const t = tananiTekshir(tana);
console.log(t.ok ? `ok: ${String(t.qiymat.matn.length)}` : t.xato);
}Konsolda:
ok: 9
matn 1–200 belgi
tana — obyekt, matn bilanKanondagi Tekshiruv<T> aynan shunday. Ishlovchi if (!t.ok) throw new HttpXato(422, t.xato) dan keyin t.qiymat ni xavfsiz ishlatadi.
4-mashq: API qadami
src/express.d.ts va src/umumiy/sorov-id.ts ni yarating, sorovId() ni birinchi middleware qiling, xato ishlovchida req.sorovId ni logga yozing, PATCH ishlovchiga tiplar bering. X-Request-Id testini qo'shing. npm run check yashil bo'lgach — holat 17 ni kanon xabari bilan commit qiling.
Yechim
Namuna — «API qadami» bo'limi. Tekshiruv: grep -rn "as any" src/ bo'sh bo'lsin; curl -i -H 'X-Request-Id: kanon-17-0001' … javobida X-Request-Id: kanon-17-0001 qaytsin (kanon chiqishidagidek). Testlar soni — 10.
8. Real ishda
- Har TypeScript + Express loyihasida
types/express.d.tskabi fayl bor:req.user,req.sorovId,req.tenant. Auth middlewarereq.userni qo'yadi (auth qismi). - NestJS bu muammolarni dekoratorlar va DTO bilan hal qiladi (NestJS qismi) — lekin ichida o'sha Express tiplari.
- Kod sharhida
req.body as …va(req as any)— qizil bayroq. - Intervyu: "
unknownvaanyfarqi?", "Declaration merging nima va qachon kerak?"
Xulosa
Request<Params, ResBody, ReqBody, Query, Locals>— tartibni yodlang;Response<ResBody>noto'g'ri javobni ushlaydi.req.paramstipi marshrut satridan chiqadi (ixtiyoriy qism —Partial);req.query—ParsedQs, satr bo'lishi shart emas.req.body—unknown; tekshiruvdan keyingina tip haqiqatga mos.declare global { namespace Express { interface Request … } }+export {}—req.sorovId; middleware birinchi ulanadi.- Build yo'q: ~100 ms farq server uchun ahamiyatsiz; tip tekshiruvi —
tsc --noEmitalohida.
Keyingi dars: REST prinsiplari va resurslar — API'ni resurslar atrofida loyihalash va vazifalar-api ga /teglar resursini qo'shish.
Manbalar
@types/express5.0.6 (DefinitelyTyped), npm reestri, 2026-10-24.- TypeScript Handbook — Declaration Merging: https://www.typescriptlang.org/docs/handbook/declaration-merging.html
- Node.js 24 — Type stripping: https://nodejs.org/docs/latest-v24.x/api/typescript.html
- Kanon: holat 17 (
express.d.ts,sorov-id.ts, testlar 10),t17-tipler(tsc xatolari),t17-build(sovuq start).req.params/req.query/res.localstsc chiqishlari — biz ishga tushirdik (tsc 6.0.3, @types/express 5.0.6, holat 17 nusxasi).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!