Pourquoi NestJS avec GraphQL ?
Contexte réel : GraphQL est une alternative puissante aux API REST, offrant un contrôle total sur les données requises et renvoyées par l'API. Un dev a besoin de ce type d'infrastructure au quotidien pour construire des applications rapides, scalables et efficaces.
Un cas d'utilisation concret : Lorsqu'un développeur crée une application web monopage (SPA) qui nécessite des données complexes provenant de plusieurs sources API, GraphQL permet de récupérer exactement les données nécessaires en un seul appel. Par exemple, dans une application e-commerce, au lieu de faire plusieurs requêtes pour obtenir les informations du produit, des revues, des catégories et des prix, on peut utiliser GraphQL pour récupérer toutes ces données en un seul appel.
Prerequis
Connaissances Necessaires :
- Connaissance de base de Node.js et JavaScript ES6.
- Familiarité avec les concepts de TypeScript.
- Un certain niveau d'expertise avec les frameworks Angular, React ou Vue.js pour mieux comprendre la façon dont GraphQL peut être intégré à ces technologies.
Outils à Installer :
- Node.js (>=12.x)
- npm (Node Package Manager) ou yarn
- Visual Studio Code (VSCode)
Concepts fondamentaux
Schema et Types de Données
GraphQL utilise un langage de schéma pour décrire les données que l'API peut renvoyer. Cela définit la structure exacte des requêtes et réponses possibles.
Exemple :
type Query {
hello: String
}
schema {
query: Query
}
Resolvers
Les résolveurs sont des fonctions qui définissent comment récupérer les données pour une requête particulière. Ils sont liés aux champs du schéma et déterminent la logique métier à exécuter.
Exemple :
import { Resolver, Query } from '@nestjs/graphql';
@Resolver()
export class AppResolver {
@Query(() => String)
hello(): string {
return 'Hello World!';
}
}
Mutations
Les mutations sont utilisées pour modifier les données de l'API. Elles ressemblent aux requêtes mais permettent également des modifications.
Exemple :
type Mutation {
createTask(title: String!): Task
}
type Task {
id: ID!
title: String!
}
Subscriptions
Les souscriptions sont utilisées pour envoyer des notifications en temps réel à l'client. Cela permet aux clients de recevoir des mises à jour instantanées dès qu'une certaine action est effectuée.
Exemple :
type Subscription {
newTask: Task
}
type Task {
id: ID!
title: String!
}
Mise en pratique : Projet fil rouge
Nous allons créer une application simple pour gérer les tâches à faire avec GraphQL et NestJS. Cette application comprendra des opérations de création, lecture, mise à jour et suppression de tâches.
Étape 1 : Initialiser le projet
nest new task-manager
cd task-manager
npm install @nestjs/graphql graphql-tools graphql apollo-server-express
Étape 2 : Créer les types et résolveurs
Créez un fichier task.model.ts :
export class Task {
id: string;
title: string;
completed: boolean;
}
Créez un fichier app.resolver.ts :
import { Resolver, Query, Mutation, Arg } from '@nestjs/graphql';
import { Task } from './task.model';
let tasks: Task[] = [];
@Resolver()
export class AppResolver {
@Query(() => [Task])
async findAllTasks(): Promise<Task[]> {
return tasks;
}
@Mutation(() => Task)
async createTask(@Arg('title') title: string): Promise<Task> {
const task = { id: Date.now().toString(), title, completed: false };
tasks.push(task);
return task;
}
@Mutation(() => Task)
async updateTask(
@Arg('id') id: string,
@Arg('title', { nullable: true }) title?: string,
@Arg('completed', { nullable: true }) completed?: boolean
): Promise<Task> {
const task = tasks.find(t => t.id === id);
if (task) {
if (title !== undefined) task.title = title;
if (completed !== undefined) task.completed = completed;
}
return task;
}
@Mutation(() => Task)
async deleteTask(@Arg('id') id: string): Promise<Task> {
const index = tasks.findIndex(t => t.id === id);
if (index !== -1) {
const task = tasks.splice(index, 1)[0];
return task;
}
throw new Error(`Task with id ${id} not found`);
}
}
Étape 3 : Configurer le serveur GraphQL
Modifier app.module.ts :
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { join } from 'path';
@Module({
imports: [
GraphQLModule.forRoot({
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
sortSchema: true,
playground: true,
}),
],
controllers: [],
providers: [],
})
export class AppModule {}
Étape 4 : Exécuter l'application
npm run start
Accédez à http://localhost:3000/graphql et essayez les requêtes suivantes :
- Requête pour obtenir toutes les tâches :
query {
findAllTasks {
id
title
completed
}
}
- Mutation pour créer une nouvelle tâche :
mutation {
createTask(title: "Buy groceries") {
id
title
completed
}
}
Erreurs fréquentes et debugging
Erreur 1 : Cannot query field "findAllTasks" on type "Query"
Cause : Le nom de la fonction du résolveur n'est pas correctement associée au champ dans le schéma.
Correction :
@Resolver()
export class AppResolver {
@Query(() => [Task], { name: 'findAllTasks' })
async findAllTasks(): Promise<Task[]> {
return tasks;
}
}
Erreur 2 : Type 'Promise<Task[]>' is not assignable to type 'Task[]'
Cause : La fonction de résolution renvoie une promesse, mais le schéma attend un tableau direct.
Correction :
@Query(() => [Task])
async findAllTasks(): Promise<Task[]> {
return tasks;
}
Erreur 3 : Cannot resolve field "completed" on type "Task"
Cause :
Le champ completed n'est pas défini dans le schéma.
Correction :
type Task {
id: ID!
title: String!
completed: Boolean!
}
Pour aller plus loin
Piste 1 : Intégrer les souscriptions
Ajoutez des mutations et des souscriptions pour permettre la création instantanée de nouvelles tâches.
Piste 2 : Optimiser le chargement des données
Utilisez le système de résolution asynchrone GraphQL pour charger uniquement les données nécessaires.
Piste 3 : Authentification et autorisation
Ajoutez une couche d'authentification et d'autorisation à votre API pour protéger vos données sensibles.
Défi pratique
Créez un mini-projet réel qui implémente une application de gestion des utilisateurs avec des opérations CRUD. Incluez des fonctionnalités comme la création d'utilisateurs, l'affichage des informations utilisateur et la mise à jour des profils. Utilisez GraphQL pour récupérer uniquement les données nécessaires en un seul appel.