IlmHamroh
JavaScript Full-stack/25-qism. HTTP, Express va REST API17/51-dars18 daqiqa
Mundarija (24)

Express TypeScript bilan: tipli ishlovchi (handler), req.body va declaration merging

Qisqacha: @types/express 5 da Request<Params, ResBody, ReqBody, Query> va Response<ResBody> generiklari bor. Marshrut satridan req.params tipini TypeScript o'zi chiqaradi. req.body ga tip yozish — yolg'on: tana tekshirilmagan, shuning uchun uni unknown deb oling. O'z maydoningizni (req.sorovId) qo'shish uchun declaration merging — declare global { namespace Express { interface Request { … } } }. vazifalar-api build qilmaydi: Node 24 .ts ni o'zi ishga tushiradi, farq ~100 ms.

Bu darsda

  • Request va Response generiklarini to'g'ri yozasiz va RequestHandler, ErrorRequestHandler tiplarini ishlatasiz.
  • req.params tipi marshrut satridan qanday chiqishini bilasiz.
  • Nega req.body — unknown bo'lishi kerakligini tushuntira olasiz.
  • Declaration merging bilan req.sorovId qo'shasiz va X-Request-Id middleware'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:

  1. Ortiqcha ishonch. req.body.matn ga string tipini yozasiz, TypeScript rozi bo'ladi. Lekin mijoz {"matn": 42} yuborsa, tip baribir string deydi — bu yolg'on.
  2. Ortiqcha any. req.sorovId — Express'da yo'q maydon. Xatoni o'chirish uchun (req as any).sorovId yoziladi 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):

ts
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):

ts
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
}
text
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:

  1. TS2322 — "string tipini number ga berib bo'lmaydi". URL'dan kelgan hamma narsa satr. /vazifalar/5 da id — "5", son emas. Son kerak bo'lsa, o'zingiz o'girasiz va tekshirasiz (idniOl).
  2. TS18046 — "req.body tipi unknown". Bu xato emas, himoya. unknown bilan avval tekshirmasdan hech narsa qilib bo'lmaydi.
  3. TS2353 — "obyekt literali faqat ma'lum xususiyatlarni ko'rsata oladi, Vazifa da xato yo'q". Response<Vazifa> — bu ishlovchi faqat Vazifa qaytaradi. Xato javobni xato ishlovchi beradi.
  4. 4-qatorda xato yo'q. req.sorovId Express'da yo'q, lekin biz uni declaration merging bilan qo'shganmiz. Bu haqda — keyingi bo'limda.
  5. TS2339 — "foydalanuvchi xususiyati Request<…> tipida yo'q". Merging qilinmagan maydon — xato. Xabar oxirida to'liq tip ko'rinadi: beshinchi generik sukut bo'yicha Record<string, any>.

2.3 Biz sinagan yana uch holat

Biz ham holat 17 da vaqtinchalik fayl bilan tsc --noEmit ishlatdik (tsc 6.0.3):

ts
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
});
text
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.params tipi marshrut satridan chiqadi. "/kurs/:kod{/:sana}" dan TypeScript { kod: string } & Partial<{ sana: string }> yasadi: kod majburiy, {/:sana} ixtiyoriy qism bo'lgani uchun sana — string | undefined (yangi route sintaksisi). Bu faqat ishlovchi marshrut bilan bitta chaqiruvda yozilganda ishlaydi. Ishlovchi alohida funksiya bo'lsa, Params ni o'zingiz berasiz.
  • req.query — to'rt xil bo'lishi mumkin. ParsedQs tipi ?til=uz (satr), ?til=uz&til=ru (massiv) va ?til[a]=1 (obyekt) holatlarini qamraydi. Shuning uchun req.query.til ni to'g'ridan string ga berib bo'lmaydi. Kanon har parametrni bitta() 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:

ts
.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 YangiVazifa deb 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:

ts
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:

text
url, sorovId

Ikkinchi e'londan keyin Sorov da ikkala maydon ham majburiy. Birini tushirib qoldirsangiz:

ts
interface Sorov {
  url: string;
}
interface Sorov {
  sorovId: string;
}

const s: Sorov = { url: "/vazifalar" };

Xato:

text
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:

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 global faqat modul ichida ishlaydi.
  • namespace — bu yerda faqat tip e'loni, kod yaratmaydi. tsconfig dagi erasableSyntaxOnly (type stripping darsi) .d.ts dagi 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 uchun sorovId() ilovada birinchi ulanadi.

3.3 Middleware: X-Request-Id

src/umumiy/sorov-id.ts (kanon):

ts
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
HTTP/1.1 201 Created
X-Request-Id: kanon-17-0001
Location: /vazifalar/1

3.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:

ts
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();
};
text
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:

ts
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:

text
500
416 Range Not Satisfiable
500
500

if 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):

Sovuq start: .ts to'g'ridan va tsc build
  • 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). Kanon npm run check uchalasini birga ishlatadi: tip, lint, test.

5. API qadami: tipli ishlovchi, merging va commit (holat 17)

Holat 17 ning oxirgi qismi. O'zgarishlar:

  1. Yangi src/express.d.ts — «Express'ning Request iga qo'shish» bo'limidagi fayl.
  2. Yangi src/umumiy/sorov-id.ts va ilova.ts da birinchi middleware:
diff
+import { sorovId } from "./umumiy/sorov-id.ts";
 …
   app.disable("x-powered-by");
+  app.use(sorovId());
   app.use(helmet());
  1. Xato ishlovchi logga ID yozadi. Uzun status < 500 && expose sharti esa «Type guard: httpErrorsmi» bo'limidagi funksiyaga ajratiladi (to'liq faylda ko'rasiz):
diff
 const xatoIshlovchi: ErrorRequestHandler =
-  (xato: unknown, _req, res, next) => {
+  (xato: unknown, req, res, next) => {
 …
-  console.error(xato);
+  console.error(`[${req.sorovId}]`, xato);
  1. cors sozlamasidagi exposedHeaders: ["Location", "X-Request-Id"] (Mashhur middleware'lar darsida qo'shilgan) endi ma'noga ega: frontend ID'ni o'qiy oladi.
  2. marshrut.ts da 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):

ts
// 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:

ts
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):

bash
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

  • Request ning uchinchi generigi — req. tipi
  • Tekshirilmagan tana uchun to'g'ri tip —
  • declare global ishlashi uchun faylda bo'lishi kerak — export
  • So'rov ID sarlavhasi — X-Request-
Yechim
  • body — tartib: Params, ResBody, ReqBody, Query, Locals.
  • unknown — any tekshiruvni o'chiradi, unknown majbur 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
ts
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:

text
mijoz-123456
yangi-1
yangi-2
yangi-3

To'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
ts
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:

text
ok: 9
matn 1–200 belgi
tana — obyekt, matn bilan

Kanondagi 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.ts kabi fayl bor: req.user, req.sorovId, req.tenant. Auth middleware req.user ni 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: "unknown va any farqi?", "Declaration merging nima va qachon kerak?"

Xulosa

  • Request<Params, ResBody, ReqBody, Query, Locals> — tartibni yodlang; Response<ResBody> noto'g'ri javobni ushlaydi.
  • req.params tipi 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 --noEmit alohida.

Keyingi dars: REST prinsiplari va resurslar — API'ni resurslar atrofida loyihalash va vazifalar-api ga /teglar resursini qo'shish.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Express TypeScript bilan: tipli ishlovchi (handler), req.body va declaration merging — IlmHamroh