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

GraphQL asoslari: sxema, Query, Mutation va resolver'lar Node.js'da

Qisqacha: GraphQL — mijoz o'ziga kerakli maydonlarni o'zi tanlab so'raydigan API tili. Server bitta endpoint'da (/graphql) tiplangan sxema e'lon qiladi: type Vazifa { id: Int!, matn: String! }, o'qish uchun Query, o'zgartirish uchun Mutation. Har maydonni resolver funksiyasi hisoblaydi. Javob aynan so'rov shaklida keladi, xatolar esa errors massivida — ko'pincha HTTP 200 bilan.

Bu darsda

  • SDL'da tiplar, skalyarlar, ! (majburiy), ro'yxatlar va input tiplarini yozasiz.
  • Query va Mutation resolver'larini va maydon resolver'ini (Vazifa.egasi) tushuntirasiz.
  • GraphQL Yoga serveriga so'rov, o'zgaruvchi va mutation yuborib, javob shaklini o'qiysiz.
  • GraphQL xato formatini va HTTP status kodlari bilan bog'liq tuzoqni bilasiz.
  • REST va GraphQL orasida loyiha uchun tanlay olasiz.

Oldin bilishingiz kerak: REST prinsiplari va resurslar, Filtrlash, saralash va maydon tanlash, OpenAPI 3.1 spetsifikatsiyasi.

1. Nega bu kerak?

vazifalar mobil ilovasi bosh ekranida vazifalar ro'yxatini ko'rsatadi: faqat matn va egasining ismi. REST'da buning uchun:

  1. GET /v1/vazifalar — har vazifaning hamma maydonlari keladi: id, matn, bajarildi, muddat, ustuvorlik, teglar, yaratilgan... Ekranga ikkitasi kerak. Bu — over-fetching (ortiqcha olish).
  2. Egasining ismi javobda yo'q, faqat egasiId. Har egasi uchun GET /v1/foydalanuvchilar/:id. Bu — under-fetching (yetarlicha olmaslik): bitta ekran uchun ko'p so'rov.

REST'da buning davosi bor: maydon tanlash (?fields=matn) va ?include=egasi. Lekin har endpoint uchun alohida yoziladi. GraphQL (Facebook, 2012-yilda ichki, 2015-yilda ochiq) shu g'oyani API'ning markaziga qo'ydi: mijoz so'rovda kerakli maydonlarni daraxt shaklida yozadi, server aynan shuni qaytaradi.

graphql
{
  vazifalar(bajarildi: false) {
    matn
    egasi { ism }
  }
}

Bitta so'rov, kerakli maydonlar, ichma-ich bog'langan ma'lumot.

Darsdagi server chiqishlari — biz ishga tushirgan GraphQL Yoga 5.24.6 + graphql 17.0.2 serveridan (Node 24.21.0, 2026-10-28) va kanon tajribasidan (2026-10-27). Apollo Server — sinalmagan, hujjat bo'yicha.

2. Sxema: SDL

2.1 Tiplar

GraphQL API'ning markazi — sxema (schema). U SDL (Schema Definition Language) tilida yoziladi:

graphql
type Foydalanuvchi {
  id: Int!
  ism: String!
}

type Vazifa {
  id: Int!
  matn: String!
  bajarildi: Boolean!
  egasi: Foydalanuvchi!
}
  • Skalyar tiplar — Int (32 bitli butun), Float, String, Boolean, ID (identifikator, satr sifatida uzatiladi).
  • ! — majburiy (non-null). String! — hech qachon null emas. ! siz — null bo'lishi mumkin. Diqqat: bu TypeScript'ga teskari — GraphQL'da sukut nullable.
  • [Vazifa!]! — ro'yxatning o'zi null emas va ichidagi elementlar ham null emas.
  • Obyekt tiplar bir-biriga bog'lanadi: Vazifa.egasi — Foydalanuvchi. Bu bog'lanish so'rovda ichma-ich tanlashni beradi.

O'z skalyar tiplaringiz ham bo'ladi (scalar DateTime) — ularni serializatsiya qilish qoidasi bilan e'lon qilasiz.

2.2 Kirish nuqtalari: Query va Mutation

Ikkita maxsus tip — API'ning "eshiklari":

graphql
type Query {
  vazifalar(bajarildi: Boolean): [Vazifa!]!
  vazifa(id: Int!): Vazifa
}

input YangiVazifa {
  matn: String!
}

type Mutation {
  vazifaQosh(kirish: YangiVazifa!): Vazifa!
  bajar(id: Int!): Vazifa!
}
  • Query — o'qish (REST'dagi GET). Maydonlari argument olishi mumkin: vazifa(id: Int!).
  • Mutation — o'zgartirish (POST, PATCH, DELETE). Nomi — fe'l: vazifaQosh, bajar.
  • input — argument sifatida beriladigan obyekt. Oddiy type argument bo'la olmaydi — input alohida, chunki kirish va chiqish shakli odatda farq qiladi (kirishda id yo'q).
  • vazifa(id: Int!): Vazifa — natija nullable: topilmasa null. Bu GraphQL'dagi "404".

Uchinchi kirish nuqtasi — Subscription (realtime) — keyingi darsda.

Sxema — shartnoma. U OpenAPI bilan bir xil vazifani bajaradi, faqat API tilining ichiga qurilgan: server sxemani introspection so'rovi bilan o'zi tavsiflaydi, vositalar (GraphiQL, kod generatorlari) uni shu yo'l bilan o'qiydi.

3. Resolver'lar

3.1 Har maydon — funksiya

Sxema "nima bor" deydi. Resolver — "qanday olinadi". Har resolver to'rt argument oladi: (parent, args, context, info).

js
const resolvers = {
  Query: {
    vazifalar: (_p, { bajarildi }) =>
      bajarildi === undefined
        ? VAZ
        : VAZ.filter((v) => v.bajarildi === bajarildi),
    vazifa: (_p, { id }) => VAZ.find((v) => v.id === id) ?? null,
  },
  Mutation: {
    vazifaQosh: (_p, { kirish }) => {
      const v = { id: keyingiId++, matn: kirish.matn.trim(),
        bajarildi: false, egasiId: 1 };
      VAZ.push(v);
      return v;
    },
  },
  Vazifa: {
    egasi: (v) => EGALAR.find((e) => e.id === v.egasiId),
  },
};
  • parent — ota obyekt. Vazifa.egasi resolveri uchun bu — vazifaning o'zi (v), u yerdan egasiId olinadi.
  • args — maydon argumentlari ({ id: 2 }), sxemaga ko'ra tekshirilgan.
  • context — har so'rov uchun umumiy obyekt: joriy foydalanuvchi, baza ulanishi, loader'lar. Autentifikatsiya shu yerda (28-qism sessiyasi context ga tushadi).
  • info — so'rovning o'zi haqida ma'lumot (qaysi maydonlar tanlangan). Kamdan-kam kerak.

Resolver yozilmagan maydon uchun sukut resolver ishlaydi: parent[maydonNomi]. Shuning uchun Vazifa.matn uchun resolver yozmadik — vazifa obyektida matn bor. egasi esa obyektda yo'q, faqat egasiId — resolver kerak.

3.2 Bajarilish tartibi

Server so'rovni daraxt bo'ylab yuradi: avval Query.vazifalar — massiv. Keyin har element uchun tanlangan maydonlar: matn (sukut), egasi (resolver). egasi natijasi uchun yana tanlanganlar: ism. Mantiqni JS'da takrorlaymiz — kichik so'rov tahlilchisi va bajaruvchi bilan:

js
function tahlil(matn) {
  const tok = matn.match(/[{}]|\w+/g);
  let i = 0;
  function tanlov() {
    const maydonlar = {};
    i++; // "{"
    while (tok[i] !== "}") {
      const nom = tok[i++];
      maydonlar[nom] = tok[i] === "{" ? tanlov() : true;
    }
    i++; // "}"
    return maydonlar;
  }
  return tanlov();
}

function bajar(qiymat, tanlov, resolverlar, tur) {
  if (Array.isArray(qiymat)) {
    return qiymat.map((x) => bajar(x, tanlov, resolverlar, tur));
  }
  const natija = {};
  for (const [nom, ichki] of Object.entries(tanlov)) {
    const r = resolverlar[tur]?.[nom];
    const v = r ? r(qiymat) : qiymat[nom];
    natija[nom] = ichki === true
      ? v
      : bajar(v, ichki, resolverlar, ichki.__tur ?? nom);
  }
  return natija;
}

const EGALAR = [{ id: 1, ism: "Aziz" }, { id: 2, ism: "Dilnoza" }];
const VAZ = [
  { id: 1, matn: "Non olish", bajarildi: false, egasiId: 1 },
  { id: 2, matn: "Hisobot", bajarildi: true, egasiId: 2 },
];
const resolverlar = {
  Vazifa: { egasi: (v) => EGALAR.find((e) => e.id === v.egasiId) },
};

const sorov = tahlil("{ matn egasi { ism } }");
console.log(JSON.stringify(sorov));
console.log(JSON.stringify(bajar(VAZ, sorov, resolverlar, "Vazifa")));

Konsolda:

text
{"matn":true,"egasi":{"ism":true}}
[{"matn":"Non olish","egasi":{"ism":"Aziz"}},{"matn":"Hisobot","egasi":{"ism":"Dilnoza"}}]

Javob aynan so'rov shaklida: bajarildi, id, egasiId so'ralmagan — ular yo'q. Bu o'quv bajaruvchisi: haqiqiy graphql paketi argumentlarni, fragmentlarni, tiplarni tekshirishni, asinxron resolver'larni va xatolarni ham biladi. Bizdagi __tur esa tip nomini topish uchun soddalashtirish — haqiqiy bajaruvchi buni sxemadan biladi.

Bajarish daraxti shunday ko'rinadi:

flowchart TB
  Q["Query.vazifalar"] --> V1["Vazifa 1"]
  Q --> V2["Vazifa 2"]
  V1 --> M1["matn: sukut resolver"]
  V1 --> E1["egasi: resolver"]
  E1 --> I1["ism: sukut"]
  V2 --> M2["matn: sukut resolver"]
  V2 --> E2["egasi: resolver"]
  E2 --> I2["ism: sukut"]

Har egasi tuguni — alohida resolver chaqiruvi. Vazifalar ko'paysa, bu tugunlar ham ko'payadi.

Tekshirib ko'ring: So'rovda egasi { ism } 100 ta vazifa uchun so'ralsa, Vazifa.egasi resolveri necha marta chaqiriladi?

Javob

100 marta — har vazifa uchun alohida. Agar resolver ichida bazaga so'rov bo'lsa — 100 ta SQL so'rov. Bu GraphQL'dagi mashhur N+1 muammosi (N+1 muammosi). Kanon buni o'lchagan: 50 vazifa — 50 "baza" chaqiruvi. Yechim — DataLoader, keyingi darsda.

4. Server: GraphQL Yoga

4.1 Ishga tushirish

Node'da ikki mashhur server bor:

  • GraphQL Yoga (The Guild) — Web standart Request/Response ustiga qurilgan, Node http, Express, Fastify, Bun, Deno, edge'da bir xil ishlaydi. Kanon shuni tanlagan.
  • Apollo Server — eng ko'p tarqalgan, Apollo ekotizimi (Client, Federation, Studio) bilan. Kanonda sinalmagan.

Ikkalasi ham graphql (graphql-js) paketining ustida — sxema, tahlil va bajarish uniki. Bizning server (resolver'lar yuqorida, typeDefs — darsdagi SDL):

js
import { createYoga, createSchema } from "graphql-yoga";
import http from "node:http";

const yoga = createYoga({
  schema: createSchema({ typeDefs, resolvers }),
  graphiql: false,
});
http.createServer(yoga).listen(4000);

graphiql: true (sukut) bo'lsa, brauzerda /graphql ni ochganda GraphiQL — so'rov yozish, avtoto'ldirish va hujjat paneli — chiqadi. Production'da odatda o'chiriladi.

4.2 So'rovlar

GraphQL so'rovi — oddiy HTTP POST: tanada JSON { "query": "...", "variables": {...} }. Biz quyidagi so'rovlarni yubordik (har javob oldida HTTP status):

js
await gql("{ vazifalar(bajarildi: false) { matn egasi { ism } } }");
await gql(
  "query Bitta($id: Int!) { vazifa(id: $id) { id matn bajarildi } }",
  { id: 2 },
);
await gql("{ vazifa(id: 99) { matn } }");
await gql(
  "mutation { vazifaQosh(kirish: { matn: \"  Sut olish \" }) " +
    "{ id matn bajarildi } }",
);
text
200 {"data":{"vazifalar":[{"matn":"Non olish","egasi":{"ism":"Aziz"}},{"matn":"Ingliz tili mashqi","egasi":{"ism":"Aziz"}}]}}
200 {"data":{"vazifa":{"id":2,"matn":"Hisobot tayyorlash","bajarildi":true}}}
200 {"data":{"vazifa":null}}
200 {"data":{"vazifaQosh":{"id":4,"matn":"Sut olish","bajarildi":false}}}
  • Javob doim data ichida, so'rov shaklida.
  • Ikkinchi so'rov — o'zgaruvchi bilan: $id so'rov matnida, qiymati alohida variables da. Qiymatni so'rov satriga yopishtirmang — xuddi SQL'dagi parametrlar kabi (Parametrli so'rovlar va SQL injection). Bitta — operatsiya nomi: loglarda va kuzatuvda qaysi so'rov ekanini ko'rsatadi.
  • Uchinchi: topilmadi — xato emas, null (sxemada Vazifa nullable).
  • To'rtinchi — mutation. Resolver trim() qildi.

GET ham mumkin: GET /graphql?query={ vazifa(id: 1) { matn } } — biz yubordik, javob 200 {"data":{"vazifa":{"matn":"Non olish"}}}. GET faqat query uchun. Mutation'ni GET bilan yuborib ko'rdik:

text
GET mutation 405 {"errors":[{"message":"Can only perform a mutation operation from a POST request.","extensions":{"code":"BAD_REQUEST"}}]}

Sabab — o'zgartirish GET bilan bo'lmasligi kerak: havola yoki <img> orqali mutation yuborish (CSRF) yo'li yopiladi. GET so'rovlarni HTTP keshlash mumkin — buni keyingi darsda persisted queries bilan ko'ramiz.

5. Xatolar

5.1 Xato formati

GraphQL'da xatolar errors massivida. Biz uch xil xato yubordik:

text
200 {"errors":[{"message":"Vazifa topilmadi","locations":[{"line":1,"column":12}],"path":["bajar"],"extensions":{"code":"NOT_FOUND"}}],"data":null}
200 {"errors":[{"message":"Cannot query field \"parol\" on type \"Vazifa\".","locations":[{"line":1,"column":19}],"extensions":{"code":"GRAPHQL_VALIDATION_FAILED"}}]}
200 {"errors":[{"message":"Argument \"Query.vazifa(id:)\" of type \"Int!\" is required, but it was not provided.","locations":[{"line":1,"column":3}],"extensions":{"code":"GRAPHQL_VALIDATION_FAILED"}}]}

Ikki xil xato bor:

  1. Bajarish xatosi (birinchisi) — resolver GraphQLError tashladi: throw new GraphQLError("Vazifa topilmadi", { extensions: { code: "NOT_FOUND" } }). path — qaysi maydonda, extensions.code — mashina uchun kod (REST'dagi Problem Details type iga o'xshash). data: null — bajar maydoni Vazifa! (non-null), shuning uchun null ota darajaga ko'tarildi.
  2. Validatsiya xatosi (ikkinchi, uchinchi) — so'rov sxemaga mos emas: parol maydoni yo'q; majburiy id berilmagan. Bunda data umuman yo'q: so'rov bajarilmadi.

Validatsiya xatolari tarjima qilinmaydi — ular dasturchi uchun. Foydalanuvchiga ko'rsatiladigan xato — bajarish xatosi, extensions.code bo'yicha frontend tarjima qiladi.

Muhim: bitta so'rovda bir nechta maydon bo'lsa, biri xato bo'lib, qolganlari muvaffaqiyatli bo'lishi mumkin — qisman natija: data da muvaffaqiyatlilar, errors da xatolar. REST'da bunday holat deyarli yo'q.

5.2 HTTP status: 200 yoki 400?

Yuqoridagi uchala xato — HTTP 200. REST'dan kelgan dasturchini bu chalg'itadi: monitoring "hamma so'rov muvaffaqiyatli" deydi. Biz bitta tajriba qildik: o'sha validatsiya xatosini ikki xil Accept sarlavhasi bilan yubordik:

text
application/json { salom } 200 application/json; charset=utf-8
application/json { parol } 200 application/json; charset=utf-8
application/graphql-response+json { salom } 200 application/graphql-response+json; charset=utf-8
application/graphql-response+json { parol } 400 application/graphql-response+json; charset=utf-8

Yangi media tur application/graphql-response+json bilan so'ralsa, Yoga validatsiya xatosiga 400 qaytardi. Bu — "GraphQL over HTTP" spetsifikatsiyasi qoralamasi (GraphQL Foundation) talabi: yangi media turda so'rov bajarilmagan bo'lsa — 4xx; eski application/json da esa moslik uchun 200 qoladi. Yangi klientlar (graphql-http, urql, Apollo Client'ning yangi versiyalari) yangi turni so'raydi. Monitoringda shunga qarab, errors maydonini ham hisobga oling.

6. REST yoki GraphQL?

REST GraphQL
Endpoint ko'p (/vazifalar, /vazifalar/:id) bitta (/graphql)
Javob shakli server belgilaydi mijoz tanlaydi
Shartnoma OpenAPI (alohida) sxema (ichida, introspection)
HTTP kesh oson (GET + ETag) qiyin (POST, bitta URL)
Xato status kodlar errors + ko'pincha 200
Fayl yuklash oddiy multipart qo'shimcha spetsifikatsiya
Xavf ma'lum chuqur so'rovlar, N+1 (keyingi dars)

GraphQL kuchli joylari: ko'p xil mijoz (veb, mobil, smart-TV) bir xil ma'lumotning turli bo'laklarini so'raydi; ma'lumot grafga o'xshash (foydalanuvchi → buyurtmalar → mahsulotlar → sharhlar); frontend jamoasi backend'ni kutmasdan maydon tanlashni xohlaydi. Zaif joylari: oddiy CRUD, ochiq ommaviy API (keshlash, rate limit oddiy bo'lishi kerak), fayl va strim.

Ko'p kompaniyada ikkalasi birga: ichki mobil/veb uchun GraphQL, hamkorlar uchun REST.

7. Hujumchi nigohi

  1. Sxemani o'rganadi. Introspection yoqiq bo'lsa, hujumchi butun API'ni — ichki maydonlar va mutation'lar bilan — bitta so'rovda oladi. Himoya: ochiq bo'lmagan API'da production'da introspection'ni o'chirish (yashirish xavfsizlik emas, lekin hujumni qiyinlashtiradi); maxfiy maydonni sxemaga umuman qo'ymaslik.
  2. Ruxsatsiz maydonni so'raydi. REST'da himoya endpoint darajasida, GraphQL'da esa har maydon darajasida bo'lishi kerak: Foydalanuvchi.email ni faqat egasi ko'rsin. Himoya: resolver ichida context.foydalanuvchi ni tekshirish yoki ruxsat qatlami (graphql-shield, direktivalar).
  3. Og'ir so'rov yuboradi. egasi { vazifalar { egasi { vazifalar ... } } } — ichma-ich cheksiz. Himoya — chuqurlik va murakkablik limiti (keyingi dars).
  4. Xato xabaridan o'rganadi. Kutilmagan xatoda stack trace yoki SQL matni errors[].message ga tushsa — ichki ma'lumot sizadi. Biz resolver ichida new Error("SELECT * FROM maxfiy jadval xatosi") tashlab ko'rdik:
text
masking 200 {"errors":[{"message":"Unexpected error.","locations":[{"line":1,"column":9}],"path":["buzuq"],"extensions":{"code":"INTERNAL_SERVER_ERROR"}}],"data":{"salom":"Salom","buzuq":null}}

Yoga oddiy Error matnini Unexpected error. bilan almashtirdi (masking) — faqat GraphQLError matni mijozga ko'rinadi. Shu javobda qisman natija ham ko'rinyapti: salom muvaffaqiyatli, buzuq — null va xato. Asl xatoni serverda log qiling.

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Cannot query field "x" on type "Y" maydon sxemada yo'q yoki nomi xato sxemani GraphiQL'da ko'ring
must have a selection of subfields obyekt maydon ichki tanlovsiz so'raldi vazifalar { matn }
Cannot return null for non-nullable field resolver ! maydonga null qaytardi sxemani nullable qiling yoki resolverni tuzating
Monitoringda xato yo'q, foydalanuvchida bor xatolar HTTP 200 bilan errors ni ham sanash; yangi media tur
Ma'lumot egasiId bilan keldi, egasi null maydon resolveri yo'q Vazifa.egasi resolveri
Qiymat so'rov satriga yopishtirilgan o'zgaruvchi ishlatilmagan $id + variables

Biz yuborgan to'rtinchi xato so'rovi ({ vazifalar } — ichki tanlovsiz) aynan ikkinchi qatordagi xabarni berdi: Field "vazifalar" of type "[Vazifa!]!" must have a selection of subfields. Did you mean "vazifalar { ... }"?

9. Mashqlar

Mashqlar kurs/mashqlar/29/59-graphql/ da.

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

  • O'qish uchun kirish tipi —
  • Argument sifatida beriladigan obyekt tipi kalit so'zi —
  • String! dagi ! ma'nosi — null
  • Resolver argumentlari: parent, args, , info
  • Validatsiya xatosi kodi — GRAPHQL_VALIDATION_
Yechim
  • Query — o'zgartirish uchun Mutation.
  • input — input YangiVazifa { matn: String! }.
  • emas — non-null; ! siz maydon nullable.
  • context — har so'rov uchun umumiy obyekt (foydalanuvchi, loader'lar).
  • FAILED — so'rov bajarilmadi, data yo'q.

2-mashq (o'rta): qancha ortiqcha ma'lumot?

Darsdagi tahlil funksiyasini oling. ortiqcha(obyektlar, sorovMatni) yozing: u REST javobi (obyektlar — to'liq) va GraphQL javobi (faqat tanlangan maydonlar, ichma-ich tanlovsiz, birinchi daraja) baytlarini (JSON.stringify(...).length) va tejash foizini qaytarsin.

Yechim
js
function tahlil(matn) {
  const tok = matn.match(/[{}]|\w+/g);
  let i = 0;
  function tanlov() {
    const maydonlar = {};
    i++;
    while (tok[i] !== "}") {
      const nom = tok[i++];
      maydonlar[nom] = tok[i] === "{" ? tanlov() : true;
    }
    i++;
    return maydonlar;
  }
  return tanlov();
}

function ortiqcha(obyektlar, sorovMatni) {
  const maydonlar = Object.keys(tahlil(sorovMatni));
  const kesilgan = obyektlar.map((o) =>
    Object.fromEntries(maydonlar.map((m) => [m, o[m]])),
  );
  const rest = JSON.stringify(obyektlar).length;
  const gql = JSON.stringify(kesilgan).length;
  const foiz = Math.round((1 - gql / rest) * 100);
  return { rest, gql, foiz };
}

const VAZ = Array.from({ length: 20 }, (_, i) => ({
  id: i + 1,
  matn: `Vazifa ${i + 1}`,
  bajarildi: i % 3 === 0,
  ustuvorlik: "orta",
  muddat: null,
  teglar: ["uy", "xarid"],
  yaratilgan: "2026-10-28T09:00:00.000Z",
  egasiId: 1 + (i % 5),
}));
console.log(ortiqcha(VAZ, "{ matn }"));
console.log(ortiqcha(VAZ, "{ id matn bajarildi }"));

Konsolda:

text
{ rest: 3116, gql: 412, foiz: 87 }
{ rest: 3116, gql: 916, foiz: 71 }

Ro'yxat ekrani uchun faqat matn — javob bir necha barobar kichik. Mobil tarmoqda bu sezilarli. Lekin bu hisob siqishsiz: gzip takrorlanuvchi kalitlarni yaxshi siqadi va farq kamayadi. Va REST ham ?fields=matn bilan shu natijaga erisha oladi — GraphQL'ning ustunligi buni har endpoint uchun alohida yozmaslikda.

3-mashq (qiyin): argumentli maydonlar

tahlil ni kengaytiring: maydon argument olishi mumkin — vazifa(id: 2) { matn }, vazifalar(bajarildi: false) { matn }. Argument qiymatlari: butun son, true/false, qo'shtirnoqdagi satr. Natija: { nom: { args: {...}, tanlov: {...} } }, oddiy maydon uchun true. Keyin Query resolverlari bilan bajaring.

Yechim
js
function tahlil(matn) {
  const tok = matn.match(/"[^"]*"|[{}():,]|[\w-]+/g);
  let i = 0;
  function qiymat(t) {
    if (t.startsWith("\"")) return t.slice(1, -1);
    if (t === "true" || t === "false") return t === "true";
    return Number(t);
  }
  function tanlov() {
    const maydonlar = {};
    i++;
    while (tok[i] !== "}") {
      const nom = tok[i++];
      const args = {};
      if (tok[i] === "(") {
        i++;
        while (tok[i] !== ")") {
          const k = tok[i];
          args[k] = qiymat(tok[i + 2]);
          i += 3;
          if (tok[i] === ",") i++;
        }
        i++;
      }
      const ichki = tok[i] === "{" ? tanlov() : null;
      maydonlar[nom] = ichki ? { args, tanlov: ichki } : true;
    }
    i++;
    return maydonlar;
  }
  return tanlov();
}

const VAZ = [
  { id: 1, matn: "Non olish", bajarildi: false },
  { id: 2, matn: "Hisobot", bajarildi: true },
  { id: 3, matn: "Sport", bajarildi: false },
];
const Query = {
  vazifa: ({ id }) => VAZ.find((v) => v.id === id) ?? null,
  vazifalar: ({ bajarildi }) =>
    VAZ.filter(
      (v) => bajarildi === undefined || v.bajarildi === bajarildi,
    ),
};

function kes(qiymat, tanlov) {
  if (qiymat === null) return null;
  if (Array.isArray(qiymat)) return qiymat.map((x) => kes(x, tanlov));
  const juftlar = Object.keys(tanlov).map((k) => [k, qiymat[k]]);
  return Object.fromEntries(juftlar);
}

function bajar(sorov) {
  const data = {};
  for (const [nom, m] of Object.entries(tahlil(sorov))) {
    data[nom] = kes(Query[nom](m.args), m.tanlov);
  }
  return { data };
}

const s =
  "{ vazifa(id: 2) { matn } vazifalar(bajarildi: false) { id } }";
console.log(JSON.stringify(bajar(s)));
console.log(JSON.stringify(bajar("{ vazifa(id: 9) { matn } }")));

Konsolda:

text
{"data":{"vazifa":{"matn":"Hisobot"},"vazifalar":[{"id":1},{"id":3}]}}
{"data":{"vazifa":null}}

Bitta so'rovda ikki "endpoint" — REST'da ikki HTTP so'rov bo'lardi. id: 9 uchun null — GraphQL'dagi "topilmadi". Bizning tahlilchi o'zgaruvchilar ($id), fragmentlar, izohlar va qochirilgan qo'shtirnoqlarni bilmaydi va xato so'rovda qotib qolishi mumkin. Haqiqiy loyihada so'rovni faqat graphql paketining parse va validate funksiyalari tahlil qiladi.

10. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi

Bu darsda vazifalar-api ga kod qo'shilmaydi. Kanon GraphQL Yoga'ni tajribada sinadi: sxema, resolver'lar, noma'lum maydon xatosi va NOT_FOUND mutation xatosi (HTTP 200), 20 vazifali so'rov mediana 2.26 ms (lokal, bitta o'lchov). vazifalar-api REST bo'lib qoladi: bitta frontend, oddiy CRUD — GraphQL'ning kuchli tomonlari bu yerda kerak emas. NestJS'da GraphQL moduli bor (30-qism) — u yerda code-first sxema bilan qayta uchrashamiz.

O'zingiz sinab ko'ring: alohida papkada npm i graphql-yoga@5.24.6 graphql@17.0.2, darsdagi sxema va resolver'lar bilan server ko'taring. graphiql: true qilib, brauzerda http://127.0.0.1:4000/graphql ni oching — avtoto'ldirish va hujjat panelida sxemani ko'ring.

11. Real ishda

  • GitHub API v4, Shopify, Facebook — ommaviy GraphQL API'lar; GitHub'da REST va GraphQL yonma-yon.
  • Headless CMS (Strapi, Contentful, Hygraph) — kontentni GraphQL orqali beradi (Tayyor backend'lar: BaaS va headless CMS).
  • Mobil ilovalar — bitta ekran uchun bitta so'rov; Apollo Client, urql, Relay klientlari.
  • Federation — bir nechta servis sxemalari bitta "supergraph" ga birlashadi (Apollo Federation, GraphQL Mesh).
  • Intervyuda: "GraphQL REST'dan nimasi bilan farq qiladi?", "Resolver nima?", "Nega GraphQL xatolari 200 bilan keladi?", "Over-fetching va under-fetching?"

Xulosa

  • GraphQL — bitta endpoint, tiplangan sxema (SDL), mijoz maydonlarni o'zi tanlaydi; javob so'rov shaklida.
  • Query — o'qish, Mutation — o'zgartirish, input — kirish obyekti; ! — non-null, sukut nullable.
  • Resolver (parent, args, context, info); yozilmagan maydon uchun sukut resolver parent[nom].
  • Qiymatlar — variables orqali, so'rov satriga emas.
  • Xatolar errors da; validatsiya xatosida data yo'q; application/json da 200, application/graphql-response+json da 400.
  • Ruxsat har maydon darajasida; introspection va og'ir so'rovlar — xavf.

Keyingi dars: GraphQL chuqur — N+1 va DataLoader, chuqurlik limiti, cursor sahifalash, subscriptions va persisted queries.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

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