NestJS en GraphQL in 2026: Schema's, Resolvers en Sollicitatievragen

De integratie van NestJS met GraphQL maakt de ontwikkeling van type-safe API's mogelijk. Deze tutorial behandelt code-first en schema-first benaderingen, resolver-patronen en sollicitatievragen voor 2026.

NestJS en GraphQL in 2026: Schema's, Resolvers en Sollicitatievragen

De integratie van NestJS met GraphQL biedt een gestructureerde aanpak voor het bouwen van type-safe API's die TypeScript-decorators combineren met de querytaal van GraphQL. Deze tutorial behandelt zowel de schema-first als code-first benadering, resolver-patronen en de sollicitatievragen die hiring managers in 2026 stellen.

Code-First vs Schema-First

NestJS 11 gebruikt standaard code-first GraphQL en genereert het schema uit TypeScript-klassen. Schema-first blijft beschikbaar voor teams die werken met bestaande .graphql-bestanden of SDL-gebaseerde workflows.

NestJS GraphQL Configureren met Apollo Server

NestJS integreert met Apollo Server via het @nestjs/graphql-pakket. De configuratie verschilt afhankelijk van de gekozen benadering—code-first genereert SDL uit decorators, terwijl schema-first .graphql-bestanden direct parseert.

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: generates schema
      sortSchema: true, // Alphabetical ordering for readability
      playground: process.env.NODE_ENV !== 'production', // Disable in prod
      introspection: process.env.NODE_ENV !== 'production',
    }),
  ],
})
export class AppModule {}

De optie autoSchemaFile schakelt de code-first modus in. Wanneer deze op true wordt gezet, wordt het schema in het geheugen gegenereerd zonder naar schijf te schrijven—handig voor serverless deployments waar bestandssysteemtoegang beperkt kan zijn.

GraphQL Types Definiëren met Code-First Decorators

Code-first definieert GraphQL types met behulp van TypeScript-klassen gedecoreerd met @ObjectType(). Elk veld gebruikt @Field() om het GraphQL-type en nullability te specificeren.

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

@ObjectType({ description: 'Application user' }) // Description appears in schema docs
export class User {
  @Field(() => ID) // Maps to GraphQL ID scalar
  id: string;

  @Field()
  email: string;

  @Field({ nullable: true }) // Optional field in GraphQL
  displayName?: string;

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

  @Field(() => [Post], { nullable: 'itemsAndList' }) // Both list and items can be null
  posts?: Post[];

  // Fields without @Field() are excluded from GraphQL schema
  passwordHash: string;
}

De optie nullable accepteert drie waarden: true (veld is optioneel), 'items' (lijst-items kunnen null zijn) en 'itemsAndList' (zowel de lijst als items kunnen null zijn). Deze granulariteit komt exact overeen met de nullability-semantiek van GraphQL.

Resolvers Bouwen voor Queries en Mutations

Resolvers verwerken inkomende GraphQL-operaties. NestJS gebruikt @Resolver() om een klasse als resolver te markeren, waarbij method-decorators de operatietypes specificeren.

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) // Binds resolver to User type for field resolution
export class UsersResolver {
  constructor(private readonly usersService: UsersService) {}

  @Query(() => [User], { name: 'users' }) // Explicit query name
  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);
  }
}

De decorator @Resolver(() => User) stelt de context in voor @ResolveField()-decorators, waardoor veld-niveau resolutie voor berekende of gerelateerde data mogelijk wordt.

Input Types en Validatie met class-validator

GraphQL input types definiëren mutation payloads. Het combineren van @InputType() met class-validator decorators maakt zowel schema-niveau als runtime validatie mogelijk.

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: 'Invalid email format' })
  email: string;

  @Field()
  @MinLength(8, { message: 'Password must be at least 8 characters' })
  @Matches(/[A-Z]/, { message: 'Password must contain uppercase letter' })
  password: string;

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

Validatie wordt globaal ingeschakeld door de ValidationPipe toe te voegen in main.ts. GraphQL-fouten bevatten validatieberichten in het extensions-veld, wat client-compatibiliteit waarborgt.

Klaar om je Node.js / NestJS gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Gerelateerde Data Resolven met @ResolveField en DataLoader

Field resolvers behandelen relaties tussen types. Zonder optimalisatie triggert het ophalen van een lijst gebruikers met hun posts N+1 queries—één voor gebruikers, dan één per gebruiker voor posts.

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 batches requests: one query for all user IDs
    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 bundelt en cacht verzoeken binnen een enkele GraphQL-operatie. Voor NestJS wordt de DataLoader op request-niveau voorzien met behulp van @Injectable({ scope: Scope.REQUEST }).

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 }) // New instance per request
export class PostsLoader {
  constructor(private readonly postsService: PostsService) {}

  public readonly batchByUserId = new DataLoader<string, Post[]>(
    async (userIds: readonly string[]) => {
      // Single query: SELECT * FROM posts WHERE user_id IN (...)
      const posts = await this.postsService.findByUserIds([...userIds]);
      // Map results back to input order
      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 reduceert N+1 queries tot een enkele gebundelde query, cruciaal voor GraphQL API's waar clients de query-diepte bepalen. Voor diepere behandeling van NestJS-architectuurpatronen, zie de NestJS Module & Dependency Injection sollicitatievragen.

Subscriptions voor Real-Time Data

GraphQL subscriptions sturen data naar clients via WebSocket-verbindingen. NestJS gebruikt de graphql-ws-bibliotheek, die het GraphQL over WebSocket protocol implementeert.

app.module.ts - Enable subscriptionstypescript
GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  autoSchemaFile: true,
  subscriptions: {
    'graphql-ws': true, // Modern protocol
    'subscriptions-transport-ws': false, // Deprecated legacy protocol
  },
}),
posts.resolver.tstypescript
import { Resolver, Subscription } from '@nestjs/graphql';
import { PubSub } from 'graphql-subscriptions';
import { Post } from './post.entity';

const pubSub = new PubSub(); // Use Redis PubSub for multi-instance deployments

@Resolver(() => Post)
export class PostsResolver {
  @Subscription(() => Post, {
    filter: (payload, variables) => 
      payload.postCreated.userId === variables.userId, // Client-side filtering
  })
  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 }); // Trigger subscription
    return post;
  }
}

Voor productie-deployments over meerdere instanties wordt de in-memory PubSub vervangen door graphql-redis-subscriptions om events cluster-breed te distribueren.

Authenticatie en Autorisatie in GraphQL

NestJS Guards werken naadloos met GraphQL resolvers. De executie-context verschilt van REST—gebruik GqlExecutionContext om het request te extraheren.

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; // Extract request from GraphQL context
  }
}

Guards worden toegepast op resolver- of method-niveau. Autorisatie op veldniveau gebruikt @ResolveField() met conditionele logica gebaseerd op gebruikersrollen.

users.resolver.tstypescript
@UseGuards(GqlAuthGuard)
@Resolver(() => User)
export class UsersResolver {
  @Query(() => User)
  me(@CurrentUser() user: User): User {
    return user; // Return authenticated user
  }

  @ResolveField(() => String, { nullable: true })
  email(@Parent() user: User, @CurrentUser() currentUser: User): string | null {
    // Only return email if viewing own profile or admin
    if (user.id === currentUser.id || currentUser.role === 'ADMIN') {
      return user.email;
    }
    return null;
  }
}

Voor complexe autorisatie kunt u GraphQL Shield of de ingebouwde CASL-integratie van NestJS overwegen. Het artikel NestJS Guards en Interceptors behandelt deze patronen in detail.

Veelgestelde NestJS GraphQL Sollicitatievragen

Technische sollicitatiegesprekken voor NestJS-posities bevatten vaak GraphQL-specifieke vragen. Hier zijn de patronen die hiring managers evalueren.

V: Hoe behandelt NestJS het N+1 probleem in GraphQL?

DataLoader bundelt field resolver calls binnen een enkel request. Wanneer meerdere parent-objecten hetzelfde veld opvragen, verzamelt DataLoader alle keys, voert één gebundelde query uit en distribueert de resultaten. De loader moet request-scoped zijn om caching-problemen tussen requests te voorkomen.

V: Wat is het verschil tussen code-first en schema-first in NestJS GraphQL?

Code-first genereert het GraphQL-schema uit TypeScript-decorators tijdens runtime, waardoor types en schema automatisch gesynchroniseerd blijven. Schema-first parseert .graphql SDL-bestanden, wat handmatige typedefinities vereist. Code-first is geschikt voor TypeScript-native teams; schema-first werkt beter wanneer het schema het contract is tussen frontend- en backend-teams.

V: Hoe zou men veld-niveau permissies implementeren?

Er bestaan drie benaderingen: (1) @ResolveField() met conditionele returns gebaseerd op gebruikerscontext, (2) Aangepaste decorators die permissies controleren vóór veldresolutie, (3) Schema-directieven zoals @auth(requires: ADMIN) verwerkt door een directive transformer. De eerste benadering biedt de meeste flexibiliteit; directieven bieden de schoonste schema-documentatie.

V: Leg de GraphQL-context uit in NestJS.

Het context-object passeert door alle resolvers binnen een request. NestJS vult het standaard met het HTTP-request. Aangepaste context wordt geconfigureerd in GraphQLModule.forRoot() via de context-optie—nuttig voor het toevoegen van DataLoader-instanties, geauthenticeerde gebruiker of databaseverbindingen.

V: Hoe schalen subscriptions over meerdere server-instanties?

In-memory PubSub werkt alleen voor single-instance deployments. Multi-instance architecturen vereisen een externe broker—Redis PubSub is standaard. Elke server-instantie abonneert op Redis-kanalen; wanneer een instantie een event publiceert, ontvangen alle instanties het en sturen het naar verbonden WebSocket-clients.

Voor aanvullende NestJS-sollicitatievoorbereiding kan de module Middleware en Interceptors verkend worden, die request lifecycle-patronen behandelt.

Conclusie

  • NestJS GraphQL ondersteunt zowel code-first als schema-first benaderingen—code-first vereenvoudigt TypeScript-projecten, schema-first is geschikt voor SDL-gestuurde workflows
  • DataLoader elimineert N+1 queries door field resolver requests te bundelen binnen een enkele operatie
  • Request-scoped DataLoader-instanties voorkomen cache-vervuiling bij gelijktijdige requests
  • GqlExecutionContext verbindt NestJS Guards met de resolver-context van GraphQL
  • Productie-subscriptions vereisen Redis PubSub voor multi-instance event distributie
  • Autorisatie op veldniveau combineert @ResolveField() met gebruikerscontext-controles

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Delen

Gerelateerde artikelen