2026'da NestJS ve GraphQL: Şemalar, Resolver'lar ve Mülakat Soruları

NestJS GraphQL entegrasyonu hakkında kapsamlı rehber: schema-first ve code-first yaklaşımları, resolver'lar, DataLoader ve 2026 mülakat soruları.

2026'da NestJS ve GraphQL: Şemalar, Resolver'lar ve Mülakat Soruları

NestJS GraphQL entegrasyonu, TypeScript dekoratörleri ve GraphQL sorgu dilini kullanan tip güvenli API'ler oluşturmak için yapılandırılmış bir yaklaşım sunar. Bu rehber, schema-first ve code-first yaklaşımlarını, resolver kalıplarını ve 2026 yılında işe alım müdürlerinin sorduğu mülakat sorularını kapsar.

Code-First vs Schema-First

NestJS 11 varsayılan olarak code-first GraphQL kullanır ve şemayı TypeScript sınıflarından oluşturur. Schema-first yaklaşımı, mevcut .graphql dosyaları veya SDL tabanlı iş akışları olan ekipler için hala kullanılabilir durumdadır.

Apollo Server ile NestJS GraphQL Kurulumu

NestJS, @nestjs/graphql paketi aracılığıyla Apollo Server ile entegre olur. Kurulum, seçilen yaklaşıma göre farklılık gösterir — code-first dekoratörlerden SDL oluşturur, schema-first ise .graphql dosyalarını doğrudan ayrıştırır.

app.module.tstypescript
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { join } from 'path';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      autoSchemaFile: join(process.cwd(), 'src/schema.gql'), // Code-first: şema oluşturur
      sortSchema: true, // Okunabilirlik için alfabetik sıralama
      playground: process.env.NODE_ENV !== 'production', // Üretimde devre dışı bırak
      introspection: process.env.NODE_ENV !== 'production',
    }),
  ],
})
export class AppModule {}

autoSchemaFile seçeneği code-first modunu etkinleştirir. true olarak ayarlamak şemayı diske yazmadan bellekte oluşturur — dosya sistemi erişiminin kısıtlı olabileceği serverless dağıtımları için kullanışlıdır.

Code-First Dekoratörleri ile GraphQL Tiplerini Tanımlama

Code-first, GraphQL tiplerini @ObjectType() ile dekore edilmiş TypeScript sınıfları kullanarak tanımlar. Her alan, GraphQL tipini ve nullable özelliğini belirtmek için @Field() kullanır.

user.entity.tstypescript
import { ObjectType, Field, ID, Int } from '@nestjs/graphql';

@ObjectType({ description: 'Uygulama kullanıcısı' }) // Açıklama şema belgelerinde görünür
export class User {
  @Field(() => ID) // GraphQL ID skalerine eşler
  id: string;

  @Field()
  email: string;

  @Field({ nullable: true }) // GraphQL'de isteğe bağlı alan
  displayName?: string;

  @Field(() => Int, { defaultValue: 0 })
  postCount: number;

  @Field(() => [Post], { nullable: 'itemsAndList' }) // Hem liste hem de öğeler null olabilir
  posts?: Post[];

  // @Field() olmayan alanlar GraphQL şemasından hariç tutulur
  passwordHash: string;
}

nullable seçeneği üç değer kabul eder: true (alan isteğe bağlı), 'items' (liste öğeleri null olabilir) ve 'itemsAndList' (hem liste hem de öğeler null olabilir). Bu ayrıntı düzeyi, GraphQL'in nullability semantiği ile tam olarak eşleşir.

Sorgular ve Mutasyonlar için Resolver Oluşturma

Resolver'lar gelen GraphQL işlemlerini yönetir. NestJS, bir sınıfı resolver olarak işaretlemek için @Resolver() kullanır ve metot dekoratörleri işlem tiplerini belirtir.

users.resolver.tstypescript
import { Resolver, Query, Mutation, Args, ID } from '@nestjs/graphql';
import { User } from './user.entity';
import { UsersService } from './users.service';
import { CreateUserInput } from './dto/create-user.input';

@Resolver(() => User) // Alan çözümlemesi için resolver'ı User tipine bağlar
export class UsersResolver {
  constructor(private readonly usersService: UsersService) {}

  @Query(() => [User], { name: 'users' }) // Açık sorgu adı
  findAll(): Promise<User[]> {
    return this.usersService.findAll();
  }

  @Query(() => User, { nullable: true })
  user(@Args('id', { type: () => ID }) id: string): Promise<User | null> {
    return this.usersService.findOne(id);
  }

  @Mutation(() => User)
  createUser(@Args('input') input: CreateUserInput): Promise<User> {
    return this.usersService.create(input);
  }
}

@Resolver(() => User) dekoratörü, @ResolveField() dekoratörleri için bağlam oluşturur ve hesaplanmış veya ilişkili veriler için alan düzeyinde çözümlemeyi mümkün kılar.

Input Tipleri ve class-validator ile Doğrulama

GraphQL input tipleri mutasyon payload'larını tanımlar. @InputType()class-validator dekoratörleri ile birleştirmek, şema düzeyinde ve çalışma zamanı doğrulamasını etkinleştirir.

dto/create-user.input.tstypescript
import { InputType, Field } from '@nestjs/graphql';
import { IsEmail, MinLength, IsOptional, Matches } from 'class-validator';

@InputType()
export class CreateUserInput {
  @Field()
  @IsEmail({}, { message: 'Geçersiz e-posta formatı' })
  email: string;

  @Field()
  @MinLength(8, { message: 'Şifre en az 8 karakter olmalıdır' })
  @Matches(/[A-Z]/, { message: 'Şifre büyük harf içermelidir' })
  password: string;

  @Field({ nullable: true })
  @IsOptional()
  @MinLength(2)
  displayName?: string;
}

main.ts dosyasına ValidationPipe ekleyerek doğrulamayı global olarak etkinleştirin. GraphQL hataları, istemci uyumluluğunu koruyarak extensions alanında doğrulama mesajlarını içerir.

Node.js / NestJS mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

@ResolveField ve DataLoader ile İlişkili Verileri Çözümleme

Alan resolver'ları tipler arasındaki ilişkileri yönetir. Optimizasyon olmadan, kullanıcı listesini gönderileriyle birlikte getirmek N+1 sorgu tetikler — kullanıcılar için bir sorgu, ardından her kullanıcı için gönderiler için bir sorgu.

users.resolver.tstypescript
import { Resolver, ResolveField, Parent } from '@nestjs/graphql';
import { User } from './user.entity';
import { Post } from '../posts/post.entity';
import { PostsLoader } from '../posts/posts.loader';

@Resolver(() => User)
export class UsersResolver {
  constructor(private readonly postsLoader: PostsLoader) {}

  @ResolveField(() => [Post])
  async posts(@Parent() user: User): Promise<Post[]> {
    // DataLoader istekleri gruplar: tüm kullanıcı ID'leri için tek sorgu
    return this.postsLoader.batchByUserId.load(user.id);
  }

  @ResolveField(() => Int)
  async postCount(@Parent() user: User): Promise<number> {
    const posts = await this.postsLoader.batchByUserId.load(user.id);
    return posts.length;
  }
}

DataLoader, tek bir GraphQL işlemi içinde istekleri gruplar ve önbelleğe alır. NestJS'de DataLoader, @Injectable({ scope: Scope.REQUEST }) kullanılarak istek kapsamına alınmalıdır.

posts/posts.loader.tstypescript
import { Injectable, Scope } from '@nestjs/common';
import * as DataLoader from 'dataloader';
import { PostsService } from './posts.service';
import { Post } from './post.entity';

@Injectable({ scope: Scope.REQUEST }) // Her istek için yeni örnek
export class PostsLoader {
  constructor(private readonly postsService: PostsService) {}

  public readonly batchByUserId = new DataLoader<string, Post[]>(
    async (userIds: readonly string[]) => {
      // Tek sorgu: SELECT * FROM posts WHERE user_id IN (...)
      const posts = await this.postsService.findByUserIds([...userIds]);
      // Sonuçları giriş sırasına göre eşle
      const postsMap = new Map<string, Post[]>();
      posts.forEach(post => {
        const existing = postsMap.get(post.userId) || [];
        postsMap.set(post.userId, [...existing, post]);
      });
      return userIds.map(id => postsMap.get(id) || []);
    }
  );
}

DataLoader, N+1 sorgularını tek bir toplu sorguya indirger — istemcilerin sorgu derinliğini kontrol ettiği GraphQL API'leri için kritik öneme sahiptir. NestJS mimari kalıpları hakkında daha fazla bilgi için NestJS Modülleri ve Dependency Injection mülakat soruları modülüne bakılabilir.

Gerçek Zamanlı Veri için Subscription'lar

GraphQL subscription'ları, WebSocket bağlantıları üzerinden istemcilere veri iletir. NestJS, GraphQL over WebSocket protokolünü uygulayan graphql-ws kütüphanesini kullanır.

app.module.ts - Subscription'ları etkinleştirtypescript
GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  autoSchemaFile: true,
  subscriptions: {
    'graphql-ws': true, // Modern protokol
    'subscriptions-transport-ws': false, // Kullanımdan kaldırılmış eski protokol
  },
}),
posts.resolver.tstypescript
import { Resolver, Subscription } from '@nestjs/graphql';
import { PubSub } from 'graphql-subscriptions';
import { Post } from './post.entity';

const pubSub = new PubSub(); // Çoklu örnek dağıtımları için Redis PubSub kullanın

@Resolver(() => Post)
export class PostsResolver {
  @Subscription(() => Post, {
    filter: (payload, variables) => 
      payload.postCreated.userId === variables.userId, // İstemci tarafı filtreleme
  })
  postCreated() {
    return pubSub.asyncIterableIterator('postCreated');
  }

  @Mutation(() => Post)
  async createPost(@Args('input') input: CreatePostInput): Promise<Post> {
    const post = await this.postsService.create(input);
    pubSub.publish('postCreated', { postCreated: post }); // Subscription'ı tetikle
    return post;
  }
}

Birden fazla örnek üzerinde üretim dağıtımları için, bellek içi PubSub yerine olayları küme genelinde yayınlamak üzere graphql-redis-subscriptions kullanılmalıdır.

GraphQL'de Kimlik Doğrulama ve Yetkilendirme

NestJS Guard'ları GraphQL resolver'ları ile sorunsuz çalışır. Yürütme bağlamı REST'ten farklıdır — isteği çıkarmak için GqlExecutionContext kullanılmalıdır.

guards/gql-auth.guard.tstypescript
import { Injectable, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class GqlAuthGuard extends AuthGuard('jwt') {
  getRequest(context: ExecutionContext) {
    const ctx = GqlExecutionContext.create(context);
    return ctx.getContext().req; // GraphQL bağlamından isteği çıkar
  }
}

Guard'lar resolver veya metot düzeyinde uygulanır. Alan düzeyinde yetkilendirme, kullanıcı rollerine dayalı koşullu mantık ile @ResolveField() kullanır.

users.resolver.tstypescript
@UseGuards(GqlAuthGuard)
@Resolver(() => User)
export class UsersResolver {
  @Query(() => User)
  me(@CurrentUser() user: User): User {
    return user; // Kimliği doğrulanmış kullanıcıyı döndür
  }

  @ResolveField(() => String, { nullable: true })
  email(@Parent() user: User, @CurrentUser() currentUser: User): string | null {
    // E-postayı yalnızca kendi profili görüntülenirken veya admin ise döndür
    if (user.id === currentUser.id || currentUser.role === 'ADMIN') {
      return user.email;
    }
    return null;
  }
}

Karmaşık yetkilendirme için GraphQL Shield veya NestJS'in yerleşik CASL entegrasyonu değerlendirilebilir. NestJS Guard'lar ve Interceptor'lar makalesi bu kalıpları derinlemesine ele alır.

Yaygın NestJS GraphQL Mülakat Soruları

NestJS pozisyonları için teknik mülakatlar sıklıkla GraphQL'e özgü sorular içerir. İşe alım müdürlerinin değerlendirdiği kalıplar şunlardır.

S: NestJS GraphQL'deki N+1 problemini nasıl çözer?

DataLoader, tek bir istek içinde alan resolver çağrılarını gruplar. Birden fazla üst nesne aynı alanı istediğinde, DataLoader tüm anahtarları toplar, tek bir toplu sorgu yürütür ve sonuçları dağıtır. Loader, istekler arası önbellek sorunlarını önlemek için istek kapsamında olmalıdır.

S: NestJS GraphQL'de code-first ve schema-first arasındaki fark nedir?

Code-first, GraphQL şemasını çalışma zamanında TypeScript dekoratörlerinden oluşturur ve tipleri ile şemayı otomatik olarak senkronize tutar. Schema-first, .graphql SDL dosyalarını ayrıştırır ve manuel tip tanımları gerektirir. Code-first, TypeScript-native ekipler için uygundur; schema-first, şemanın frontend ve backend ekipleri arasında sözleşme olduğu durumlarda daha iyi çalışır.

S: Alan düzeyinde izinleri nasıl uygularsınız?

Üç yaklaşım mevcuttur: (1) Kullanıcı bağlamına dayalı koşullu dönüşlerle @ResolveField(), (2) Alan çözümlemesinden önce izinleri kontrol eden özel dekoratörler, (3) Direktif dönüştürücü tarafından işlenen @auth(requires: ADMIN) gibi şema direktifleri. İlk yaklaşım en fazla esnekliği sunar; direktifler en temiz şema belgelerini sağlar.

S: NestJS'de GraphQL bağlamını açıklayın.

Bağlam nesnesi, bir istek içindeki tüm resolver'lardan geçer. NestJS varsayılan olarak HTTP isteği ile doldurur. Özel bağlam, GraphQLModule.forRoot() içinde context seçeneği ile yapılandırılır — DataLoader örnekleri, kimliği doğrulanmış kullanıcı veya veritabanı bağlantıları eklemek için kullanışlıdır.

S: Subscription'lar birden fazla sunucu örneğinde nasıl ölçeklenir?

Bellek içi PubSub yalnızca tek örnekli dağıtımlar için çalışır. Çok örnekli mimariler harici bir aracı gerektirir — Redis PubSub standarttır. Her sunucu örneği Redis kanallarına abone olur; herhangi bir örnek bir olay yayınladığında, tüm örnekler bunu alır ve bağlı WebSocket istemcilerine iletir.

Ek NestJS mülakat hazırlığı için, istek yaşam döngüsü kalıplarını kapsayan Middleware ve Interceptor'lar modülüne bakılabilir.

Sonuç

  • NestJS GraphQL hem code-first hem de schema-first yaklaşımlarını destekler — code-first TypeScript projelerini basitleştirir, schema-first SDL odaklı iş akışlarına uygundur
  • DataLoader, tek bir işlem içinde alan resolver isteklerini gruplayarak N+1 sorgularını ortadan kaldırır
  • İstek kapsamlı DataLoader örnekleri, eşzamanlı istekler arasında önbellek kirliliğini önler
  • GqlExecutionContext, NestJS Guard'larını GraphQL resolver bağlamı ile köprüler
  • Üretim subscription'ları, çok örnekli olay dağıtımı için Redis PubSub gerektirir
  • Alan düzeyinde yetkilendirme, @ResolveField() ile kullanıcı bağlam kontrollerini birleştirir

Pratik yapmaya başla!

Mülakat simülatörleri ve teknik testlerle bilgini test et.

Etiketler

#nestjs
#graphql
#node.js
#typescript
#api

Paylaş

İlgili makaleler