NestJS et GraphQL en 2026 : Schémas, Resolvers et Questions d'Entretien

Guide complet sur l'intégration de NestJS avec GraphQL : approches code-first et schema-first, création de resolvers, et préparation aux entretiens techniques en 2026.

NestJS et GraphQL en 2026 : Schémas, Resolvers et Questions d'Entretien

L'intégration de GraphQL dans NestJS offre une approche structurée pour construire des APIs type-safe exploitant les décorateurs TypeScript et le langage de requête GraphQL. Ce tutoriel couvre les approches schema-first et code-first, les patterns de resolvers, ainsi que les questions d'entretien posées par les recruteurs en 2026.

Code-First vs Schema-First

NestJS 11 utilise par défaut l'approche code-first, générant le schéma à partir des classes TypeScript. L'approche schema-first reste disponible pour les équipes disposant de fichiers .graphql existants ou de workflows basés sur SDL.

Configuration de NestJS GraphQL avec Apollo Server

NestJS s'intègre avec Apollo Server via le package @nestjs/graphql. La configuration diffère selon l'approche choisie : code-first génère le SDL depuis les décorateurs, tandis que schema-first parse directement les fichiers .graphql.

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'),
      sortSchema: true,
      playground: process.env.NODE_ENV !== 'production',
      introspection: process.env.NODE_ENV !== 'production',
    }),
  ],
})
export class AppModule {}

L'option autoSchemaFile active le mode code-first. La définir sur true génère le schéma en mémoire sans écriture sur le disque, ce qui s'avère utile pour les déploiements serverless où l'accès au système de fichiers peut être restreint.

Définition des Types GraphQL avec les Décorateurs Code-First

L'approche code-first définit les types GraphQL à l'aide de classes TypeScript décorées avec @ObjectType(). Chaque champ utilise @Field() pour spécifier son type GraphQL et sa nullabilité.

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

@ObjectType({ description: 'Utilisateur de l\'application' })
export class User {
  @Field(() => ID)
  id: string;

  @Field()
  email: string;

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

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

  @Field(() => [Post], { nullable: 'itemsAndList' })
  posts?: Post[];

  // Les champs sans @Field() sont exclus du schéma GraphQL
  passwordHash: string;
}

L'option nullable accepte trois valeurs : true (champ optionnel), 'items' (les éléments de la liste peuvent être null), et 'itemsAndList' (la liste et les éléments peuvent être null). Cette granularité correspond précisément à la sémantique de nullabilité de GraphQL.

Construction des Resolvers pour Queries et Mutations

Les resolvers gèrent les opérations GraphQL entrantes. NestJS utilise @Resolver() pour marquer une classe comme resolver et @Query() ou @Mutation() pour définir les opérations.

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

@Resolver(() => User)
export class UserResolver {
  constructor(private readonly userService: UserService) {}

  @Query(() => [User], { name: 'users' })
  async findAll(): Promise<User[]> {
    return this.userService.findAll();
  }

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

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

Le premier argument de @Query() et @Mutation() définit le type de retour GraphQL. L'option name personnalise le nom du champ dans le schéma, utile lorsque les conventions de nommage TypeScript diffèrent des standards GraphQL.

Création des Input Types pour les Mutations

Les mutations nécessitent des input types pour valider et typer les données entrantes. NestJS utilise @InputType() pour définir ces structures.

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

@InputType()
export class CreateUserInput {
  @Field()
  @IsEmail({}, { message: 'Format email invalide' })
  email: string;

  @Field()
  @MinLength(8, { message: 'Le mot de passe doit contenir au moins 8 caractères' })
  password: string;

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

L'intégration avec class-validator permet une validation déclarative. Le ValidationPipe de NestJS applique automatiquement ces règles avant l'exécution du resolver.

Gestion des Relations avec les Field Resolvers

Les field resolvers résolvent des champs spécifiques de manière paresseuse, évitant le chargement de données non demandées. Cette technique implémente le pattern N+1 avec DataLoader.

user.resolver.tstypescript
import { Resolver, ResolveField, Parent } from '@nestjs/graphql';
import { User } from './user.entity';
import { Post } from '../post/post.entity';
import { PostService } from '../post/post.service';

@Resolver(() => User)
export class UserResolver {
  constructor(private readonly postService: PostService) {}

  @ResolveField(() => [Post])
  async posts(@Parent() user: User): Promise<Post[]> {
    return this.postService.findByUserId(user.id);
  }

  @ResolveField(() => Int)
  async postCount(@Parent() user: User): Promise<number> {
    return this.postService.countByUserId(user.id);
  }
}

Le décorateur @Parent() injecte l'objet parent résolu. Cette approche permet une résolution conditionnelle basée sur les champs demandés par le client.

Optimisation avec DataLoader pour Éviter le Problème N+1

Le problème N+1 survient lorsque les field resolvers effectuent une requête par élément parent. DataLoader agrège ces requêtes en une seule opération batch.

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

@Injectable({ scope: Scope.REQUEST })
export class PostLoader {
  constructor(private readonly postService: PostService) {}

  readonly batchByUserId = new DataLoader<string, Post[]>(async (userIds) => {
    const posts = await this.postService.findByUserIds([...userIds]);
    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) || []);
  });
}

L'utilisation de Scope.REQUEST garantit qu'une nouvelle instance de DataLoader est créée pour chaque requête, évitant les fuites de cache entre utilisateurs.

Authentification et Autorisations dans GraphQL

La sécurisation des resolvers utilise les guards NestJS combinés avec des décorateurs personnalisés pour extraire les informations d'authentification.

auth.guard.tstypescript
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';

@Injectable()
export class GqlAuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const ctx = GqlExecutionContext.create(context);
    const { req } = ctx.getContext();
    return !!req.user;
  }
}

// current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';

export const CurrentUser = createParamDecorator(
  (data: unknown, context: ExecutionContext) => {
    const ctx = GqlExecutionContext.create(context);
    return ctx.getContext().req.user;
  },
);

Le guard convertit le contexte d'exécution standard en contexte GraphQL via GqlExecutionContext.create(). Cette conversion est nécessaire car GraphQL utilise une structure de contexte différente de REST.

Gestion des Erreurs et Exceptions GraphQL

GraphQL gère les erreurs différemment de REST. Les exceptions sont transformées en erreurs GraphQL structurées avec des extensions personnalisables.

graphql-exception.filter.tstypescript
import { Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { GqlExceptionFilter, GqlArgumentsHost } from '@nestjs/graphql';
import { GraphQLError } from 'graphql';

@Catch(HttpException)
export class GraphqlExceptionFilter implements GqlExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const gqlHost = GqlArgumentsHost.create(host);
    const response = exception.getResponse();
    
    return new GraphQLError(
      typeof response === 'string' ? response : (response as any).message,
      {
        extensions: {
          code: this.getErrorCode(exception.getStatus()),
          status: exception.getStatus(),
        },
      },
    );
  }

  private getErrorCode(status: number): string {
    const codes: Record<number, string> = {
      400: 'BAD_REQUEST',
      401: 'UNAUTHENTICATED',
      403: 'FORBIDDEN',
      404: 'NOT_FOUND',
    };
    return codes[status] || 'INTERNAL_SERVER_ERROR';
  }
}

Cette approche maintient la compatibilité avec les clients GraphQL standard qui attendent des erreurs structurées selon la spécification GraphQL.

Subscriptions pour les Mises à Jour en Temps Réel

Les subscriptions GraphQL permettent les communications en temps réel via WebSocket. NestJS utilise graphql-subscriptions pour le pub/sub.

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

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      autoSchemaFile: true,
      subscriptions: {
        'graphql-ws': true,
        'subscriptions-transport-ws': false,
      },
    }),
  ],
})
export class AppModule {}

// notification.resolver.ts
import { Resolver, Subscription, Mutation, Args } from '@nestjs/graphql';
import { PubSub } from 'graphql-subscriptions';

const pubSub = new PubSub();

@Resolver()
export class NotificationResolver {
  @Subscription(() => String, {
    filter: (payload, variables) => payload.userId === variables.userId,
  })
  notificationReceived(@Args('userId') userId: string) {
    return pubSub.asyncIterableIterator('NOTIFICATION_RECEIVED');
  }

  @Mutation(() => Boolean)
  async sendNotification(
    @Args('userId') userId: string,
    @Args('message') message: string,
  ) {
    await pubSub.publish('NOTIFICATION_RECEIVED', {
      notificationReceived: message,
      userId,
    });
    return true;
  }
}

Le protocole graphql-ws est la norme moderne pour les subscriptions GraphQL, remplaçant l'ancien subscriptions-transport-ws qui n'est plus maintenu.

Prêt à réussir tes entretiens Node.js / NestJS ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

Questions d'Entretien NestJS GraphQL en 2026

Les entretiens techniques évaluent la compréhension des concepts GraphQL et leur application dans NestJS. Voici les questions fréquemment posées.

Quelle est la différence entre code-first et schema-first ?

Code-first génère le schéma SDL à partir des décorateurs TypeScript, offrant un typage end-to-end automatique. Schema-first parse les fichiers .graphql et génère les types TypeScript via des plugins. Code-first convient aux nouvelles applications TypeScript, tandis que schema-first s'adapte aux projets avec un schéma GraphQL existant ou des équipes familières avec SDL.

Comment résoudre le problème N+1 en GraphQL ?

Le problème N+1 se résout avec DataLoader, qui agrège les requêtes individuelles en opérations batch. Chaque DataLoader doit être scopé à la requête (Scope.REQUEST) pour éviter les fuites de cache. L'implémentation batch doit retourner les résultats dans l'ordre exact des clés d'entrée.

Comment sécuriser une API GraphQL NestJS ?

La sécurisation combine plusieurs couches : guards pour l'authentification au niveau resolver, interceptors pour la transformation des réponses, class-validator pour la validation des inputs, et throttling pour la protection contre les abus. L'introspection et le playground doivent être désactivés en production.

Quelle est la différence entre Query, Mutation et Subscription ?

Query lit les données sans effets de bord. Mutation modifie les données sur le serveur. Subscription établit une connexion WebSocket pour les mises à jour en temps réel. GraphQL garantit que les queries sont idempotentes, tandis que les mutations peuvent avoir des effets de bord.

Comment gérer les erreurs dans GraphQL NestJS ?

Les erreurs GraphQL utilisent la classe GraphQLError avec des extensions personnalisées. Les exception filters convertissent les exceptions NestJS en erreurs GraphQL structurées. Les erreurs partielles permettent de retourner des données valides même si certains champs échouent.

Conclusion

L'intégration de NestJS avec GraphQL fournit un framework robuste pour construire des APIs type-safe et performantes. L'approche code-first simplifie le développement en éliminant la duplication entre les définitions TypeScript et le schéma GraphQL. Les patterns comme DataLoader et les guards d'authentification garantissent des applications scalables et sécurisées. La maîtrise de ces concepts, combinée à une compréhension des trade-offs entre les différentes approches, prépare efficacement aux entretiens techniques Node.js en 2026.

Partager

Articles similaires