# 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. - Published: 2026-07-25 - Updated: 2026-07-25 - Author: SharpSkill - Reading time: 5 min --- 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](https://www.apollographql.com/docs/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. ```typescript // app.module.ts import { Module } from '@nestjs/common'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { join } from 'path'; @Module({ imports: [ GraphQLModule.forRoot({ 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. ```typescript // user.entity.ts 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](https://graphql.org/learn/schema/#lists-and-non-null). ## 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. ```typescript // users.resolver.ts 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 { return this.usersService.findAll(); } @Query(() => User, { nullable: true }) user(@Args('id', { type: () => ID }) id: string): Promise { return this.usersService.findOne(id); } @Mutation(() => User) createUser(@Args('input') input: CreateUserInput): Promise { 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](https://github.com/typestack/class-validator) decorators maakt zowel schema-niveau als runtime validatie mogelijk. ```typescript // dto/create-user.input.ts 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. ## 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. ```typescript // users.resolver.ts 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 { // DataLoader batches requests: one query for all user IDs return this.postsLoader.batchByUserId.load(user.id); } @ResolveField(() => Int) async postCount(@Parent() user: User): Promise { 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 })`. ```typescript // posts/posts.loader.ts 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( 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(); 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](/technologies/node-nestjs/interview-questions/nestjs-modules-di). ## 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](https://github.com/enisdenjo/graphql-ws) implementeert. ```typescript // app.module.ts - Enable subscriptions GraphQLModule.forRoot({ driver: ApolloDriver, autoSchemaFile: true, subscriptions: { 'graphql-ws': true, // Modern protocol 'subscriptions-transport-ws': false, // Deprecated legacy protocol }, }), ``` ```typescript // posts.resolver.ts 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 { 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. ```typescript // guards/gql-auth.guard.ts 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. ```typescript // users.resolver.ts @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](https://github.com/dimatill/graphql-shield) of de ingebouwde CASL-integratie van NestJS overwegen. Het artikel [NestJS Guards en Interceptors](/blog/node-nestjs/nestjs-guards-interceptors-modular-architecture) 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](/technologies/node-nestjs/interview-questions/middleware-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 --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/nl/blog/node-nestjs/nestjs-graphql-schemas-resolvers-tutorial