IlmHamroh
JavaScript Full-stack/29-qism. Backend integratsiyalar: fayl, to'lov, bot, realtime64/69-dars14 daqiqa
Mundarija (25)

Hono: Web standartlari ustida bir kod — Node, Bun, Deno va Workers

Qisqacha: Hono — brauzerdagi Request va Response obyektlari ustiga qurilgan kichik backend freymvork. Ilova bitta fetch(request) → response funksiyasi, shuning uchun bir xil kod Node, Bun, Deno, Cloudflare Workers va AWS Lambda'da ishlaydi. @hono/zod-validator bilan validatsiya, hc klienti bilan esa frontend marshrut turlarini TypeScript orqali "biladi".

Bu darsda

  • "Fetch handler" g'oyasini tushunasiz va uni freymvorksiz, brauzerda yozib ko'rasiz.
  • Hono'da marshrut, c.json, parametrlar va middleware yozasiz.
  • @hono/zod-validator ning xom xatosini o'qilishi oson javobga aylantirasiz.
  • hc RPC klienti TypeScript xatosini qanday ushlashini ko'rasiz.
  • Bitta faylni Node, Bun va Deno'da ishga tushirasiz.

Oldin bilishingiz kerak: Fastify, Built-in fetch va undici, Middleware va request lifecycle, Zod 4 serverda.

Oldingi darsda Fastify sxemani markazga qo'yishini ko'rdik. Hono esa boshqa narsani markazga qo'yadi — Web standartlarini.

1. Nega bu kerak?

Express va Fastify Node'ning http moduliga bog'langan: req va res — Node obyektlari. Bunday kod Cloudflare Workers'da ishlamaydi, chunki u yerda Node yo'q. Workers, Deno va Bun esa boshqa tilda gapiradi: so'rov — Request, javob — Response. Bu obyektlarni siz brauzerdagi fetch dan bilasiz.

Hono shu tilda yozilgan. Ilova — oddiy funksiya: Request oladi, Response qaytaradi. Har runtime bunday funksiyani o'zi chaqira oladi.

Biz bitta Hono faylini uch runtime'da ishga tushirdik (Node 24.21.0, Bun 1.4.3, Deno 2.9.6; hono 4.13.13):

js
import { Hono } from "hono";

const app = new Hono();
app.get("/salom", (c) => c.json({
  salom: "dunyo", runtime: c.req.header("x-r"),
}));

const ish = typeof Bun !== "undefined" ? "Bun " + Bun.version
  : typeof Deno !== "undefined" ? "Deno " + Deno.version.deno
  : "Node " + process.versions.node;
const r = await app.request("/salom", { headers: { "x-r": ish } });
console.log(r.status, await r.text());

node h.mjs, bun h.mjs, deno run h.mjs natijalari:

text
200 {"salom":"dunyo","runtime":"Node 24.21.0"}
200 {"salom":"dunyo","runtime":"Bun 1.4.3"}
200 {"salom":"dunyo","runtime":"Deno 2.9.6"}

Kod bir harf ham o'zgarmadi. Deno'ga hech qanday ruxsat bayrog'i ham kerak bo'lmadi: app.request() tarmoqqa chiqmaydi, faylni o'qimaydi.

O'xshatish: Express — faqat bitta markadagi rozetkaga mos vilka. Hono — xalqaro standartdagi vilka: qaysi mamlakatga borsangiz ham, o'tkazgich (adapter) bilan ulanadi.

2. Fetch handler: freymvorksiz

2.1 G'oya

Hono'ni ochishdan oldin uning yuragini o'zimiz yozamiz. Bu blok brauzerda ham jonli ishlaydi — Request, Response, URL brauzerda bor:

js
const vazifalar = [{ id: 1, matn: "Non olish" }];

async function ishlov(req) {
  const url = new URL(req.url);
  if (req.method === "GET" && url.pathname === "/v1/vazifalar") {
    return Response.json(vazifalar);
  }
  if (req.method === "POST" && url.pathname === "/v1/vazifalar") {
    const tana = await req.json().catch(() => ({}));
    if (typeof tana.matn !== "string" || !tana.matn.trim()) {
      return Response.json({ xato: "matn" }, { status: 400 });
    }
    const v = { id: vazifalar.length + 1, matn: tana.matn.trim() };
    vazifalar.push(v);
    return Response.json(v, { status: 201 });
  }
  return new Response("Topilmadi", { status: 404 });
}

const MANZIL = "https://example.com/v1/vazifalar";
const yubor = (matn) => new Request(MANZIL, {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ matn }),
});

for (const so of [yubor(" Sport "), yubor(""),
  new Request(MANZIL),
  new Request("https://example.com/yoq")]) {
  const r = await ishlov(so);
  console.log(r.status, await r.text());
}

Konsolda:

text
201 {"id":2,"matn":"Sport"}
400 {"xato":"matn"}
200 [{"id":1,"matn":"Non olish"},{"id":2,"matn":"Sport"}]
404 Topilmadi

ishlov — butun "server". U port ochmaydi, Node'ga bog'lanmagan. Kim Request bersa, unga Response qaytaradi. Bun'da Bun.serve({ fetch: ishlov }), Deno'da Deno.serve(ishlov), Workers'da export default { fetch: ishlov }.

2.2 Nega freymvork kerak

if lar ko'payib ketadi: /v1/vazifalar/:id, query, middleware, xatolar. Hono aynan shu qismni beradi: tez router, c (kontekst) obyekti, middleware zanjiri va yordamchilar. Hono'ning o'zida bog'liqlik yo'q — faqat Web API'lar.

flowchart LR
  K["Hono ilova: app.fetch"] --> N["Node: @hono/node-server"]
  K --> B["Bun: Bun.serve"]
  K --> D["Deno: Deno.serve"]
  K --> W["Cloudflare Workers"]
  K --> L["AWS Lambda: hono/aws-lambda"]

Diagrammaga qarang: markazda bitta app.fetch. Har runtime'ga faqat ulash usuli farq qiladi. Node va Lambda Web standartida gapirmaydi — ularga kichik adapter kerak.

Tekshirib ko'ring: Nega Express'ni Cloudflare Workers'da to'g'ridan ishga tushirib bo'lmaydi, Hono'ni esa bo'ladi?

Javob

Express node:http ning IncomingMessage va ServerResponse obyektlari bilan ishlaydi. Workers'da Node'ning HTTP serveri yo'q — u yerda faqat Request → Response funksiyasi chaqiriladi. Hono ichkarida faqat Web standartlarini ishlatgani uchun unga moslashish shart emas.

3. Hono'da marshrutlar

3.1 Birinchi ilova

bash
npm i hono@4.13.13 @hono/node-server@2.1.5
js
import { Hono } from "hono";
import { serve } from "@hono/node-server";

const app = new Hono();

app.get("/v1/salom", (c) => c.json({ salom: "dunyo" }));
app.get("/v1/vazifalar/:id", (c) => {
  const id = Number(c.req.param("id"));
  const tartib = c.req.query("tartib") ?? "yangi";
  return c.json({ id, tartib });
});

serve({ fetch: app.fetch, port: 3000, hostname: "127.0.0.1" });
  • c — kontekst: so'rov (c.req), javob yasovchilar (c.json, c.text, c.html, c.body), holat (c.set, c.get).
  • c.req.param("id") — yo'l parametri, c.req.query("tartib") — query. Ikkalasi ham satr.
  • @hono/node-server — Node'ning http serverini app.fetch ga ulaydigan adapter.

3.2 Portsiz test: app.request()

app.request(yol, init) — Fastify'ning inject() iga o'xshash. U Request yasab, app.fetch ga beradi va Response qaytaradi. Darsdagi tajribalar shu usulda.

Maslahat: c.json(data, 201) — status ikkinchi argument. c.status(201) ni alohida ham chaqirish mumkin, lekin bitta joyda yozilgani o'qishga osonroq.

4. Middleware: await next()

Hono middleware'i — async (c, next) => { … }. await next() — keyingi middleware va marshrut bajariladi. Undan keyingi qator javob tayyor bo'lgandan keyin ishlaydi. Bu "piyoz" tartibini keyingi darsda Koa misolida batafsil ko'ramiz.

Biz vaqt o'lchaydigan va log yozadigan middleware qo'ydik (Node 24.21.0, hono 4.13.13):

js
app.use(async (c, next) => {
  const t0 = Date.now();
  await next();
  c.header("X-Vaqt", `${Date.now() - t0}ms`);
  console.log("log:", c.req.method, c.req.path, c.res.status);
});

Uchta so'rov yubordik: to'g'ri POST, bo'sh matn bilan POST va yo'q manzil. Har qator oxiridagi statusga qarang:

text
log: POST /v1/vazifalar 201
log: POST /v1/vazifalar 400
log: GET /yoq 404

c.res.status — await next() dan keyin javob allaqachon tayyor. 404 ham log'ga tushdi: Hono o'zi 404 Not Found matnini qaytardi.

Hono'da tayyor middleware'lar paket ichida: hono/cors, hono/logger, hono/secure-headers, hono/body-limit, hono/etag, hono/request-id (hono.dev "Middleware" bo'limi bo'yicha; kanonda sinalmagan). Express'dagidek alohida paket o'rnatish shart emas.

js
import { cors } from "hono/cors";
import { secureHeaders } from "hono/secure-headers";

app.use("/v1/*", cors({ origin: "https://example.com" }));
app.use(secureHeaders());

5. Validatsiya: @hono/zod-validator

5.1 Xom xato

bash
npm i @hono/zod-validator@0.9.1 zod@4.6.7
js
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";

const Vazifa = z.object({ matn: z.string().trim().min(1).max(100) });

app.post("/v1/vazifalar", zValidator("json", Vazifa), (c) => {
  const { matn } = c.req.valid("json");
  return c.json({ id: 1, matn }, 201);
});

zValidator("json", sxema) — tanani Zod bilan tekshiradigan middleware. Marshrut ichida c.req.valid("json") — tekshirilgan va tozalangan qiymat (trim bajarilgan). Biz " Non olish " va "" yubordik. Ikkinchi javobga qarang — uni frontend dasturchi ko'radi:

text
201 {"id":1,"matn":"Non olish"}
400 {"success":false,"error":{"name":"ZodError","message":"[\n  {\n    \"origin\": \"string\",\n    \"code\": \"too_small\",\n    \"minimum\": 1,\n    \"inclusive\": true,\n    \"path\": [\n      \"matn\"\n    ],\n    \"message\": \"Too small: expected string to have >=1 characters\"\n  }\n]"}}

Ikkinchi javob — xom Zod xatosi: message ichida JSON satr, \n belgilari bilan. Kanon ham shuni qayd etgan: "xom zod xabari — formatlash kerak". Bu javobni mijozga shu holida berish yomon: o'qib bo'lmaydi va ichki tuzilmani ochib qo'yadi.

5.2 Hook bilan formatlash

zValidator ning uchinchi argumenti — natija hook'i. Unda success false bo'lsa, o'z javobingizni qaytarasiz. Zod 4 dagi z.prettifyError (Zod 4 serverda) xatoni o'qiladigan matnga aylantiradi:

ts
const app = new Hono().post(
  "/v1/vazifalar",
  zValidator("json", Vazifa, (natija, c) => {
    if (!natija.success) {
      return c.json(
        {
          xato: "validatsiya",
          tafsilot: z.prettifyError(natija.error),
        },
        400,
      );
    }
  }),
  (c) => c.json({ id: 1, matn: c.req.valid("json").matn }, 201),
);

Biz ishga tushirdik (matn: ""):

text
400 {
  xato: 'validatsiya',
  tafsilot: '✖ Too small: expected string to have >=1 characters\n  → at matn'
}

vazifalar-api da bu joyda Problem Details yasaladi — g'oya bir xil.

Tekshirib ko'ring: Hook ichida natija.success true bo'lsa hech narsa qaytarmadik. Nima bo'ladi?

Javob

Hook hech narsa qaytarmasa, validator keyingi qadamga — marshrut funksiyasiga o'tadi. Response qaytarsa — zanjir shu yerda to'xtaydi va o'sha javob ketadi.

6. RPC klient: hc

6.1 Muammo

Frontend fetch("/v1/vazifalar", { body: JSON.stringify({ text: "…" }) }) yozdi — matn o'rniga text. Xato faqat ishga tushganda, 400 bilan bilinadi. tRPC darsida bu muammoni tip orqali yechgandik. Hono'da ham shunga o'xshash yo'l bor — hc klienti.

6.2 Tip eksporti va klient

Marshrutlar zanjir bilan yozilsa, typeof app ichida har marshrutning kirish va chiqish turi bo'ladi. Uni eksport qilasiz, klient import qiladi:

ts
import { hc } from "hono/client";

export type App = typeof app;

const mijoz = hc<App>("http://localhost", {
  fetch: (input: RequestInfo | URL, init?: RequestInit) =>
    app.request(input, init),
});

const yaxshi = await mijoz.v1.vazifalar.$post({
  json: { matn: "Non" },
});
if (yaxshi.status === 201) {
  const v = await yaxshi.json();
  console.log(v.id, v.matn.toUpperCase());
}

Bu yerda klientga fetch sifatida app.request berdik — test uchun tarmoqsiz. Haqiqiy frontendda bu argument kerak emas, manzil yetarli. Natija: 1 NON. v.matn ning turi — string, buni TypeScript o'zi chiqardi.

Endi ataylab matn o'rniga text yozamiz. Biz tsc (TypeScript 6.0.3) bilan tekshirdik:

Kompilyator:

text
TS2353: Object literal may only specify known properties, and 'text' does not exist in type '{ matn: string; }'.

Tarjima: "obyekt literalida faqat ma'lum xususiyatlar bo'lishi mumkin, text esa { matn: string } turida yo'q". Xato kodni ishga tushirishdan oldin chiqdi.

hc tRPC OpenAPI + generator
Qo'shimcha qadam yo'q (tip eksporti) router spetsifikatsiya + generatsiya
Boshqa tillar yo'q yo'q bor
URL'lar oddiy REST tRPC formati oddiy REST

hc faqat frontend ham TypeScript'da va bitta monorepoda bo'lsa foydali. Ommaviy API uchun OpenAPI kerak.

Diqqat: Tip faqat zanjir (new Hono().get(…).post(…)) yoki const routes = app.get(…) bilan saqlanadi. app.get(…) ni alohida qatorlarda chaqirib, typeof app ni eksport qilsangiz — klient marshrutlarni "ko'rmaydi" (hono.dev "RPC" bo'limi).

7. Har runtime'da ishga tushirish

Runtime Ulash Kanon GET req/s
Node serve({ fetch: app.fetch }) (@hono/node-server) 21 634
Bun export default app yoki Bun.serve({ fetch: app.fetch }) 26 325
Deno Deno.serve(app.fetch) sinalmagan
Workers export default app sinalmagan
AWS Lambda export const handler = handle(app) (hono/aws-lambda) Serverless darsida
Bir xil API: soniyasiga so'rov (GET)
  • Express (Node)solishtirish10 698 req/s
  • Hono (Node)@hono/node-server 2.1.521 634 req/s
  • Hono (Bun)Bun 1.4.326 325 req/s

Manba: IlmHamroh kanon o'lchovi, 2026-10-27: hono 4.13.13, autocannon 8.0.0, 50 ulanish, 5 s, Windows 11, i5-12500H; bo'sh API, bazasiz

POST (validatsiya bilan) Node'da 15 648, Bun'da 24 290. Bir kod, ikki runtime — farqni runtime berdi. Bazasiz API ekanini unutmang.

Hono'ning o'zida bog'liqlik yo'q va u kichik. Shuning uchun u edge muhitlar (Workers kabi, foydalanuvchiga yaqin serverlar) uchun mashhur. Edge'ni Serverless va edge darsida ko'ramiz.

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Javob o'rniga ZodError matni hook'siz zValidator uchinchi argument bilan formatlang
c.req.param("id") === 5 — false parametr satr Number() yoki Zod z.coerce.number()
hc marshrutlarni bilmaydi typeof app zanjirsiz zanjir yoki const routes = app.get(…)
Node'da app.listen yo'q Hono'da listen yo'q @hono/node-server dagi serve
Context is not finalized middleware next() ni kutmadi await next() yoki Response qaytaring
Express middleware ulanmadi boshqa interfeys Hono middleware'ini toping

Beshinchi qatordagi xato matni Hono manba kodidan (hono-base), kanonda sinalmagan.

9. Mashqlar

Mashqlar kurs/mashqlar/29/64-hono/ da. 2- va 3-mashq uchun alohida papkada npm i hono@4.13.13.

1-mashq (oson): bo'shliqlarni to'ldiring

  • Hono ilova — Request olib, qaytaradigan funksiya.
  • Portsiz test usuli — app.()
  • Node'da ishga tushirish uchun adapter paketi — @hono/
  • Middleware'da keyingi qadamni chaqirish — await ()
  • Tipli klient funksiyasi —
Yechim
  • Response — Web standart obyekti.
  • request — Request yasab, app.fetch ga beradi.
  • node-server — Node http ni app.fetch ga ulaydi.
  • next — keyingi middleware va marshrut.
  • hc — hono/client dan.

2-mashq (o'rta): query va 404

Hono ilova yozing: GET /v1/taomlar — narx query bo'lsa (masalan ?narx=30000), shu narxdan arzon yoki teng taomlarni qaytarsin; bo'lmasa hammasini. GET /v1/taomlar/:id — bitta taom yoki 404 { xato: "topilmadi" }. Taomlar: osh 35 000, lag'mon 28 000, manti 30 000. app.request bilan uch so'rov: ?narx=30000, /2, /7. Ishora: c.req.query("narx") satr qaytaradi, yo'q bo'lsa undefined; c.json(data, 404).

Yechim
js
import { Hono } from "hono";

const taomlar = [
  { id: 1, nom: "Osh", narx: 35000 },
  { id: 2, nom: "Lag'mon", narx: 28000 },
  { id: 3, nom: "Manti", narx: 30000 },
];

const app = new Hono();
app.get("/v1/taomlar", (c) => {
  const narx = c.req.query("narx");
  if (narx === undefined) return c.json(taomlar);
  return c.json(taomlar.filter((t) => t.narx <= Number(narx)));
});
app.get("/v1/taomlar/:id", (c) => {
  const t = taomlar.find((x) => x.id === Number(c.req.param("id")));
  return t ? c.json(t) : c.json({ xato: "topilmadi" }, 404);
});

for (const yol of ["/v1/taomlar?narx=30000", "/v1/taomlar/2",
  "/v1/taomlar/7"]) {
  const r = await app.request(yol);
  console.log(r.status, await r.text());
}

Konsolda (biz ishga tushirdik: Node 24.21.0, hono 4.13.13):

text
200 [{"id":2,"nom":"Lag'mon","narx":28000},{"id":3,"nom":"Manti","narx":30000}]
200 {"id":2,"nom":"Lag'mon","narx":28000}
404 {"xato":"topilmadi"}

3-mashq (qiyin): so'rov ID'si middleware'i

Middleware yozing: so'rovda x-request-id sarlavhasi bo'lsa — o'shani, bo'lmasa "id-" + hisoblagich ni (1, 2, …) olsin, c.set("rid", …) bilan saqlasin va javobga x-request-id sarlavhasini qo'ysin. Marshrut GET /v1/salom — { rid: c.get("rid") }. Ikki so'rov sarlavhasiz, bittasi x-request-id: tashqi-7 bilan. Har javobning statusi, sarlavhasi va tanasini chiqaring. Ishora: Correlation ID darsidagi g'oya; sarlavha c.header(nom, qiymat).

Yechim
js
import { Hono } from "hono";

let hisob = 0;
const app = new Hono();
app.use(async (c, next) => {
  const rid = c.req.header("x-request-id") ?? `id-${++hisob}`;
  c.set("rid", rid);
  await next();
  c.header("x-request-id", rid);
});
app.get("/v1/salom", (c) => c.json({ rid: c.get("rid") }));

for (const h of [{}, {}, { "x-request-id": "tashqi-7" }]) {
  const r = await app.request("/v1/salom", { headers: h });
  console.log(r.status, r.headers.get("x-request-id"),
    await r.text());
}

Konsolda (biz ishga tushirdik: Node 24.21.0, hono 4.13.13):

text
200 id-1 {"rid":"id-1"}
200 id-2 {"rid":"id-2"}
200 tashqi-7 {"rid":"tashqi-7"}

Haqiqiy loyihada tashqaridan kelgan ID'ni uzunligi va belgilari bo'yicha tekshiring — aks holda log'ga istalgan matn yoziladi (log injection).

10. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi

vazifalar-api Express'da qoladi. Kanon Hono'ni tajribada sinadi: bir xil API Node'da (@hono/node-server) va Bun'da; validatsiya @hono/zod-validator bilan. Natijalar — yuqoridagi grafik va xom Zod xatosi. Serverless tajribasida Hono'ning hono/aws-lambda adapteri ham ishlatildi — keyingi darslarda ko'ramiz.

Nega loyihaga kirmaydi? Loyiha Node'da ishlaydi va Node'dan chiqmaydi (33-qismda VPS va Docker). Hono'ning asosiy kuchi — ko'p runtime — bizga hozir kerak emas. Lekin Hono'ni bilish foydali: kichik servis, webhook qabul qiluvchi yoki edge funksiya kerak bo'lganda u eng qulay tanlovlardan biri.

O'zingiz sinab ko'ring: 3-mashq faylini node, keyin (o'rnatgan bo'lsangiz) bun va deno run bilan ishga tushiring — natija bir xil bo'lishi kerak.

11. Real ishda

  • Cloudflare Workers va edge — Hono shu muhitlar uchun eng ko'p tanlanadigan freymvorklardan biri.
  • Kichik servislar — webhook qabul qilish, proksi, BFF (frontend uchun backend).
  • Next.js ichida — Route Handler'da Hono ilovasini ulash naqshi uchraydi (hono.dev "Vercel/Next.js" bo'limi).
  • Intervyuda: "Web standart Request/Response nima beradi?", "Hono nega bir nechta runtime'da ishlaydi?", "Tipli RPC klient qanday ishlaydi?"

Xulosa

  • Hono ilovasi — fetch(Request) → Response; shu sababli Node, Bun, Deno, Workers, Lambda'da bir kod.
  • c.json, c.req.param, c.req.query, app.request() — asosiy vositalar; parametrlar satr.
  • Middleware — async (c, next); await next() dan keyin javob tayyor.
  • zValidator hook'siz xom ZodError qaytaradi — formatlang.
  • hc marshrut turlarini TypeScript orqali beradi: noto'g'ri maydon — kompilyatsiya xatosi.
  • Kanonda Hono Node'da 21 634, Bun'da 26 325 so'rov/s (bazasiz).

Keyingi dars: Koa, Elysia va AdonisJS — "piyoz" middleware, Bun'ga xos freymvork va hammasi ichida bo'lgan MVC.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!