GraphQL et NestJS forment un excellent partenariat, vous offrant une base solide pour vos API et un cadre facile à utiliser pour construire des applications web évolutives. La combinaison est parfaite pour créer des applications prêtes à la production, et les deux sont des outils très pertinents dans l’écosystème technologique d’aujourd’hui.
Découvrez comment vous pouvez créer une API à l’aide de ces deux produits.
Qu’est-ce que GraphQL ?
GraphQL est un langage de requête et de manipulation de données que vous pouvez utiliser pour créer des API de manière plus précise et plus concise. GraphQL fournit une description complète et adéquate des données existant dans une API et permet au client d’obtenir les données exactes dont il a besoin.
GraphQL offre de nombreuses fonctionnalités qui font défaut aux API REST, qu’il s’agisse de requêtes de données précises ou d’outils de développement plus performants, tels que l’éditeur graphiql. Il permet également d’interroger plusieurs ressources par le biais d’une seule requête.
Qu’est-ce que NestJS ?
NestJS est un framework Node.js progressif que vous pouvez utiliser pour construire des applications côté serveur évolutives et efficaces. NestJS fournit de nombreux plugins, ainsi que des outils pour un développement rapide et facile, y compris le support GraphQL, GRPC, WebSockets, etc.
NestJS est bien connu dans l’écosystème pour sa structure de projet optimisée utilisant des modules, des contrôleurs, des services et des schémas. Son CLI intégré vous permet de créer une architecture API structurée. Vous pouvez utiliser les principes d’injection de dépendances pour contrôler la façon dont les parties d’une application communiquent entre elles.
Implémentation de GraphQL avec NestJS et MongoDB
Avant de construire une API avec NestJS et GraphQL, vous devez disposer des bonnes dépendances. Vous devez installer Node.js et NestJS, que vous pouvez installer en lançant npm i -g @nestjs/cli.
L’exemple qui suit est une application simple qui stocke des informations sur des livres. Exécutez la commande suivante dans votre terminal pour créer une nouvelle application NestJS :
nest new <app-name>
Naviguez vers le répertoire de l’application générée (<app-name> ;) et installez ses dépendances avec la commande suivante :
$ npm install --save @nestjs/config @nestjs/graphql graphql-tools graphql \
@nestjs/apollo apollo-server-express @nestjs/mongoose @types/graphql
Il existe deux approches principales pour construire des API GraphQL, à savoir :
- L’approche Schema-first : où vous décrivez l’API dans des fichiers de définition de schéma ou SDL, et NestJS génère des définitions Typescript basées sur ces fichiers.
- Approche « code-first » : où vous définissez des requêtes, des mutations et d’autres fonctionnalités GraphQL à l’aide de classes Typescript et de décorateurs, et NestJS génère des fichiers SDL basés sur ces requêtes.
L’exemple suivant décrit comment utiliser une approche « code-first ».
Tout d’abord, vous devez initialiser GraphQL dans votre fichier AppModule et le connecter à une base de données MongoDB :
// app.module.ts
import { Module } from '@nestjs/common';
import { GraphQLModule as NestGraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { join } from 'path';
import { MongooseModule } from '@nestjs/mongoose';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule, ConfigService } from '@nestjs/config';
import mongodbConfig from './config/mongodb.config';
@Module({
imports: [
ConfigModule.forRoot({
load: [mongodbConfig],
isGlobal: true
}),
NestGraphQLModule.forRootAsync<ApolloDriverConfig>({
driver: ApolloDriver,
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
installSubscriptionHandlers: true,
sortSchema: true,
playground: true,
debug: configService.get<boolean>("DEBUG"),
uploads: false,
}),
}),
MongooseModule.forRootAsync({
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
uri: configService.get('MONGO_URI')
})
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Ce module importe le module GraphQLModule de @nestjs/graphql et le fichier MongooseModule de @nestjs/mongoose qui permet de se connecter à MongoDB. Les autoSchemaFile spécifie l’emplacement du fichier de schéma généré, et la propriété sortSchema garantit que les champs sont classés par ordre alphabétique.
Voici ce que votre serveur MongoDB config devrait ressembler à ceci :
import { registerAs } from '@nestjs/config';
/**
* Mongo database connection config
*/
export default registerAs('mongodb', () => {
const {
MONGO_URI
} = process.env;
return {
uri: `${MONGO_URI}`,
};
});
Définition du schéma GraphQL
Après avoir configuré les connexions GraphQL et MongoDB, vous devez définir les requêtes et les mutations GraphQL pour générer un schéma (schema.gql) fichier.
Rédiger des requêtes
Dans le l’approche du code d’abord, vous créez un modèle à l’aide de l’outil ObjectType décorateur. Vous transformerez ultérieurement ce modèle en un type GraphQL.
Par exemple :
// book.model.ts
import { Field, ObjectType } from '@nestjs/graphql';
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
export type BookDocument = Book & Document;
@ObjectType()
@Schema()
export class Book {
@Field()
title: string;
@Field()
author: string;
@Field()
publishedDate: boolean;
}
export const BookSchema = SchemaFactory.createForClass(Book);
GraphQL, par défaut, ne peut pas utiliser les schémas créés. Pour les rendre fonctionnels, vous avez besoin d’un service de résolution qui contient les fonctions d’exécution des types GraphQL. Vous pouvez le faire avec la classe Résolveur décorateur.
// books.resolver.ts
import { Resolver, Query, Mutation, Args, ID } from '@nestjs/graphql';
import { Book } from './book.model';
import { BookService } from './books.service';
@Resolver(() => Book)
export class BookResolver {
constructor(private readonly bookService: BookService) { }
@Query(() => [Book])
async books(): Promise<Book[]> {
return this.bookService.findAll();
}
@Query(() => Book)
async book(@Args('id', { type: () => ID }) id: string): Promise<Book> {
return this.bookService.findOne(id);
}
}
Vous pouvez implémenter le décorateur BookService, importé ci-dessus, comme suit :
// books.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Book, BookDocument } from './book.model';
@Injectable()
export class BookService {
constructor(@InjectModel(Book.name) private bookModel: Model<BookDocument>) { }
async findAll(): Promise<Book[]> {
return this.bookModel.find().exec();
}
async findOne(id: string): Promise<Book> {
return this.bookModel.findById(id).exec();
}
}
Vous devez également ajouter le BookResolver à la liste des fournisseurs dans la section books.module.ts.
import { Module } from "@nestjs/common";
import { MongooseModule } from "@nestjs/mongoose";
import { BookService } from './books.service';
import { BookResolver } from './books.resolver';
import { Book, BookSchema } from './book.model';
@Module({
providers: [
BookService,
BookResolver
],
imports: [MongooseModule.forFeature([
{
name: Book.name,
schema: BookSchema,
},
]),
],
})
export class BooksModule {}
Travailler avec les mutations
Alors que vous utilisez une requête pour récupérer des données dans GraphQL, les mutations créent ou mettent à jour des données dans la base de données. Pour créer des mutations, vous devez accepter les données des utilisateurs. Les mutations Type d’entrée qui transforme une classe en un type d’entrée GraphQL, est très utile ici.
// book.input.ts
import { InputType, Field } from '@nestjs/graphql';
@InputType()
export class BookInput {
@Field()
title: string;
@Field()
author: string;
@Field()
publishedDate: boolean
}
Vous pouvez maintenant mettre à jour books.resolver.ts pour ressembler à ceci :
import { Resolver, Query, Mutation, Args, ID } from '@nestjs/graphql';
import { Book } from './book.model';
import { BookService } from './books.service';
import { BookInput } from './book.input';
@Resolver(() => Book)
export class BookResolver {
constructor(private readonly bookService: BookService) { }
@Mutation(() => Book)
async createBook(@Args('input') input: BookInput): Promise<Book> {
return this.bookService.create(input);
}
@Mutation(() => Book)
async updateBook(
@Args('id', { type: () => ID }) id: string,
@Args('input') input: BookInput,
): Promise<Book> {
return this.bookService.update(id, input);
}
@Mutation(() => Book)
async deleteBook(@Args('id', { type: () => ID }) id: string): Promise<Book> {
return this.bookService.delete(id);
}
}
Et books.service.ts comme ceci :
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Book, BookDocument } from './book.model';
@Injectable()
export class BookService {
constructor(@InjectModel(Book.name) private bookModel: Model<BookDocument>) { }
async create(book: Book): Promise<Book> {
const newBook = new this.bookModel(book);
return newBook.save();
}
async update(id: string, book: Book): Promise<Book> {
return this.bookModel.findByIdAndUpdate(id, book, { new: true }).exec();
}
async delete(id: string): Promise<Book> {
return this.bookModel.findByIdAndDelete(id).exec();
}
}
Le @Mutation marque une fonction comme un type de mutation et le décorateur @Args récupère toutes les entrées passées dans la fonction.
Enfin, vous devez importer l’élément Module Livres en AppModule pour le rendre fonctionnel. Vous devez également passer l’élément Module Livres à forRootAsync comme indiqué ci-dessous.
import { BooksModule } from './books/books.module';
/**
* other imports
*/
@Module({
imports: [
ConfigModule.forRoot({
load: [mongodbConfig],
isGlobal: true
}),
NestGraphQLModule.forRootAsync<ApolloDriverConfig>({
driver: ApolloDriver,
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
installSubscriptionHandlers: true,
sortSchema: true,
playground: true,
debug: configService.get<boolean>("DEBUG"),
uploads: false,
}),
}),
MongooseModule.forRootAsync({
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
uri: configService.get('MONGO_URI')
})
}),
BooksModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Vous pouvez tester le code en exécutant npm run start:dev dans votre terminal, et votre application devrait démarrer avec succès sur le port 3000.
Ouvrez localhost:3000/graphql dans votre navigateur pour afficher le fichier Graphiql qui permet de tester des requêtes et des mutations. Voici un exemple de requête :
Et voici un exemple de mutation :
Construire des API efficaces avec NestJS et GraphQL
Construire une API GraphQL dans NestJS avec MongoDB en utilisant Mongoose implique de définir un schéma pour l’API GraphQL, un schéma pour le modèle Mongoose, un service pour interagir avec la base de données, et un résolveur pour mapper les opérations GraphQL aux méthodes du service.
NestJS dispose de fonctionnalités intégrées pour la construction d’API, y compris des décorateurs pour définir les itinéraires, des gardes pour les protéger et des middlewares pour gérer les demandes et les réponses. Il prend également en charge d’autres bases de données comme PostgreSQL, MySQL et SQLite, ainsi que d’autres bibliothèques GraphQL comme Apollo et TypeGraphQL.