NestJS und GraphQL 2026: Schemas, Resolver und Interviewfragen

Die Integration von NestJS mit GraphQL ermöglicht die Entwicklung typsicherer APIs. Dieses Tutorial behandelt Code-First und Schema-First Ansätze, Resolver-Muster und Interviewfragen für 2026.

NestJS und GraphQL 2026: Schemas, Resolver und Interviewfragen

Die Integration von NestJS mit GraphQL ermöglicht die Entwicklung typsicherer APIs, die TypeScript-Dekoratoren mit der Abfragesprache von GraphQL kombinieren. Dieses Tutorial behandelt sowohl den Schema-First als auch den Code-First Ansatz, Resolver-Muster und die Interviewfragen, die Personalverantwortliche im Jahr 2026 stellen.

Code-First vs Schema-First

NestJS 11 verwendet standardmäßig Code-First GraphQL und generiert das Schema aus TypeScript-Klassen. Schema-First bleibt für Teams verfügbar, die bereits mit .graphql-Dateien oder SDL-basierten Workflows arbeiten.

NestJS GraphQL mit Apollo Server einrichten

NestJS integriert sich über das @nestjs/graphql-Paket mit Apollo Server. Die Konfiguration unterscheidet sich je nach gewähltem Ansatz—Code-First generiert SDL aus Dekoratoren, während Schema-First .graphql-Dateien direkt parst.

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 {}

Die Option autoSchemaFile aktiviert den Code-First Modus. Wird sie auf true gesetzt, wird das Schema im Speicher generiert, ohne auf die Festplatte geschrieben zu werden—nützlich für Serverless-Deployments, bei denen der Dateisystemzugriff eingeschränkt sein kann.

GraphQL-Typen mit Code-First Dekoratoren definieren

Code-First definiert GraphQL-Typen mithilfe von TypeScript-Klassen, die mit @ObjectType() dekoriert sind. Jedes Feld verwendet @Field(), um seinen GraphQL-Typ und die Nullfähigkeit anzugeben.

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;
}

Die Option nullable akzeptiert drei Werte: true (Feld ist optional), 'items' (Listenelemente können null sein) und 'itemsAndList' (sowohl die Liste als auch die Elemente können null sein). Diese Granularität entspricht exakt der Nullfähigkeitssemantik von GraphQL.

Resolver für Queries und Mutations erstellen

Resolver verarbeiten eingehende GraphQL-Operationen. NestJS verwendet @Resolver(), um eine Klasse als Resolver zu markieren, wobei Methoden-Dekoratoren die Operationstypen spezifizieren.

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);
  }
}

Der Dekorator @Resolver(() => User) etabliert den Kontext für @ResolveField()-Dekoratoren und ermöglicht die Auflösung auf Feldebene für berechnete oder verknüpfte Daten.

Eingabetypen und Validierung mit class-validator

GraphQL-Eingabetypen definieren Mutations-Payloads. Die Kombination von @InputType() mit class-validator-Dekoratoren ermöglicht sowohl Schema-Level als auch Laufzeit-Validierung.

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;
}

Die Validierung wird global aktiviert, indem die ValidationPipe in main.ts hinzugefügt wird. GraphQL-Fehler enthalten Validierungsmeldungen im extensions-Feld, was die Client-Kompatibilität gewährleistet.

Bereit für deine Node.js / NestJS-Interviews?

Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.

Verknüpfte Daten mit @ResolveField und DataLoader auflösen

Feld-Resolver verarbeiten Beziehungen zwischen Typen. Ohne Optimierung löst das Abrufen einer Liste von Benutzern mit ihren Beiträgen N+1 Abfragen aus—eine für Benutzer, dann eine pro Benutzer für Beiträge.

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 bündelt und cached Anfragen innerhalb einer einzelnen GraphQL-Operation. Für NestJS wird der DataLoader auf Request-Ebene mittels @Injectable({ scope: Scope.REQUEST }) bereitgestellt.

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 reduziert N+1 Abfragen auf eine einzige gebündelte Abfrage, was für GraphQL-APIs entscheidend ist, bei denen Clients die Abfragetiefe kontrollieren. Für eine tiefere Behandlung von NestJS-Architekturmustern siehe die NestJS Module & Dependency Injection Interviewfragen.

Subscriptions für Echtzeitdaten

GraphQL-Subscriptions übertragen Daten über WebSocket-Verbindungen an Clients. NestJS verwendet die graphql-ws-Bibliothek, die das GraphQL over WebSocket Protokoll implementiert.

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;
  }
}

Für Produktions-Deployments über mehrere Instanzen hinweg wird die In-Memory PubSub durch graphql-redis-subscriptions ersetzt, um Ereignisse clusterweit zu verteilen.

Authentifizierung und Autorisierung in GraphQL

NestJS Guards funktionieren nahtlos mit GraphQL-Resolvern. Der Ausführungskontext unterscheidet sich von REST—GqlExecutionContext wird verwendet, um die Anfrage zu extrahieren.

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 werden auf Resolver- oder Methodenebene angewendet. Autorisierung auf Feldebene verwendet @ResolveField() mit bedingter Logik basierend auf Benutzerrollen.

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;
  }
}

Für komplexe Autorisierung bietet sich GraphQL Shield oder die integrierte CASL-Integration von NestJS an. Der Artikel NestJS Guards und Interceptors behandelt diese Muster ausführlich.

Häufige NestJS GraphQL Interviewfragen

Technische Interviews für NestJS-Positionen beinhalten häufig GraphQL-spezifische Fragen. Hier sind die Muster, die Personalverantwortliche bewerten.

F: Wie behandelt NestJS das N+1-Problem in GraphQL?

DataLoader bündelt Feld-Resolver-Aufrufe innerhalb einer einzelnen Anfrage. Wenn mehrere übergeordnete Objekte dasselbe Feld anfordern, sammelt DataLoader alle Schlüssel, führt eine gebündelte Abfrage aus und verteilt die Ergebnisse. Der Loader muss request-scoped sein, um Caching-Probleme zwischen Anfragen zu vermeiden.

F: Was ist der Unterschied zwischen Code-First und Schema-First in NestJS GraphQL?

Code-First generiert das GraphQL-Schema zur Laufzeit aus TypeScript-Dekoratoren und hält Typen und Schema automatisch synchron. Schema-First parst .graphql SDL-Dateien, was manuelle Typdefinitionen erfordert. Code-First eignet sich für TypeScript-native Teams; Schema-First funktioniert besser, wenn das Schema der Vertrag zwischen Frontend- und Backend-Teams ist.

F: Wie würde man Berechtigungen auf Feldebene implementieren?

Es existieren drei Ansätze: (1) @ResolveField() mit bedingten Rückgaben basierend auf dem Benutzerkontext, (2) Benutzerdefinierte Dekoratoren, die Berechtigungen vor der Feldauflösung prüfen, (3) Schema-Direktiven wie @auth(requires: ADMIN), die von einem Direktiven-Transformer verarbeitet werden. Der erste Ansatz bietet die größte Flexibilität; Direktiven bieten die sauberste Schema-Dokumentation.

F: Erklären Sie den GraphQL-Kontext in NestJS.

Das Kontextobjekt wird durch alle Resolver innerhalb einer Anfrage weitergereicht. NestJS befüllt es standardmäßig mit der HTTP-Anfrage. Benutzerdefinierter Kontext wird in GraphQLModule.forRoot() über die context-Option konfiguriert—nützlich zum Hinzufügen von DataLoader-Instanzen, authentifizierten Benutzern oder Datenbankverbindungen.

F: Wie skalieren Subscriptions über mehrere Server-Instanzen?

In-Memory PubSub funktioniert nur für Single-Instance-Deployments. Multi-Instance-Architekturen erfordern einen externen Broker—Redis PubSub ist Standard. Jede Server-Instanz abonniert Redis-Kanäle; wenn eine Instanz ein Ereignis veröffentlicht, erhalten alle Instanzen es und senden es an verbundene WebSocket-Clients.

Für zusätzliche NestJS-Interviewvorbereitung sollte das Modul Middleware und Interceptors erkundet werden, das Request-Lifecycle-Muster behandelt.

Fazit

  • NestJS GraphQL unterstützt sowohl Code-First als auch Schema-First Ansätze—Code-First vereinfacht TypeScript-Projekte, Schema-First eignet sich für SDL-gesteuerte Workflows
  • DataLoader eliminiert N+1 Abfragen durch Bündelung von Feld-Resolver-Anfragen innerhalb einer einzelnen Operation
  • Request-scoped DataLoader-Instanzen verhindern Cache-Verschmutzung bei gleichzeitigen Anfragen
  • GqlExecutionContext verbindet NestJS Guards mit dem Resolver-Kontext von GraphQL
  • Produktions-Subscriptions erfordern Redis PubSub für Multi-Instance Event-Verteilung
  • Autorisierung auf Feldebene kombiniert @ResolveField() mit Benutzerkontext-Prüfungen

Fang an zu üben!

Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.

Teilen

Verwandte Artikel