8.17-bob: GraphQL — NestJS GraphQL
8-QISM — NestJS (chuqur) · 17-mavzu
1. Kirish va motivatsiya
Mikroservislarni 8.16-bob bildik. Endi REST'ga (5.7) muqobil API uslubini — GraphQL ni — o'rganamiz. Shu paytgacha barcha API'larimiz REST edi (endpoint'lar — GET /users, POST /orders). GraphQL — boshqa falsafa: bitta endpoint, va mijoz aniq kerakli ma'lumotni so'raydi (ortiqcha emas, kam emas). Bu — Facebook yaratgan, ko'p kompaniya (GitHub, Shopify) ishlatadigan kuchli yondashuv. NestJS GraphQL'ni birinchi darajada qo'llab-quvvatlaydi — @nestjs/graphql bilan TypeScript dekoratorlardan (code-first) avtomatik schema.
REST'ning ikki muammosi: over-fetching (kerak bo'lmagan maydonlar ham keladi — /users butun user obyektini beradi, sizga faqat ism kerak bo'lsa ham) va under-fetching (bir sahifa uchun ko'p endpoint — user + uning buyurtmalari + har buyurtma mahsulotlari = 3+ so'rov). GraphQL buni hal qiladi: mijoz bitta so'rovda aniq nima kerakligini aytadi (qaysi maydonlar, qaysi bog'liqliklar), server aynan shuni beradi. Frontend uchun juda qulay (11-QISM).
Bu bob: GraphQL nima va nega, REST vs GraphQL, code-first vs schema-first, resolver (@Query/@Mutation), @ObjectType/@Field, scalar va custom scalar, argumentlar (@Args/ArgsType), field resolver (bog'liqlik), subscriptions (real-time), N+1 muammosi (DataLoader — eng muhim), pagination (cursor/Relay), Apollo Sandbox, guards/interceptors/pipes GraphQL'da, xato boshqaruvi va federation (qisqacha). Bu bob 5.7 (REST), 8.13 (DB), 5.13 (real-time), 8.16 (mikroservis) bilan bog'liq. GraphQL — moslashuvchan, mijoz-boshqaruvchi API.
O'xshatish: REST — belgilangan menyu (har taom — alohida tovoq, alohida buyurtma; "kombo" olsang ham kerak bo'lmagan narsa keladi). GraphQL — buffet bilan aniq talab: bitta stolga (endpoint) borasiz va aynan nima xohlashingizni aytasiz ("faqat osh va non, salatsiz") — ortiqcha tovoq yo'q, kam emas, bir martada hammasi. Oshpaz (server) aniq so'rovingizni tayyorlaydi. Bu — mijozga kuch beradi (nima kerakligini o'zi belgilaydi), lekin oshpaz (server) buni to'g'ri uddalashi kerak (N+1 — 2.12).
Nega muhim?
- Aniq ma'lumot — over/under-fetching yo'q (mijoz so'raydi).
- Bitta so'rov — bog'liq ma'lumot birga (frontend qulay).
- Tipli schema — o'zini hujjatlaydi (introspection).
- NestJS code-first — dekoratordan avtomatik (tur xavfsiz).
2. Nazariya — chuqur tushuntirish
2.1. GraphQL nima (asoslar)
GraphQL — so'rov tili (query language) API uchun:
- BITTA endpoint (/graphql) — REST'da ko'p 5.7-bob
- Mijoz AYNAN kerakli ma'lumotni so'raydi
- 3 amal turi:
Query — o'qish (GET kabi)
Mutation — o'zgartirish (POST/PUT/DELETE kabi)
Subscription — real-time (WebSocket — 5.13)
So'rov: Javob (aynan so'ralgani):
query { {
user(id: 1) { "user": {
ism "ism": "Ali",
email "email": "ali@a.uz",
buyurtmalar { summa } "buyurtmalar": [{ "summa": 50000 }]
} }
} }GraphQL — API so'rov tili: bitta endpoint (
/graphql), mijoz aniq maydonlarni so'raydi. 3 amal: Query (o'qish), Mutation (o'zgartirish), Subscription (real-time — 5.13). Javob — so'rov shakliga mos (aynan so'ralgan maydonlar). Bu — REST'dan tubdan farq (mijoz boshqaradi).
2.2. REST vs GraphQL (taqqoslash)
REST 5.7-bob GraphQL
Ko'p endpoint Bitta endpoint (/graphql)
Server javob shaklini belgilar Mijoz so'raydi (aniq)
Over/under-fetching Aynan kerakli
HTTP status/metodlar Query/Mutation/Subscription
Oddiy, kesh oson (HTTP) Moslashuvchan, lekin kesh qiyin
Fayl, oddiy CRUD — qulay Murakkab bog'liq ma'lumot — qulay
GraphQL REST'ni ALMASHTIRMAYDI — har biri o'rni borREST vs GraphQL: REST — ko'p endpoint, server belgilaydi, oddiy/kesh oson; GraphQL — bitta endpoint, mijoz so'raydi, moslashuvchan/murakkab bog'liqlik uchun. GraphQL REST'dan "yaxshiroq" emas — boshqacha (har biri o'rni bor). Murakkab, bog'liq, mijoz-har-xil (mobil+veb) — GraphQL; oddiy CRUD, fayl, kesh muhim — REST.
2.3. Code-first vs Schema-first
CODE-FIRST (NestJS afzal):
TypeScript klass/dekorator schema AVTOMATIK generatsiya
Tur xavfsiz (7), DRY, refactor oson
@ObjectType, @Field, @Query (2.4, 2.5)
SCHEMA-FIRST:
GraphQL SDL (.graphql fayl) qo'lda TS tur generatsiya
Til-agnostik, frontend bilan kelishish osonCode-first vs Schema-first: code-first (NestJS afzal) — TypeScript dekoratorlardan schema avtomatik (tur xavfsiz — 7, DRY — DTO kabi 8.5). Schema-first — SDL (
.graphql) qo'lda yoziladi, TS tur generatsiya. Code-first — TS dunyosida tabiiy (kitobda shuni ishlatamiz). Ikkalasi ham NestJS'da.
Solishtirish uchun — bir xil User turi va query ikki uslubda:
// ── CODE-FIRST (kitobda shu — dekoratordan schema chiqadi) ──
@ObjectType()
export class User {
@Field(() => ID) id: number;
@Field() ism: string;
}
@Resolver(() => User)
export class UsersResolver {
@Query(() => User) user(@Args("id", { type: () => ID }) id: number) { /* ... */ }
}
// autoSchemaFile: "schema.gql" schema AVTOMATIK yoziladi (2.4)# ── SCHEMA-FIRST (SDL fayl qo'lda — user.graphql) ──
type User {
id: ID!
ism: String!
}
type Query {
user(id: ID!): User
}
# typePaths: ["**/*.graphql"] bu SDL'dan TS tur generatsiya
# Resolver'da @Resolver("User") + @Query("user") NOM bo'yicha bog'lanadiIkki uslub farqi (kod): code-first — TS klass manba (
.gqlchiqadi); schema-first — SDL manba (TS tur chiqadi —nest generateyokidefinitions). Schema-firstGraphQLModule.forRoot({ typePaths: ["**/*.graphql"] }). Aralash jamoada (frontend SDL'ni oldin belgilaydi) — schema-first; sof TS jamoa — code-first. Kitobda code-first (2.4+).
2.4. GraphQLModule sozlash (code-first)
import { GraphQLModule } from "@nestjs/graphql";
import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo";
@Module({
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: "schema.gql", // code-first: avtomatik schema (2.3)
sortSchema: true,
playground: true, // GraphQL Playground (interaktiv — dev)
}),
],
})
export class AppModule {}GraphQLModule:
driver(Apollo — eng keng),autoSchemaFile— code-first (dekoratorlardan schema avtomatik — 2.3),playground(interaktiv test UI — Swagger kabi — 8.8, faqat dev — 14). Bu — GraphQL'ning markaziy sozlamasi. Mercurius driver ham (Fastify).
2.5. ObjectType va Field (GraphQL tur)
import { ObjectType, Field, Int, ID } from "@nestjs/graphql";
@ObjectType() // GraphQL tur (entity)
export class User {
@Field(() => ID) // ID tur
id: number;
@Field() // String (avtomatik)
ism: string;
@Field()
email: string;
@Field(() => Int) // raqam (aniq tur)
yosh: number;
@Field({ nullable: true }) // ixtiyoriy
avatar?: string;
// parol — @Field YO'Q GraphQL'da ko'rinmaydi (14)
parol: string;
@Field(() => [Order]) // bog'liqlik (massiv)
buyurtmalar: Order[];
}@ObjectType/@Field — GraphQL turini belgilaydi (DTO 8.5 kabi).
@Field()— schema'da ko'rinadigan maydon (tur:() => Int,ID,[Order]).nullable(ixtiyoriy).@Fieldyo'q maydon GraphQL'da ko'rinmaydi (parol — 14 — avtomatik yashirin). Code-first: bu klassdan schema avtomatik. Tur xavfsiz (7).
2.5a. Scalar turlar va custom scalar (Date, JSON)
import { Field, Int, Float, ID, GraphQLISODateTime } from "@nestjs/graphql";
// ── O'RNATILGAN (built-in) scalar turlar ──
// String — matn (@Field() avtomatik)
// Int — butun son (@Field(() => Int))
// Float — kasr son (@Field(() => Float))
// Boolean — true/false (@Field() boolean uchun avtomatik)
// ID — unikal identifikator (@Field(() => ID) — String kabi seriyalanadi)
@ObjectType()
export class Hodisa {
@Field(() => ID) id: number;
@Field(() => GraphQLISODateTime) // NestJS'ning ISO Date scalar'i
vaqt: Date;
}// ── CUSTOM SCALAR (masalan: Date'ni o'zimiz boshqarish) ──
import { Scalar, CustomScalar } from "@nestjs/graphql";
import { Kind, ValueNode } from "graphql";
@Scalar("Date", () => Date) // schema'da "Date" nomli scalar
export class DateScalar implements CustomScalar<number, Date> {
description = "Date maxsus scalar turi (millisekund timestamp)";
parseValue(value: number): Date { // mijoz server (kirish)
return new Date(value);
}
serialize(value: Date): number { // server mijoz (chiqish)
return value.getTime();
}
parseLiteral(ast: ValueNode): Date { // inline literal (query ichida)
if (ast.kind === Kind.INT) return new Date(parseInt(ast.value, 10));
return null;
}
}
// provider sifatida ro'yxatdan o'tkaziladi: providers: [DateScalar]Scalar turlar — GraphQL'ning "atom" turlari: built-in (
String,Int,Float,Boolean,ID) va NestJS qo'shimchalari (GraphQLISODateTime,GraphQLTimestamp). Standart GraphQL'da Date yo'q — shuning uchunGraphQLISODateTimeyoki custom scalar (@Scalar+CustomScalar:serializechiqish,parseValue/parseLiteralkirish).JSON,Upload,EmailAddresskabilar uchungraphql-scalarskutubxonasi. Custom scalar — o'z formatingizni schema'ga qo'shish.
2.6. Resolver va Query (o'qish)
import { Resolver, Query, Args, Int } from "@nestjs/graphql";
@Resolver(() => User) // User uchun resolver (controller kabi)
export class UsersResolver {
constructor(private usersService: UsersService) {} // DI (8.2)
@Query(() => [User]) // ro'yxat query
users() {
return this.usersService.hammasi();
}
@Query(() => User, { nullable: true }) // bitta query
user(@Args("id", { type: () => Int }) id: number) {
return this.usersService.bitta(id);
}
}@Resolver/@Query — resolver = GraphQL "controller" 8.1-bob.
@Query(() => Tur)— o'qish amali (GET kabi).@Args— argument (id). Service'ni chaqiradi (DI — controller kabi yupqa — 8.1: 2.7). Mijozquery { users { ism } }deydi faqat ism keladi. Bu — GraphQL'ning yuragi.
2.7. Mutation (o'zgartirish)
import { Mutation, Args } from "@nestjs/graphql";
@Mutation(() => User)
yaratUser(@Args("input") input: CreateUserInput) { // input tur (2.8)
return this.usersService.yarat(input);
}
@Mutation(() => User)
yangilaUser(
@Args("id", { type: () => Int }) id: number,
@Args("input") input: UpdateUserInput,
) {
return this.usersService.yangila(id, input);
}
@Mutation(() => Boolean)
ochirUser(@Args("id", { type: () => Int }) id: number) {
return this.usersService.ochir(id);
}@Mutation — o'zgartirish amali (POST/PUT/DELETE kabi — 5.7). Qaytaradi (o'zgartirilgan obyekt — mijoz kerakli maydonni so'raydi).
@Args("input")— InputType 2.8-bob. Mijozmutation { yaratUser(input: {...}) { id ism } }. Query (o'qish) va Mutation (yozish) — GraphQL'da ajratilgan.
2.8. InputType va ArgsType (kirish — validatsiya)
import { InputType, Field } from "@nestjs/graphql";
import { IsEmail, MinLength } from "class-validator";
@InputType() // kirish turi (DTO — 8.5)
export class CreateUserInput {
@Field()
@MinLength(2) // validatsiya (8.5 — bir xil!)
ism: string;
@Field()
@IsEmail()
email: string;
@Field()
@MinLength(8)
parol: string;
}@InputType — mutatsiya kirish turi (DTO 8.5 GraphQL versiyasi). class-validator dekoratorlari bir xil ishlaydi (
@IsEmail,@MinLength— 8.5)! ValidationPipe (global — 8.5) GraphQL'da ham. Bir manba (tur + validatsiya — DRY). Bu — NestJS'ning GraphQL+validatsiya integratsiyasi.
2.9. Field Resolver (bog'liqlik — lazy)
import { ResolveField, Parent } from "@nestjs/graphql";
@Resolver(() => User)
export class UsersResolver {
@Query(() => [User])
users() { return this.usersService.hammasi(); }
// Field resolver — user.buyurtmalar FAQAT so'ralganda yuklanadi (lazy)
@ResolveField(() => [Order])
buyurtmalar(@Parent() user: User) { // ota obyekt
return this.ordersService.userBuyurtmalari(user.id);
}
}
// query { users { ism } } buyurtmalar YUKLANMAYDI
// query { users { ism buyurtmalar }} buyurtmalar yuklanadi (kerak bo'lsa)@ResolveField — bog'liq maydonni faqat so'ralganda yuklash (lazy — over-fetching yo'q).
@Parent()— ota obyekt (user). Mijozbuyurtmalarso'ramasa — yuklanmaydi (tejamkor). Lekin bu N+1 muammosini keltiradi (2.12 — har user uchun alohida so'rov) — DataLoader bilan hal qilinadi 2.13-bob.
2.10. Subscriptions (real-time — 5.13)
import { Subscription } from "@nestjs/graphql";
import { PubSub } from "graphql-subscriptions";
const pubSub = new PubSub();
@Resolver()
export class NotificationsResolver {
@Subscription(() => Notification)
yangiBildirishnoma() {
return pubSub.asyncIterator("yangiBildirishnoma"); // kanal
}
@Mutation(() => Notification)
async yubor(@Args("matn") matn: string) {
const xabar = { matn, vaqt: new Date() };
await pubSub.publish("yangiBildirishnoma", { yangiBildirishnoma: xabar }); // e'lon
return xabar;
}
}@Subscription — real-time (server mijoz push — WebSocket — 5.13).
PubSub(publish/subscribe — 5.13: 2.10). Mijozsubscription { yangiBildirishnoma { matn } }— kanal ochiladi, harpublishda xabar keladi. Chat, bildirishnoma, jonli yangilanish. Resurs talab (ochiq ulanish — ehtiyot). Production'da Redis PubSub (5.13, 8.15).
2.11. Guards/Pipes GraphQL'da (8.5, 8.6)
import { UseGuards } from "@nestjs/common";
import { GqlExecutionContext } from "@nestjs/graphql";
// GraphQL uchun guard (context boshqacha)
@Injectable()
export class GqlAuthGuard extends AuthGuard("jwt") {
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context); // GraphQL context
return ctx.getContext().req;
}
}
@Query(() => User)
@UseGuards(GqlAuthGuard) // himoyalangan (8.9)
me(@CurrentUser() user) {
return user;
}Guards/Pipes GraphQL'da (8.5, 8.6) — ishlaydi, lekin context boshqacha (HTTP emas —
GqlExecutionContext). GuardgetRequestoverride. ValidationPipe 8.5-bob, auth guard 8.9-bob — moslashtirilgan. Enhancer'lar (guard/interceptor) field resolver'da ishlamaydi (faqat top-level Query/Mutation — performance). NestJS imkoniyatlari GraphQL'da ham (moslashtirilgan).
2.12. N+1 muammosi (eng muhim performance)
N+1 MUAMMOSI (field resolver — 2.9):
query { users { ism buyurtmalar { summa } } }
1 so'rov: SELECT * FROM users (N ta user)
N so'rov: har user uchun SELECT * FROM orders WHERE user=?
1 + N so'rov (100 user 101 so'rov!) — DB ni o'ldiradi
Yechim: DataLoader 2.13-bob — barcha buyurtmani BITTA so'rovda (batch)N+1 muammosi (GraphQL'ning eng mashhur muammosi): field resolver 2.9-bob — N ta ota uchun N ta qo'shimcha so'rov (1+N). 100 user 101 DB so'rov (juda sekin!). REST'da kamroq (server bitta JOIN — 6.7), lekin GraphQL'da har field alohida. DataLoader 2.13-bob — buni hal qiladi (batch + cache). Bu — GraphQL'da MUTLAQ bilish kerak.
2.13. DataLoader (N+1 yechimi — batch)
import * as DataLoader from "dataloader";
// DataLoader — ko'p .load() ni BITTA batch so'rovga (2.12 yechimi)
@Injectable()
export class OrdersLoader {
createLoader() {
return new DataLoader<number, Order[]>(async (userIds: number[]) => {
// BITTA so'rov — barcha user uchun (batch!)
const orders = await this.ordersService.userlarBuyurtmalari(userIds);
// Har user'ga moslash
return userIds.map((id) => orders.filter((o) => o.userId === id));
});
}
}
// Field resolver — DataLoader bilan (N+1 yo'q)
@ResolveField(() => [Order])
buyurtmalar(@Parent() user: User, @Context() ctx) {
return ctx.loaders.ordersLoader.load(user.id); // batch (1 so'rov)
}DataLoader (N+1 yechimi — docs): ko'p
.load(id)chaqiruvini bitta batch so'rovga to'playdi (1+N 2 so'rov). Har so'rovga yangi instance (request-scoped — kesh izolyatsiya — best practice). GraphQL context'da (ctx.loaders). Batch + cache. Bu — GraphQL performance'ning kaliti (N+1'siz).
2.13a. Pagination (Relay / cursor-based)
ICKI USUL:
1) OFFSET (sahifa/limit) — oddiy, lekin katta ma'lumotda sekin (2.12: filter kabi)
users(sahifa: 2, limit: 20)
2) CURSOR (Relay) — GraphQL "rasmiy" standarti (barqaror, cheksiz skroll)
users(first: 20, after: "cursor") edges + pageInfoimport { ObjectType, Field, Int, ArgsType } from "@nestjs/graphql";
// ── Relay-uslub connection (cursor) turlari ──
@ObjectType()
export class PageInfo {
@Field() hasNextPage: boolean;
@Field() hasPreviousPage: boolean;
@Field({ nullable: true }) startCursor?: string;
@Field({ nullable: true }) endCursor?: string;
}
@ObjectType()
export class UserEdge {
@Field() cursor: string; // base64(id) — barqaror ko'rsatkich
@Field(() => User) node: User; // haqiqiy element
}
@ObjectType()
export class UserConnection {
@Field(() => [UserEdge]) edges: UserEdge[];
@Field(() => PageInfo) pageInfo: PageInfo;
@Field(() => Int) totalCount: number;
}
@ArgsType()
export class PaginationArgs {
@Field(() => Int, { defaultValue: 20 }) first: number; // nechta
@Field({ nullable: true }) after?: string; // qaysi cursor'dan keyin
}
@Query(() => UserConnection)
usersConnection(@Args() { first, after }: PaginationArgs) {
return this.usersService.sahifala(first, after); // edges + pageInfo qaytaradi
}Pagination: ikki yo'l — offset (
sahifa/limit— oddiy, lekin element qo'shilsa siljish bo'ladi) va cursor/Relay (first/afteredges+pageInfo— barqaror, cheksiz skroll uchun ideal — 11). Relay standarti:cursorodatdabase64(id),pageInfo.hasNextPage— davomi bormi. Katta ro'yxat, real-time qo'shiluvchi ma'lumot (feed, chat) cursor afzal (offset dublikat/o'tkazib yuborishga olib keladi). Apollo Client cursor pagination'ni tabiiy qo'llaydi (11).
2.13b. Apollo Sandbox va Playground (interaktiv IDE)
import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo";
import { ApolloServerPluginLandingPageLocalDefault } from "@apollo/server/plugin/landingPage/default";
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: "schema.gql",
playground: false, // eski Playground O'CHIQ
plugins: [ApolloServerPluginLandingPageLocalDefault()], // yangi Apollo Sandbox
});
// /graphql ochilganda Apollo Sandbox UI (introspection, query yozish, tarix)Apollo Sandbox — Apollo Server 4+ (
@nestjs/apolloyangi) dagi zamonaviy interaktiv IDE (eskiplayground: true— GraphQL Playground endi eskirgan/deprecated).ApolloServerPluginLandingPageLocalDefaultplagini bilan yoqiladi./graphqlga brauzerdan kirilganda ochiladi: schema ko'rish (introspection), query/mutation yozish va yuborish, o'zgaruvchilar, tarix. Swagger'ning 8.8-bob GraphQL ekvivalenti. Production'da o'chirish (schema oshkor bo'ladi — 14):plugins: [ApolloServerPluginLandingPageDisabled()].
2.13c. Federation (mikroservis GraphQL — qisqacha)
FEDERATION — ko'p GraphQL servisni BITTA schema'ga birlashtirish 8.16-bob:
┌─ users-service (User turi — "egasi")
├─ orders-service (Order turi + User'ga @extends bilan maydon qo'shadi)
└─ Apollo Gateway hammasini bitta /graphql qilib ko'rsatadi (mijoz uchun yagona)// ── users-service (subgraph) ──
import { ObjectType, Field, ID, Directive } from "@nestjs/graphql";
@ObjectType()
@Directive('@key(fields: "id")') // federation: bu tur kaliti
export class User {
@Field(() => ID) id: number;
@Field() ism: string;
}
// module: GraphQLModule.forRoot({ driver: ApolloFederationDriver, autoSchemaFile: { federation: 2 } })
// ── orders-service (User'ni KENGAYTIRADI — @extends) ──
@Resolver(() => User)
export class UsersResolver {
@ResolveReference() // Gateway boshqa servisdan User so'raganda
resolveReference(ref: { __typename: string; id: number }) {
return { id: ref.id };
}
@ResolveField(() => [Order]) // User'ga "buyurtmalar" maydonini qo'shadi
buyurtmalar(@Parent() user: User) {
return this.ordersService.userBuyurtmalari(user.id);
}
}Federation (qisqacha) — bir nechta GraphQL subgraph (har mikroservis — 8.16) ni Apollo Gateway yordamida bitta birlashgan schema'ga yig'ish. Har servis o'z turini "egalik qiladi" (
@key), boshqasi uni@extends/@ResolveReferencebilan kengaytiradi (masalanorders-serviceUsergabuyurtmalarqo'shadi). NestJS:ApolloFederationDriver(subgraph) vaApolloGatewayDriver(gateway). Bu — katta, ko'p-jamoali tizim uchun (kichik loyihada ortiqcha murakkablik). Monolitda oddiy modul kifoya. Chuqurroq — 8.16 (mikroservislar).
2.14. Best practices (GraphQL)
Code-first (tur xavfsiz — 2.3, 2.4)
@ObjectType/@Field; parol @Field'siz (yashirin — 14, 2.5)
Resolver yupqa service (8.1: 2.7, 2.6)
InputType + class-validator (DRY — 8.5, 2.8)
Field resolver (lazy bog'liqlik — 2.9) + DataLoader (N+1 — 2.12, 2.13)
Subscription ehtiyotkorlik (resurs — Redis PubSub prod — 2.10)
Guard GqlExecutionContext (auth — 2.11)
Cursor pagination katta ro'yxatga (Relay — 2.13a)
Custom scalar Date/JSON uchun (GraphQLISODateTime — 2.5a)
Apollo Sandbox faqat dev (14, 2.13b); query depth/complexity limit (DoS)
Federation faqat ko'p-servisli katta tizimga (2.13c, 8.16)3. Sintaksis — tez ma'lumotnoma
// Module 2.4-bob: GraphQLModule.forRoot({ driver: ApolloDriver, autoSchemaFile: "schema.gql" })
// Tur 2.5-bob: @ObjectType() class User { @Field(() => ID) id: number; }
// Input 2.8-bob: @InputType() class CreateUserInput { @Field() @IsEmail() email: string; }
// Resolver (2.6, 2.7, 2.9)
@Resolver(() => User)
@Query(() => [User]) users() {}
@Mutation(() => User) yarat(@Args("input") input) {}
@ResolveField(() => [Order]) buyurtmalar(@Parent() user) {}
// Subscription 2.10-bob: @Subscription(() => X) pubSub.asyncIterator("kanal")
// DataLoader 2.13-bob: loader.load(id) // batch
// Scalar (2.5a): @Field(() => Float/GraphQLISODateTime); custom: @Scalar("Date") implements CustomScalar
// Pagination (2.13a): @Query(() => UserConnection) usersConnection(@Args() { first, after })
// Sandbox (2.13b): plugins: [ApolloServerPluginLandingPageLocalDefault()]
// Federation (2.13c): @Directive('@key(fields: "id")'); ApolloFederationDriver
// Enum (Misol 8): registerEnumType(OrderStatus, { name: "OrderStatus" })4. Batafsil kod namunalari
Misol 1 — GraphQLModule + tur (2.4, 2.5)
// app.module.ts
@Module({
imports: [
GraphQLModule.forRootAsync<ApolloDriverConfig>({
driver: ApolloDriver,
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
autoSchemaFile: "schema.gql",
sortSchema: true,
playground: config.get("app.env") !== "production", // faqat dev (14)
context: ({ req }) => ({ req }), // auth uchun (2.11)
}),
}),
],
})
export class AppModule {}
// users/entities/user.entity.ts
@ObjectType()
export class User {
@Field(() => ID) id: number;
@Field() ism: string;
@Field() email: string;
@Field(() => Int, { nullable: true }) yosh?: number;
@Field() faol: boolean;
// parol — @Field yo'q (yashirin — 14)
parol: string;
@Field(() => [Order], { nullable: true }) buyurtmalar?: Order[];
}Misol 2 — Resolver (Query + Mutation — 2.6, 2.7)
@Resolver(() => User)
export class UsersResolver {
constructor(private usersService: UsersService) {}
@Query(() => [User], { name: "users" })
hammasi() {
return this.usersService.hammasi();
}
@Query(() => User, { name: "user", nullable: true })
bitta(@Args("id", { type: () => Int }) id: number) {
return this.usersService.bitta(id);
}
@Mutation(() => User)
yaratUser(@Args("input") input: CreateUserInput) {
return this.usersService.yarat(input);
}
@Mutation(() => User)
yangilaUser(
@Args("id", { type: () => Int }) id: number,
@Args("input") input: UpdateUserInput,
) {
return this.usersService.yangila(id, input);
}
@Mutation(() => Boolean)
ochirUser(@Args("id", { type: () => Int }) id: number) {
return this.usersService.ochir(id).then(() => true);
}
}Misol 3 — InputType + validatsiya (2.8)
@InputType()
export class CreateUserInput {
@Field()
@IsString() @MinLength(2)
ism: string;
@Field()
@IsEmail()
email: string;
@Field()
@MinLength(8) @Matches(/[A-Z]/, { message: "Katta harf kerak" })
parol: string;
@Field(() => Int, { nullable: true })
@IsOptional() @IsInt() @Min(18)
yosh?: number;
}
// PartialType (yangilash — 8.5)
import { PartialType } from "@nestjs/graphql";
@InputType()
export class UpdateUserInput extends PartialType(CreateUserInput) {}
// main.ts — ValidationPipe (GraphQL'da ham ishlaydi — 8.5)
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));Misol 4 — Field resolver (bog'liqlik — 2.9)
@Resolver(() => User)
export class UsersResolver {
constructor(
private usersService: UsersService,
private ordersService: OrdersService,
) {}
@Query(() => [User])
users() { return this.usersService.hammasi(); }
// buyurtmalar faqat so'ralganda (lazy — 2.9)
@ResolveField(() => [Order])
buyurtmalar(@Parent() user: User) {
return this.ordersService.userBuyurtmalari(user.id);
}
// Hisoblangan maydon (DB'da yo'q)
@ResolveField(() => Int)
buyurtmalarSoni(@Parent() user: User) {
return this.ordersService.soni(user.id);
}
}
// query { users { ism buyurtmalar { summa } buyurtmalarSoni } }Misol 5 — DataLoader (N+1 yechimi — 2.13)
// loaders/orders.loader.ts
import DataLoader from "dataloader";
@Injectable({ scope: Scope.REQUEST }) // har so'rovga yangi (2.13)
export class OrdersDataLoader {
constructor(private ordersService: OrdersService) {}
public readonly batchOrders = new DataLoader<number, Order[]>(
async (userIds: readonly number[]) => {
// BITTA so'rov — barcha userId uchun (batch — 2.12 yechimi)
const orders = await this.ordersService.userlarBuyurtmalari([...userIds]);
// userId tartibida moslash (DataLoader talab)
const map = new Map<number, Order[]>();
orders.forEach((o) => {
if (!map.has(o.userId)) map.set(o.userId, []);
map.get(o.userId).push(o);
});
return userIds.map((id) => map.get(id) || []);
},
);
}
// Resolver — DataLoader bilan
@ResolveField(() => [Order])
buyurtmalar(@Parent() user: User, @Context() { loaders }) {
return loaders.ordersLoader.batchOrders.load(user.id); // 1 so'rov (N+1 yo'q!)
}Misol 6 — Subscription (real-time — 2.10)
import { PubSub } from "graphql-subscriptions";
const pubSub = new PubSub(); // prod: Redis PubSub (8.15)
@Resolver(() => Message)
export class ChatResolver {
@Query(() => [Message])
xabarlar() { return this.chatService.hammasi(); }
@Mutation(() => Message)
async xabarYubor(@Args("input") input: SendMessageInput) {
const xabar = await this.chatService.saqla(input);
await pubSub.publish("xabarQoshildi", { xabarQoshildi: xabar }); // e'lon (5.13)
return xabar;
}
@Subscription(() => Message, {
filter: (payload, variables) => // faqat shu xona (5.13)
payload.xabarQoshildi.xonaId === variables.xonaId,
})
xabarQoshildi(@Args("xonaId") xonaId: number) {
return pubSub.asyncIterator("xabarQoshildi");
}
}
// subscription { xabarQoshildi(xonaId: 1) { matn } }Misol 7 — Guard va auth (2.11)
// gql-auth.guard.ts
@Injectable()
export class GqlAuthGuard extends AuthGuard("jwt") {
getRequest(context: ExecutionContext) {
return GqlExecutionContext.create(context).getContext().req; // GraphQL req
}
}
// CurrentUser decorator (GraphQL)
export const CurrentUser = createParamDecorator(
(data: unknown, context: ExecutionContext) =>
GqlExecutionContext.create(context).getContext().req.user,
);
// Resolver
@Query(() => User)
@UseGuards(GqlAuthGuard)
me(@CurrentUser() user: User) {
return this.usersService.bitta(user.id);
}
@Mutation(() => Order)
@UseGuards(GqlAuthGuard)
buyurtmaBer(@Args("input") input: CreateOrderInput, @CurrentUser() user: User) {
return this.ordersService.yarat(user.id, input);
}Misol 8 — Enum va interface (GraphQL turlar)
import { registerEnumType } from "@nestjs/graphql";
// Enum (8.5, 7)
export enum OrderStatus {
YANGI = "yangi",
TAYYORLANMOQDA = "tayyorlanmoqda",
YETKAZILDI = "yetkazildi",
}
registerEnumType(OrderStatus, { name: "OrderStatus" }); // GraphQL'ga ro'yxat
@ObjectType()
export class Order {
@Field(() => ID) id: number;
@Field(() => OrderStatus) holat: OrderStatus; // enum maydon
@Field(() => Float) summa: number;
}
// Filter argument (ArgsType)
@ArgsType()
export class OrdersFilter {
@Field(() => OrderStatus, { nullable: true }) holat?: OrderStatus;
@Field(() => Int, { defaultValue: 1 }) sahifa: number;
@Field(() => Int, { defaultValue: 20 }) limit: number;
}Misol 9 — Error handling GraphQL (2.11)
// GraphQL'da xato — built-in exception ishlaydi (8.1)
@Query(() => User)
async user(@Args("id", { type: () => Int }) id: number) {
const user = await this.usersService.bitta(id);
if (!user) throw new NotFoundException("Foydalanuvchi topilmadi"); // GraphQL error
return user;
}
// Custom GraphQL error formatlash (module)
GraphQLModule.forRoot({
driver: ApolloDriver,
formatError: (error) => ({ // toza xato (14 — ichki yashirin)
message: error.message,
code: error.extensions?.code,
}),
});Misol 10 — Custom scalar (Date — 2.5a)
// scalars/date.scalar.ts
import { Scalar, CustomScalar } from "@nestjs/graphql";
import { Kind, ValueNode } from "graphql";
@Scalar("Date", () => Date)
export class DateScalar implements CustomScalar<number, Date> {
description = "Date scalar (millisekund timestamp)";
serialize(value: Date): number { // server mijoz
return value instanceof Date ? value.getTime() : null;
}
parseValue(value: number): Date { // mijoz o'zgaruvchisi server
return new Date(value);
}
parseLiteral(ast: ValueNode): Date { // query ichidagi literal server
return ast.kind === Kind.INT ? new Date(parseInt(ast.value, 10)) : null;
}
}
// app.module.ts — provider sifatida ro'yxat
@Module({ providers: [DateScalar] })
export class AppModule {}
// Ishlatish — endi @Field(() => Date) millisekund timestamp bo'lib seriyalanadi
@ObjectType()
export class Buyurtma {
@Field(() => ID) id: number;
@Field(() => Date) yaratilgan: Date; // custom scalar orqali
}
// Eslatma: oddiy holatda NestJS'ning tayyor GraphQLISODateTime yetarli (2.5a)Misol 11 — Cursor pagination (Relay — 2.13a)
// pagination/paginated.ts — generic connection (har tur uchun)
import { Type } from "@nestjs/common";
import { ObjectType, Field, Int } from "@nestjs/graphql";
@ObjectType()
export class PageInfo {
@Field() hasNextPage: boolean;
@Field({ nullable: true }) endCursor?: string;
}
// Generic factory — Paginated(User) => UserConnection
export function Paginated<T>(TClass: Type<T>): any {
@ObjectType({ isAbstract: true })
abstract class EdgeType {
@Field() cursor: string;
@Field(() => TClass) node: T;
}
@ObjectType({ isAbstract: true })
abstract class PaginatedType {
@Field(() => [EdgeType]) edges: EdgeType[];
@Field(() => PageInfo) pageInfo: PageInfo;
@Field(() => Int) totalCount: number;
}
return PaginatedType;
}
@ObjectType()
export class UserConnection extends Paginated(User) {}
// service — cursor mantiq (base64(id))
async sahifala(first: number, after?: string) {
const afterId = after ? Number(Buffer.from(after, "base64").toString()) : 0;
const rows = await this.repo.find({
where: { id: MoreThan(afterId) },
take: first + 1, // 1 ta ortiq keyingi bor-yo'qni bilish
order: { id: "ASC" },
});
const hasNextPage = rows.length > first;
const items = hasNextPage ? rows.slice(0, first) : rows;
return {
edges: items.map((u) => ({
cursor: Buffer.from(String(u.id)).toString("base64"),
node: u,
})),
pageInfo: {
hasNextPage,
endCursor: items.length
? Buffer.from(String(items[items.length - 1].id)).toString("base64")
: null,
},
totalCount: await this.repo.count(),
};
}
// resolver
@Query(() => UserConnection)
usersConnection(@Args() { first, after }: PaginationArgs) {
return this.usersService.sahifala(first, after);
}
// query { usersConnection(first: 20) { edges { node { ism } cursor } pageInfo { hasNextPage endCursor } } }Misol 12 — To'liq tuzilma (2.14)
GraphQL modul tuzilishi:
src/
├── app.module.ts (GraphQLModule + Sandbox — Misol 1, 2.13b)
├── scalars/date.scalar.ts (custom scalar — Misol 10, 2.5a)
├── pagination/paginated.ts(generic connection — Misol 11, 2.13a)
├── users/
│ ├── entities/user.entity.ts (@ObjectType — Misol 1)
│ ├── dto/create-user.input.ts (@InputType — Misol 3)
│ ├── users.resolver.ts (@Resolver — Misol 2, 4)
│ ├── users.service.ts (biznes mantiq — 8.1)
│ └── loaders/orders.loader.ts (DataLoader — Misol 5)
└── schema.gql (avtomatik generatsiya — code-first)
Resolver (GraphQL "controller") + service (mantiq) + entity (@ObjectType) + input (@InputType)
REST controller'siz (yoki birga — GraphQL + REST hybrid)5. To'g'ri va noto'g'ri holatlar
1) Field resolver DataLoader'siz
N+1 (100 user 101 so'rov — 2.12)
DataLoader (batch — 2.13)2) parol @Field bilan
parol GraphQL'da ko'rinadi (14)
@Field yo'q (avtomatik yashirin — 2.5)3) Mantiq resolver'da
DB/biznes resolver'da (8.1: 2.7)
service'da (resolver yupqa)4) Playground production'da
playground: true (prod — schema oshkor — 14)
faqat dev (env tekshir — 2.4)5) Validatsiyasiz input
InputType validatsiyasiz (8.5)
class-validator + ValidationPipe (DRY)6. Keng tarqalgan xatolar va yechimlari
Xato 1 — Schema generatsiya bo'lmaydi
Sababi: @ObjectType/@Field yo'q, yoki autoSchemaFile (2.4, 2.5). Yechimi: dekoratorlar; autoSchemaFile yo'l.
Xato 2 — Cannot determine GraphQL type
Sababi: @Field tur aniq emas 2.5-bob. Yechimi: @Field(() => Int/[Order]) aniq tur.
Xato 3 — N+1 (sekin)
Sababi: field resolver DataLoader'siz 2.12-bob. Yechimi: DataLoader 2.13-bob.
Xato 4 — Guard ishlamaydi
Sababi: context HTTP (GraphQL boshqacha — 2.11). Yechimi: GqlExecutionContext; getRequest override.
Xato 5 — Validatsiya ishlamaydi
Sababi: ValidationPipe yo'q 2.8-bob. Yechimi: global ValidationPipe; @InputType.
Xato 6 — Subscription ulanmaydi
Sababi: subscription transport sozlanmagan. Yechimi: installSubscriptionHandlers / graphql-ws.
Xato 7 — Date cannot represent... (scalar)
Sababi: GraphQL'da standart Date yo'q (2.5a). Yechimi: @Field(() => GraphQLISODateTime) yoki custom scalar (Misol 10).
Xato 8 — Playground eskirgan/ochilmaydi
Sababi: Apollo Server 4+ eski Playground'ni olib tashladi (2.13b). Yechimi: ApolloServerPluginLandingPageLocalDefault (Apollo Sandbox).
7. Integratsiya — bu mavzu stack'ning qayerida uchraydi
- REST 5.7-bob: muqobil API uslubi.
- DTO/validatsiya 8.5-bob: InputType + class-validator.
- DB (8.13, 8.3): resolver service DB.
- Real-time 5.13-bob: subscriptions.
- Auth 8.9-bob: GqlAuthGuard.
- N+1 6.10-bob: DataLoader.
- Frontend (11): Apollo Client (cursor pagination — 2.13a).
- Cache 8.15-bob: GraphQL cache (qiyin).
- Mikroservis 8.16-bob: Federation (subgraph + Apollo Gateway — 2.13c).
8. Eng yaxshi amaliyotlar (best practices)
- Code-first (tur xavfsiz — 2.3, 2.4).
- @ObjectType/@Field; parol @Field'siz (yashirin — 14, 2.5).
- Resolver yupqa service (8.1: 2.7).
- InputType + class-validator (DRY — 8.5, 2.8).
- Field resolver (lazy — 2.9) + DataLoader (N+1 — 2.12, 2.13).
- Subscription ehtiyotkorlik (resurs — Redis PubSub prod — 2.10).
- Guard GqlExecutionContext (auth — 2.11).
- Cursor pagination (katta ro'yxatga — Relay — 2.13a, Misol 11).
- Custom scalar (Date/JSON — GraphQLISODateTime — 2.5a, Misol 10).
- Apollo Sandbox faqat dev (14, 2.13b); query depth/complexity limit (DoS).
- Federation (faqat ko'p-servisli katta tizim — 2.13c, 8.16).
- formatError (ichki xato yashirin — 14, Misol 9).
- REST bilan tanlov (har biri o'rni — 2.2).
9. Amaliy loyiha: "GraphQL API (NestJS)"
NestJS GraphQL'ni mustahkamlash.
Maqsad
NestJS'da to'liq GraphQL API: User/Order/Product — query, mutation, field resolver, DataLoader, subscription, auth.
Talablar (requirements)
- GraphQLModule: code-first, playground dev (Misol 1, 2.4).
- ObjectType: User/Order/Product (@Field, parol yashirin — Misol 1, 2.5).
- Resolver: Query (ro'yxat/bitta), Mutation (CRUD) — Misol 2, 2.6, 2.7.
- InputType: validatsiya (class-validator — Misol 3, 2.8).
- Field resolver: user.buyurtmalar, hisoblangan (Misol 4, 2.9).
- DataLoader: N+1 yechimi (Misol 5, 2.13).
- Subscription: real-time chat/bildirishnoma (Misol 6, 2.10).
- Auth: GqlAuthGuard, CurrentUser (Misol 7, 2.11).
- Enum + filter: OrderStatus, ArgsType (Misol 8).
- Error: NotFound, formatError (Misol 9, 14).
- Pagination: cursor/Relay
usersConnection(Misol 11, 2.13a). - Custom scalar: Date (yoki GraphQLISODateTime — Misol 10, 2.5a).
- Sandbox: Apollo Sandbox dev'da yoqilgan (2.13b).
Maslahatlar (hint)
- @Field tur aniq (2.5, 2-xato).
- DataLoader N+1 (2.13, 3-xato).
- parol @Field'siz (14, 2-holat).
- Guard GqlExecutionContext (2.11, 4-xato).
- ValidationPipe + InputType (2.8, 5-xato).
- Playground dev (14, 4-holat).
"Tayyor" mezonlari (acceptance criteria)
- GraphQLModule (code-first).
- ObjectType (parol yashirin).
- Query + Mutation (CRUD).
- InputType (validatsiya).
- Field resolver.
- DataLoader (N+1 yo'q).
- Subscription.
- Auth (GqlAuthGuard).
- Enum + filter.
- Error handling.
- Cursor pagination (Relay connection).
- Custom scalar (Date) yoki GraphQLISODateTime.
- Apollo Sandbox (dev).
Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.
10. Xulosa va keyingi bobga ko'prik
Bu bobda NestJS GraphQL'ni chuqur o'rgandik:
- GraphQL (bitta endpoint, mijoz so'raydi — 2.1); REST vs GraphQL 2.2-bob; code-first vs schema-first (2.3, 2.4).
- @ObjectType/@Field (tur — 2.5); scalar + custom scalar (2.5a); resolver (@Query/@Mutation — 2.6, 2.7); InputType/ArgsType (validatsiya — 2.8).
- Field resolver (lazy bog'liqlik — 2.9); subscriptions (real-time — 2.10); guards/pipes (GqlExecutionContext — 2.11).
- N+1 muammosi 2.12-bob + DataLoader (yechimi — 2.13); pagination (cursor/Relay — 2.13a); Apollo Sandbox (2.13b); federation (2.13c); best practices 2.14-bob.
Keyingi bob — 8.18-bob: WebSockets gateway va Task scheduling (cron). GraphQL'ni bildik; endi 8-QISM'ning so'nggi ikki amaliy mavzusini — NestJS'da WebSockets (gateway — 5.13 — real-time) va task scheduling (cron — 5.22 — rejalashtirilgan vazifalar) — ko'ramiz. Bu — chat, bildirishnoma (WebSocket) va davriy vazifalar (cron — hisobot, tozalash).
Foydalanilgan rasmiy/ishonchli manbalar
- docs.nestjs.com/graphql/quick-start; /resolvers (code-first, @Resolver, @Query, @ObjectType)
- docs.nestjs.com/graphql/subscriptions (PubSub, @Subscription)
- docs.nestjs.com/graphql/dataloader; github.com/graphql/dataloader (N+1, batch, request-scoped)
- docs.nestjs.com/graphql/other-features (guards, interceptors, GqlExecutionContext)
- graphql.org/learn (Query/Mutation/Subscription, schema, scalar turlar)
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!