Passer au contenu

Résolveurs

Les résolveurs fournissent les instructions pour transformer une opération GraphQL (une requête, une mutation ou une souscription) en données. Ils retournent la même structure de données que nous spécifions dans notre schéma — soit de manière synchronisée, soit en tant que promesse qui se résout vers un résultat de cette forme. En général, vous créez une carte de résolveurs manuellement. Le paquet @nestjs/graphql, d’autre part, génère automatiquement une carte de résolveurs en utilisant les métadonnées fournies par les décorateurs que vous utilisez pour annoter les classes. Pour démontrer le processus d’utilisation des fonctionnalités du paquet pour créer une API GraphQL, nous allons créer une API simples pour les auteurs.

Code d’abord

Dans l’approche code d’abord, nous ne suivons pas le processus typique de création de notre schéma GraphQL en écrivant SDL GraphQL à la main. Au lieu de cela, nous utilisons des décorateurs TypeScript pour générer l’SDL à partir des définitions de classes TypeScript. Le paquet @nestjs/graphql lit les métadonnées définies via les décorateurs et génère automatiquement le schéma pour vous.

Types d’objets

La plupart des définitions dans un schéma GraphQL sont des types d’objets. Chaque type d’objet que vous définissez doit représenter un objet de domaine avec lequel un client d’application pourrait avoir besoin d’interagir. Par exemple, notre API d’exemple doit pouvoir récupérer une liste d’auteurs et leurs publications, donc nous devrions définir le type Author et le type Post pour supporter cette fonctionnalité.

Si nous utilisions l’approche schéma d’abord, nous définirions un tel schéma avec SDL comme ceci :

type Author {
id: Int!
firstName: String
lastName: String
posts: [Post]
}

Dans ce cas, en utilisant l’approche code d’abord, nous définissons les schémas en utilisant des classes TypeScript et en utilisant des décorateurs TypeScript pour annoter les champs de ces classes. L’équivalent du SDL ci-dessus dans l’approche code d’abord est :

authors/models/author.model.ts
import { Field, Int, ObjectType } from '@nestjs/graphql';
import { Post } from './post';
@ObjectType()
export class Author {
@Field(type => Int)
id: number;
@Field({ nullable: true })
firstName?: string;
@Field({ nullable: true })
lastName?: string;
@Field(type => [Post])
posts: Post[];
}

Le type d’objet Author, comme toute classe, est constitué d’un ensemble de champs, chaque champ déclarant un type. Le type d’un champ correspond à un type de GraphQL. Un type de champ GraphQL peut être soit un autre type d’objet soit un type scalaire. Un type scalaire GraphQL est un type primitif (comme ID, String, Boolean ou Int) qui résout à une seule valeur.

La définition du type d’objet Author ci-dessus entraînera Nest à générer l’SDL que nous avons montré ci-dessus :

type Author {
id: Int!
firstName: String
lastName: String
posts: [Post]
}

Le décorateur @Field() accepte une fonction de type optionnelle (par exemple, type => Int), et éventuellement un objet d’options.

Les options disponibles pour le décorateur @Field() incluent des clés/valeurs pour définir si un champ est nullable, fournir une description, et marquer un champ comme obsolète.

Pour définir que les éléments d’un tableau (et non le tableau lui-même) sont nullable, définissez la propriété nullable à 'items' comme montré ci-dessous :

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

Maintenant que le type d’objet Author est créé, définissons le type Post.

posts/models/post.model.ts
import { Field, Int, ObjectType } from '@nestjs/graphql';
@ObjectType()
export class Post {
@Field(type => Int)
id: number;
@Field()
title: string;
@Field(type => Int, { nullable: true })
votes?: number;
}

Le type d’objet Post entraînera la génération de la partie suivante du schéma GraphQL en SDL :

type Post {
id: Int!
title: String!
votes: Int
}

Résolveur de code d’abord

À ce stade, nous avons défini les objets (définitions de types) qui peuvent exister dans notre graphe de données, mais les clients n’ont pas encore de moyen d’interagir avec ces objets. Pour y remédier, nous devons créer une classe de résolveur. Dans la méthode code d’abord, une classe de résolveur définit des fonctions de résolveur et génère le type de requête.

authors/authors.resolver.ts
import { Resolver, Query, Args, ResolveField, Parent } from '@nestjs/graphql';
import { Author } from './models/author.model';
import { AuthorsService } from './authors.service';
import { PostsService } from './posts.service';
@Resolver(of => Author)
export class AuthorsResolver {
constructor(
private authorsService: AuthorsService,
private postsService: PostsService,
) {}
@Query(returns => Author)
async author(@Args('id') id: number) {
return this.authorsService.findOneById(id);
}
@ResolveField()
async posts(@Parent() author: Author) {
const { id } = author;
return this.postsService.findAll({ authorId: id });
}
}

Dans l’exemple ci-dessus, nous avons créé le AuthorsResolver qui définit une fonction de résolveur de requête et une fonction de résolveur de champ. Pour créer un résolveur, nous créons une classe avec des fonctions de résolveur comme méthodes, et annotons la classe avec le décorateur @Resolver().

Le décorateur @Resolver() est requis. Il prend un argument de chaîne optionnel avec le nom d’une classe. Ce nom de classe est requis chaque fois que la classe inclut des décorateurs @ResolveField() pour informer Nest que la méthode annotée est associée à un type parent.

En général, nous préférons découpler ces noms, en utilisant des noms comme getAuthor() pour notre méthode de gestion de requêtes, mais en continuant d’utiliser author pour notre nom de type de requête. Cela peut être fait en passant le nom de mappage comme argument aux décorateurs.

Vous pouvez définir plusieurs fonctions de résolveur @Query() (à la fois dans cette classe et dans n’importe quelle autre classe de résolveur), et elles seront agrégées dans une seule définition de type de requête dans l’SDL généré avec les entrées appropriées dans la carte de résolveurs.

Types de requête

Dans les exemples ci-dessus, le décorateur @Query() génère un type de requête du schéma GraphQL basé sur le nom de la méthode. Par exemple :

@Query(returns => Author)
async author(@Args('id') id: number) {
return this.authorsService.findOneById(id);
}

génère l’entrée suivante pour la requête author dans notre schéma :

type Query {
author(id: Int!): Author
}

Traditionnellement, nous préférons découpler ces noms ; par exemple, nous préférons utiliser un nom comme getAuthor() pour notre méthode de gestion de requêtes, mais continuer d’utiliser author pour notre nom de type de requête.

Options du décorateur de requête

L’objet d’options du décorateur @Query() accepte plusieurs paires clé/valeur. Cela inclut le nom de la requête, une description qui sera utilisée pour générer la documentation du schéma GraphQL, et la possibilité que la requête puisse retourner une réponse de données nulles.

Malgré tout, gardez en tête que vous devez bien structurer votre projet pour que vos modules soient bien définis avec les entités qu’ils représentent et que vous pouvez également utiliser des classes de contrôle de validation pour gérer les entrées.

Définir une classe d’arguments dédiée

Avec de nombreux appels @Args(), le code peut devenir encombré. À la place, vous pouvez créer une classe d’arguments dédiée et y accéder dans la méthode gestionnaire.

export class GetAuthorArgs {
@Field({ nullable: true })
firstName?: string;
@Field({ defaultValue: '' })
lastName: string;
}

En résumé, la gestion des résolveurs et des arguments au sein d’une API GraphQL dans NestJS peut être organisée efficacement pour assurer la propreté et l’efficacité de votre code tout en respectant les meilleures pratiques de développement.