GraphQL17 min read

Resolverlar

Resolverlar GraphQL operatsiyasini (query, mutation yoki subscription) ma'lumotga aylantirish bo'yicha ko'rsatmalarni beradi. Ular schemamizda ko'rsatgan shakldagi ma'lumotni - sin

Resolverlar GraphQL operatsiyasini (query, mutation yoki subscription) ma'lumotga aylantirish bo'yicha ko'rsatmalarni beradi. Ular schemamizda ko'rsatgan shakldagi ma'lumotni - sinxron yoki shu shakldagi natijaga yechiladigan promise ko'rinishida - qaytaradi. Odatda siz resolver map ni qo'lda yaratasiz. @nestjs/graphql paketi esa klasslarni annotatsiya qilish uchun ishlatilgan dekoratorlar taqdim etgan metadata asosida resolver map ni avtomatik generatsiya qiladi. Paket imkoniyatlaridan foydalanib GraphQL API yaratish jarayonini ko'rsatish uchun oddiy authors API ni yaratamiz.

Code first

Code first yondashuvida GraphQL schemani qo'lda GraphQL SDL yozish orqali yaratmaymiz. Buning o'rniga, TypeScript dekoratorlaridan foydalanib SDLni TypeScript klass ta'riflaridan generatsiya qilamiz. @nestjs/graphql paketi dekoratorlar orqali berilgan metadatani o'qiydi va schemani avtomatik yaratadi.

Object types

GraphQL schemadagi ko'p ta'riflar object types dan iborat. Har bir object type ilova klienti ishlashi mumkin bo'lgan domen obyektini ifodalashi kerak. Masalan, namunaviy API da authorlar va ularning postlari ro'yxatini olish imkoniyatini berishimiz kerak, shuning uchun bu funksionallikni qo'llab-quvvatlash uchun Author va Post turlarini aniqlashimiz kerak.

Agar schema first yondashuvdan foydalansak, SDLda schemani quyidagicha belgilardik:

Graphql
1type Author {
2  id: Int!
3  firstName: String
4  lastName: String
5  posts: [Post!]!
6}

Bu holatda, code first yondashuvda schemalarni TypeScript klasslari va ularning fieldlarini annotatsiya qiladigan dekoratorlar bilan belgilaymiz. Yuqoridagi SDLning code firstdagi ekvivalenti:

TypeScript
authors/models/author.model
1import { Field, Int, ObjectType } from '@nestjs/graphql';
2import { Post } from './post';
3
4@ObjectType()
5export class Author {
6  @Field(type => Int)
7  id: number;
8
9  @Field({ nullable: true })
10  firstName?: string;
11
12  @Field({ nullable: true })
13  lastName?: string;
14
15  @Field(type => [Post])
16  posts: Post[];
17}
Hint

TypeScriptning metadata reflection tizimida bir nechta cheklovlar mavjud: masalan, klass qaysi xossalardan iboratligini aniqlash yoki berilgan xossa ixtiyoriy yoki majburiyligini bilish imkonsiz. Shu sababli, schemadagi har bir fieldning GraphQL tipi va optionalitysi haqida metadata berish uchun @Field() dekoratoridan ochiq foydalanishimiz yoki bularni generatsiya qilish uchun CLI plugindan foydalanishimiz kerak.

Author object type ham har qanday klass kabi fieldlar to'plamidan iborat bo'lib, har bir field o'z turini e'lon qiladi. Field turi GraphQL typega mos keladi. Fieldning GraphQL turi boshqa object type yoki scalar type bo'lishi mumkin. GraphQL scalar type - bu ID, String, Boolean, yoki Int kabi bitta qiymatga yechiladigan primitiv tur.

Hint

GraphQLning built-in scalar turlaridan tashqari, custom scalar turlarni ham belgilashingiz mumkin (batafsil bu yerda).

Yuqoridagi Author object type ta'rifi SDLda biz ko'rsatgan matnni generatsiya qiladi:

Graphql
1type Author {
2  id: Int!
3  firstName: String
4  lastName: String
5  posts: [Post!]!
6}

@Field() dekoratori ixtiyoriy type funksiyasini (masalan, type => Int) va ixtiyoriy opsiyalar obyektini qabul qiladi.

Type funksiyasi TypeScript tipi va GraphQL tipi o'rtasida noaniqlik bo'lishi mumkin bo'lgan holatlarda kerak bo'ladi. Aniqrog'i: string va boolean turlari uchun kerak emas; number uchun esa kerak (u GraphQL Int yoki Float ga moslashtirilishi kerak). Type funksiyasi shunchaki kerakli GraphQL turini qaytarishi kerak (bu boblarda ko'rsatilgan misollar kabi).

Opsiyalar obyekti quyidagi key/value juftliklarini qabul qilishi mumkin:

  • nullable: field nullable bo'lishini belgilash (@nestjs/graphqlda har bir field default bo'yicha non-nullable); boolean
  • description: field tavsifi; string
  • deprecationReason: fieldni deprecated deb belgilash; string

Masalan:

TypeScript
1@Field({ description: `Book title`, deprecationReason: 'Not useful in v2 schema' })
2title: string;
Hint

Butun object type uchun ham tavsif qo'shish yoki deprecate qilish mumkin: @ObjectType({{ '{' }} description: 'Author model' {{ '}' }}).

Field massiv bo'lsa, quyida ko'rsatilgandek, Field() dekoratorining type funksiyasida massiv turini qo'lda ko'rsatishimiz kerak:

TypeScript
1@Field(type => [Post])
2posts: Post[];
Hint

Array qavs notatsiyasi ([ ]) orqali massiv chuqurligini ko'rsatishimiz mumkin. Masalan, [[Int]] butun sonli matritsani bildiradi.

Massivning o'zi emas, balki uning elementlari nullable bo'lishini belgilash uchun nullable xossasini 'items' qilib qo'ying:

TypeScript
1@Field(type => [Post], { nullable: 'items' })
2posts: Post[];
Hint

Agar massivning o'zi ham, elementlari ham nullable bo'lsa, nullable ni 'itemsAndList' qilib qo'ying.

Endi Author object type yaratilgach, Post object type ni ham aniqlaymiz.

TypeScript
posts/models/post.model
1import { Field, Int, ObjectType } from '@nestjs/graphql';
2
3@ObjectType()
4export class Post {
5  @Field(type => Int)
6  id: number;
7
8  @Field()
9  title: string;
10
11  @Field(type => Int, { nullable: true })
12  votes?: number;
13}

Post object type SDLda quyidagi qismini generatsiya qiladi:

Graphql
1type Post {
2  id: Int!
3  title: String!
4  votes: Int
5}

Code first resolver

Bu nuqtada biz data graphda mavjud bo'lishi mumkin bo'lgan objectlar (type ta'riflari)ni belgiladik, ammo klientlar hali bu objectlar bilan o'zaro ishlay olmaydi. Buni hal qilish uchun resolver klassini yaratishimiz kerak. Code first usulida resolver klassi ham resolver funksiyalarini aniqlaydi, ham Query type ni generatsiya qiladi. Buni quyidagi misolda ko'ramiz:

TypeScript
authors/authors.resolver
1@Resolver(() => Author)
2export class AuthorsResolver {
3  constructor(
4    private authorsService: AuthorsService,
5    private postsService: PostsService,
6  ) {}
7
8  @Query(() => Author)
9  async author(@Args('id', { type: () => Int }) id: number) {
10    return this.authorsService.findOneById(id);
11  }
12
13  @ResolveField()
14  async posts(@Parent() author: Author) {
15    const { id } = author;
16    return this.postsService.findAll({ authorId: id });
17  }
18}
Hint

Barcha dekoratorlar (@Resolver, @ResolveField, @Args va h.k.) @nestjs/graphql paketidan eksport qilinadi.

Bir nechta resolver klassini aniqlashingiz mumkin. Nest ularni runtime da birlashtiradi. Kodni qanday tashkil qilish bo'yicha batafsil ma'lumot uchun quyidagi module bo'limiga qarang.

Note

AuthorsService va PostsService ichidagi mantiq ehtiyojga qarab sodda yoki murakkab bo'lishi mumkin. Bu misolning asosiy maqsadi resolverlarni qanday qurish va ularning boshqa providerlar bilan qanday ishlashini ko'rsatishdir.

Yuqoridagi misolda AuthorsResolver ni yaratdik; u bitta query resolver funksiyasi va bitta field resolver funksiyasini belgilaydi. Resolver yaratish uchun resolver funksiyalari metodlar bo'lgan klass yaratamiz va klassni @Resolver() dekoratori bilan belgilaymiz.

Bu misolda so'rovda yuborilgan idga asoslanib author obyektini olish uchun query handler aniqladik. Metod query handler ekanini ko'rsatish uchun @Query() dekoratoridan foydalaning.

@Resolver() dekoratoriga uzatiladigan argument ixtiyoriy, ammo graf murakkablashganda muhim bo'ladi. U field resolver funksiyalari object graph bo'ylab pastga tushganda foydalanadigan parent obyektni ko'rsatish uchun ishlatiladi.

Bizning misolda klassda field resolver funksiyasi (Author object type ning posts xossasi uchun) mavjud bo'lgani uchun, @Resolver() dekoratoriga ushbu klassda aniqlangan barcha field resolverlar uchun parent type (ya'ni mos ObjectType klassi nomi)ni ko'rsatishimiz shart. Misoldan ko'rinib turibdiki, field resolver yozganda parent obyektga kirish kerak bo'ladi (field shu obyektning a'zosi). Bizning misolda authorning id sini oladigan servisni chaqirib, posts massivini to'ldiramiz. Shuning uchun @Resolver() dekoratorida parent obyektni ko'rsatish zarur. Keyin @Parent() parametr dekoratori orqali field resolver ichida parent obyektga havola olamiz.

Bir nechta @Query() resolver funksiyalarini (shu klass ichida ham, boshqa resolver klasslarida ham) aniqlashingiz mumkin va ular generatsiya qilingan SDLda bitta Query type ta'rifiga hamda resolver mapdagi mos entrylarga birlashtiriladi. Bu querylarni ular foydalanadigan model va servislar yonida belgilash va modullarda tartibli saqlash imkonini beradi.

Hint

Nest CLI boilerplate kodning barchasini avtomatik generatsiya qiladigan generator (schematic) taqdim etadi; bu qo'lda qilishimizni oldini oladi va developer tajribasini ancha soddalashtiradi. Bu imkoniyat haqida batafsil bu yerda o'qing.

Query type nomlari

Yuqoridagi misollarda @Query() dekoratori GraphQL schema query type nomini metod nomiga qarab generatsiya qiladi. Masalan, quyidagi konstruktsiyani oling:

TypeScript
1@Query(() => Author)
2async author(@Args('id', { type: () => Int }) id: number) {
3  return this.authorsService.findOneById(id);
4}

Bu schemada author query uchun quyidagi entryni generatsiya qiladi (query type metodi bilan bir xil nomdan foydalanadi):

Graphql
1type Query {
2  author(id: Int!): Author
3}
Hint

GraphQL querylar haqida ko'proq bu yerda o'qing.

Odatda biz bu nomlarni ajratishni afzal ko'ramiz; masalan, query handler metod nomi getAuthor() bo'lsin, lekin query type nomi author bo'lib qolsin. Xuddi shu narsa field resolverlar uchun ham qo'llanadi. Buni @Query() va @ResolveField() dekoratorlariga mapping nomlarini argument sifatida berib oson qilamiz, quyida ko'rsatilgandek:

TypeScript
authors/authors.resolver
1@Resolver(() => Author)
2export class AuthorsResolver {
3  constructor(
4    private authorsService: AuthorsService,
5    private postsService: PostsService,
6  ) {}
7
8  @Query(() => Author, { name: 'author' })
9  async getAuthor(@Args('id', { type: () => Int }) id: number) {
10    return this.authorsService.findOneById(id);
11  }
12
13  @ResolveField('posts', () => [Post])
14  async getPosts(@Parent() author: Author) {
15    const { id } = author;
16    return this.postsService.findAll({ authorId: id });
17  }
18}

Yuqoridagi getAuthor handler metodi SDLda quyidagi qismini generatsiya qiladi:

Graphql
1type Query {
2  author(id: Int!): Author
3}

Query dekoratori opsiyalari

@Query() dekoratorining opsiyalar obyektida (yuqorida {{ '{' }}name: 'author'{{ '}' }} bergan joyimizda) quyidagi key/value juftliklari bo'lishi mumkin:

  • name: query nomi; string
  • description: GraphQL schema hujjatlarida ishlatiladigan tavsif (masalan, GraphQL playgroundda); string
  • deprecationReason: queryni deprecated deb belgilash uchun metadata; string
  • nullable: query null data qaytarishi mumkinligini belgilaydi; boolean yoki 'items' yoki 'itemsAndList' (yuqorida tushuntirilgan)

Args dekoratori opsiyalari

@Args() dekoratoridan so'rovdagi argumentlarni olish uchun foydalaning. Bu REST route parameter argument extraction bilan juda o'xshash ishlaydi.

Odatda @Args() dekoratori sodda bo'ladi va yuqoridagi getAuthor() misolidagi kabi obyekt argumentini talab qilmaydi. Masalan, identifikator turi string bo'lsa, quyidagi konstruktsiya yetarli va GraphQL so'rovdan nomlangan fieldni olib metod argumenti sifatida beradi.

TypeScript
1@Args('id') id: string

getAuthor() holatida number tipi ishlatilgan va bu muammo tug'diradi. number TypeScript tipi kutilayotgan GraphQL ko'rinishi haqida yetarli ma'lumot bermaydi (masalan, Int va Float). Shuning uchun type referenceni aniq uzatishimiz kerak. Buni Args() dekoratoriga ikkinchi argument sifatida opsiyalarni berish orqali qilamiz:

TypeScript
1@Query(() => Author, { name: 'author' })
2async getAuthor(@Args('id', { type: () => Int }) id: number) {
3  return this.authorsService.findOneById(id);
4}

Opsiyalar obyektida quyidagi ixtiyoriy key/value juftliklarini ko'rsatish mumkin:

  • type: GraphQL turini qaytaradigan funksiya
  • defaultValue: default qiymat; any
  • description: tavsif metadata; string
  • deprecationReason: fieldni deprecate qilish va nega deprecate qilinganini ko'rsatuvchi metadata; string
  • nullable: field nullable bo'lishi

Query handler metodlari bir nechta argument qabul qilishi mumkin. Faraz qilaylik, authorni firstName va lastName bo'yicha topmoqchimiz. Bu holatda @Args ni ikki marta chaqiramiz:

TypeScript
1getAuthor(
2  @Args('firstName', { nullable: true }) firstName?: string,
3  @Args('lastName', { defaultValue: '' }) lastName?: string,
4) {}
Hint

firstName GraphQL nullable field bo'lgani uchun, null yoki undefinedning non-value turlarini field tipiga qo'shish shart emas. Biroq, resolverlarda bu non-value turlarni hisobga olgan holda type guard qilish kerakligini yodda tuting, chunki GraphQL nullable field bunday turlarni resolverga o'tkazadi.

Ajratilgan arguments klassi

Inline @Args() chaqiruvlari bilan yuqoridagi kabi kod shishib ketadi. Buning o'rniga alohida GetAuthorArgs arguments klassini yaratib, uni handlerda quyidagicha ishlatishingiz mumkin:

TypeScript
1@Args() args: GetAuthorArgs

GetAuthorArgs klassini @ArgsType() yordamida quyidagicha yarating:

TypeScript
authors/dto/get-author.args
1import { MinLength } from 'class-validator';
2import { Field, ArgsType } from '@nestjs/graphql';
3
4@ArgsType()
5class GetAuthorArgs {
6  @Field({ nullable: true })
7  firstName?: string;
8
9  @Field({ defaultValue: '' })
10  @MinLength(3)
11  lastName: string;
12}
Hint

TypeScript metadata reflection tizimi cheklovlari sababli, tur va optionalityni ko'rsatish uchun @Field dekoratoridan foydalanish yoki CLI plugindan foydalanish kerak. Shuningdek, firstName GraphQL nullable field bo'lgani uchun, null yoki undefinedning non-value turlarini field tipiga qo'shish shart emas. Biroq, resolverlarda bu non-value turlarni hisobga olgan holda type guard qilish kerakligini yodda tuting, chunki GraphQL nullable field bunday turlarni resolverga o'tkazadi.

Bu SDLda quyidagi qismini generatsiya qiladi:

Graphql
1type Query {
2  author(firstName: String, lastName: String = ''): Author
3}
Hint

GetAuthorArgs kabi arguments klasslari ValidationPipe bilan juda yaxshi ishlaydi (batafsil bu yerda).

Klass merosi

Standart TypeScript klass merosidan foydalanib, generic utility type funksiyalariga ega (fieldlar, field xossalari, validatsiyalar va h.k.) bazaviy klasslarni yaratish va ularni kengaytirish mumkin. Masalan, sahifalashga oid argumentlar doimo offset va limit fieldlarini o'z ichiga oladi, lekin boshqa indeks fieldlari typega xos bo'lishi mumkin. Quyidagi kabi iyerarxiya qurishingiz mumkin.

Bazaviy @ArgsType() klass:

TypeScript
1@ArgsType()
2class PaginationArgs {
3  @Field(() => Int)
4  offset: number = 0;
5
6  @Field(() => Int)
7  limit: number = 10;
8}

Bazaviy @ArgsType() klassdan typega xos subklass:

TypeScript
1@ArgsType()
2class GetAuthorArgs extends PaginationArgs {
3  @Field({ nullable: true })
4  firstName?: string;
5
6  @Field({ defaultValue: '' })
7  @MinLength(3)
8  lastName: string;
9}

Xuddi shu yondashuvni @ObjectType() obyektlari bilan ham ishlatish mumkin. Bazaviy klassda generic xossalarni aniqlang:

TypeScript
1@ObjectType()
2class Character {
3  @Field(() => Int)
4  id: number;
5
6  @Field()
7  name: string;
8}

Subklasslarda typega xos xossalarni qo'shing:

TypeScript
1@ObjectType()
2class Warrior extends Character {
3  @Field()
4  level: number;
5}

Resolver bilan ham merosdan foydalanishingiz mumkin. Merdos va TypeScript generiklarini birlashtirib type safety ta'minlaysiz. Masalan, generic findAll queryga ega bazaviy klass yaratish uchun quyidagi konstruktsiyadan foydalaning:

TypeScript
1function BaseResolver<T extends Type<unknown>>(classRef: T): any {
2  @Resolver({ isAbstract: true })
3  abstract class BaseResolverHost {
4    @Query(() => [classRef], { name: `findAll${classRef.name}` })
5    async findAll(): Promise<T[]> {
6      return [];
7    }
8  }
9  return BaseResolverHost;
10}

Quyidagilarni yodda tuting:

  • aniq return type (any yuqorida) kerak: aks holda TypeScript private klass ta'rifidan foydalanishga shikoyat qiladi. Tavsiya: any o'rniga interfeys aniqlang.
  • Type @nestjs/common paketidan import qilinadi
  • isAbstract: true xossasi ushbu klass uchun SDL generatsiya qilinmasligi kerakligini bildiradi. Eslatma: bu xossani boshqa turlar uchun ham SDL generatsiyasini to'xtatish maqsadida qo'llash mumkin.

Quyidagicha BaseResolver ning konkret subklassini generatsiya qilishingiz mumkin:

TypeScript
1@Resolver(() => Recipe)
2export class RecipesResolver extends BaseResolver(Recipe) {
3  constructor(private recipesService: RecipesService) {
4    super();
5  }
6}

Bu konstruktsiya SDLda quyidagini generatsiya qiladi:

Graphql
1type Query {
2  findAllRecipe: [Recipe!]!
3}

Generiklar

Yuqorida generiklardan foydalanishning bir misolini ko'rdik. Bu kuchli TypeScript xususiyati foydali abstraksiyalar yaratishda qo'l keladi. Masalan, quyida shu hujjatga asoslangan kursorli pagination implementatsiyasi keltirilgan:

TypeScript
1import { Field, ObjectType, Int } from '@nestjs/graphql';
2import { Type } from '@nestjs/common';
3
4interface IEdgeType<T> {
5  cursor: string;
6  node: T;
7}
8
9export interface IPaginatedType<T> {
10  edges: IEdgeType<T>[];
11  nodes: T[];
12  totalCount: number;
13  hasNextPage: boolean;
14}
15
16export function Paginated<T>(classRef: Type<T>): Type<IPaginatedType<T>> {
17  @ObjectType(`${classRef.name}Edge`)
18  abstract class EdgeType {
19    @Field(() => String)
20    cursor: string;
21
22    @Field(() => classRef)
23    node: T;
24  }
25
26  @ObjectType({ isAbstract: true })
27  abstract class PaginatedType implements IPaginatedType<T> {
28    @Field(() => [EdgeType], { nullable: true })
29    edges: EdgeType[];
30
31    @Field(() => [classRef], { nullable: true })
32    nodes: T[];
33
34    @Field(() => Int)
35    totalCount: number;
36
37    @Field()
38    hasNextPage: boolean;
39  }
40  return PaginatedType as Type<IPaginatedType<T>>;
41}

Yuqoridagi bazaviy klass aniqlangach, endi shu xatti-harakatni meros oladigan maxsus turlarni oson yarata olamiz. Masalan:

TypeScript
1@ObjectType()
2class PaginatedAuthor extends Paginated(Author) {}

Schema first

Oldingi bobda aytilganidek, schema first yondashuvda SDLda schema turlarini qo'lda belgilaymiz (batafsil bu yerda). Quyidagi SDL type ta'riflarini ko'rib chiqing.

Hint

Ushbu bobda qulaylik uchun barcha SDLni bir joyga (masalan, bitta .graphql faylga) jamladik. Amalda esa kodni modul tarzda tashkil qilish maqsadga muvofiq bo'lishi mumkin. Masalan, har bir domen entitiga mos type ta'riflarini, tegishli servislar, resolver kodi va Nest modul ta'rifi klassi bilan birga alohida direktoriyada saqlash foydali bo'ladi. Nest barcha individual schema type ta'riflarini runtime da birlashtiradi.

Graphql
1type Author {
2  id: Int!
3  firstName: String
4  lastName: String
5  posts: [Post]
6}
7
8type Post {
9  id: Int!
10  title: String!
11  votes: Int
12}
13
14type Query {
15  author(id: Int!): Author
16}

Schema first resolver

Yuqoridagi schema bitta queryni taqdim etadi - author(id: Int!): Author.

Hint

GraphQL querylar haqida ko'proq bu yerda o'qing.

Endi author querylarini yechadigan AuthorsResolver klassini yaratamiz:

TypeScript
authors/authors.resolver
1@Resolver('Author')
2export class AuthorsResolver {
3  constructor(
4    private authorsService: AuthorsService,
5    private postsService: PostsService,
6  ) {}
7
8  @Query()
9  async author(@Args('id') id: number) {
10    return this.authorsService.findOneById(id);
11  }
12
13  @ResolveField()
14  async posts(@Parent() author) {
15    const { id } = author;
16    return this.postsService.findAll({ authorId: id });
17  }
18}
Hint

Barcha dekoratorlar (@Resolver, @ResolveField, @Args va h.k.) @nestjs/graphql paketidan eksport qilinadi.

Note

AuthorsService va PostsService ichidagi mantiq ehtiyojga qarab sodda yoki murakkab bo'lishi mumkin. Bu misolning asosiy maqsadi resolverlarni qanday qurish va ularning boshqa providerlar bilan qanday ishlashini ko'rsatishdir.

@Resolver() dekoratori majburiy. U ixtiyoriy string argument qabul qiladi va klass nomini bildiradi. Bu klass nomi, agar klassda @ResolveField() dekoratorlari bo'lsa, Nestga dekorator bilan belgilangan metod qaysi parent type (bizning misolda Author)ga tegishli ekanini bildirish uchun kerak. Muqobil ravishda, @Resolver()ni klass boshida belgilash o'rniga, har bir metodga qo'shish mumkin:

TypeScript
1@Resolver('Author')
2@ResolveField()
3async posts(@Parent() author) {
4  const { id } = author;
5  return this.postsService.findAll({ authorId: id });
6}

Bu holatda (@Resolver() metod darajasida bo'lsa), klass ichidagi har bir @ResolveField() uchun @Resolver()ni alohida qo'shishingiz kerak. Bu yaxshi amaliyot hisoblanmaydi (ortiqcha yuk keltiradi).

Hint

@Resolver()ga uzatilgan klass nomi argumenti querylar (@Query() dekoratori) yoki mutatsiyalarga (@Mutation() dekoratori) ta'sir qilmaydi.

Warning

@Resolver dekoratorini metod darajasida ishlatish code first yondashuvda qo'llab-quvvatlanmaydi.

Yuqoridagi misollarda @Query() va @ResolveField() dekoratorlari GraphQL schema turlariga metod nomi asosida bog'lanadi. Masalan, quyidagi konstruktsiyani oling:

TypeScript
1@Query()
2async author(@Args('id') id: number) {
3  return this.authorsService.findOneById(id);
4}

Bu schemada author query uchun quyidagi entryni generatsiya qiladi (query type metodi bilan bir xil nomdan foydalanadi):

Graphql
1type Query {
2  author(id: Int!): Author
3}

Odatda biz bu nomlarni ajratishni afzal ko'ramiz; resolver metodlari uchun getAuthor() yoki getPosts() kabi nomlardan foydalanamiz. Buni dekoratorga mapping nomini argument sifatida berish orqali oson qilamiz, quyida ko'rsatilgandek:

TypeScript
authors/authors.resolver
1@Resolver('Author')
2export class AuthorsResolver {
3  constructor(
4    private authorsService: AuthorsService,
5    private postsService: PostsService,
6  ) {}
7
8  @Query('author')
9  async getAuthor(@Args('id') id: number) {
10    return this.authorsService.findOneById(id);
11  }
12
13  @ResolveField('posts')
14  async getPosts(@Parent() author) {
15    const { id } = author;
16    return this.postsService.findAll({ authorId: id });
17  }
18}
Hint

Nest CLI barcha boilerplate kodni avtomatik generatsiya qiladigan generator (schematic) taqdim etadi va developer tajribasini ancha soddalashtiradi. Bu imkoniyat haqida batafsil bu yerda o'qing.

Typelarni generatsiya qilish

Schema first yondashuvida typings generatsiyasi funksiyasi yoqilgan ( oldingi bobda outputAs: 'class' bilan ko'rsatilganidek) deb faraz qilsak, ilovani ishga tushirgach quyidagi fayl generatsiya qilinadi ( GraphQLModule.forRoot() metodida ko'rsatgan joyga). Masalan, src/graphql.ts:

TypeScript
graphql
1export class Author {
2  id: number;
3  firstName?: string;
4  lastName?: string;
5  posts?: Post[];
6}
7export class Post {
8  id: number;
9  title: string;
10  votes?: number;
11}
12
13export abstract class IQuery {
14  abstract author(id: number): Author | Promise<Author>;
15}

Klasslarni generatsiya qilish orqali (default interfeys generatsiyasi o'rniga) schema first yondashuv bilan birga deklarativ validatsiya dekoratorlaridan foydalanishingiz mumkin; bu juda foydali yondashuv (batafsil bu yerda). Masalan, generatsiya qilingan CreatePostInput klassiga class-validator dekoratorlarini qo'shib title fieldi uchun minimal va maksimal uzunlikni belgilashingiz mumkin:

TypeScript
1import { MinLength, MaxLength } from 'class-validator';
2
3export class CreatePostInput {
4  @MinLength(3)
5  @MaxLength(50)
6  title: string;
7}
Notice

Inputlar (va parametrlar) uchun auto-validationni yoqish uchun ValidationPipedan foydalaning. Validatsiya haqida batafsil bu yerda va pipe lar haqida bu yerda o'qing.

Biroq, avtomatik generatsiya qilingan faylga bevosita dekorator qo'shsangiz, bu fayl har safar generatsiya qilinganda ustidan yoziladi. Buning o'rniga, alohida fayl yarating va generatsiya qilingan klassni shunchaki kengaytiring.

TypeScript
1import { MinLength, MaxLength } from 'class-validator';
2import { Post } from '../../graphql.ts';
3
4export class CreatePostInput extends Post {
5  @MinLength(3)
6  @MaxLength(50)
7  title: string;
8}

GraphQL argument dekoratorlari

Standart GraphQL resolver argumentlariga maxsus dekoratorlar orqali kirishimiz mumkin. Quyida Nest dekoratorlari va ular ifodalovchi oddiy Apollo parametrlari taqqoslanadi.

@Root() and @Parent()root/parent
@Context(param?: string)context / context[param]
@Info(param?: string)info / info[param]
@Args(param?: string)args / args[param]

Bu argumentlarning ma'nosi quyidagicha:

  • root: parent field resolveridan qaytgan natijani o'z ichiga oladigan obyekt yoki top-level Query fieldida server konfiguratsiyasidan berilgan rootValue
  • context: ma'lum querydagi barcha resolverlar uchun umumiy obyekt; odatda har bir so'rov holatini saqlash uchun ishlatiladi
  • info: query bajarilish holati haqida ma'lumotni o'z ichiga oladigan obyekt
  • args: querydagi fieldga uzatilgan argumentlar obyekti

Modul

Yuqoridagi qadamlarni bajarganimizdan so'ng, GraphQLModule resolver mapni generatsiya qilish uchun zarur bo'lgan barcha ma'lumotni deklarativ tarzda belgilab oldik. GraphQLModule dekoratorlar orqali berilgan metadatani reflection yordamida tahlil qiladi va klasslarni to'g'ri resolver mapga avtomatik o'zgartiradi.

Qolgan yagona ish - resolver klass(lar)ini (AuthorsResolver) modulda provider sifatida ko'rsatish va modulni (AuthorsModule) import qilish, shunda Nest undan foydalanadi.

Masalan, buni AuthorsModuleda qilishimiz mumkin; u shu kontekstda kerak bo'ladigan boshqa servislarni ham taqdim etishi mumkin. AuthorsModuleni albatta biror joyda import qiling (masalan, root modulda yoki root modul import qiladigan boshqa modulda).

TypeScript
authors/authors.module
1@Module({
2  imports: [PostsModule],
3  providers: [AuthorsService, AuthorsResolver],
4})
5export class AuthorsModule {}
Hint

Kodingizni domen modeli bo'yicha tashkil qilish foydali (REST API dagi entry pointlarni qanday tashkil qilishingizga o'xshash). Bu yondashuvda modellaringizni (ObjectType klasslari), resolverlaringizni va servislaringizni domen modelini ifodalovchi Nest moduli ichida birga saqlang. Har bir modul uchun bularning barchasini bitta papkada saqlang. Shunday qilganingizda va Nest CLI orqali har bir elementni generatsiya qilsangiz, Nest bu qismlarning barchasini avtomatik bog'laydi (fayllarni mos papkalarga joylashtiradi, provider va imports massivlariga yozuvlar qo'shadi va h.k.).