GraphQL est une manière puissante et efficace de concevoir des API. Le client interroge l’API comme il le ferait pour une base de données, et l’API ne renvoie que les données demandées, ce qui réduit souvent la taille des réponses et améliore les temps de réponse. Des temps de réponse faibles sont essentiels aujourd’hui.
Associé à MongoDB, un API GraphQL offre non seulement des réponses rapides, mais aussi un format de données cohérent de bout en bout.
Imaginez ceci : votre client exécute une requête GraphQL dont la structure rappelle le JSON. Lorsque les données arrivent dans votre application — disons en TypeScript dans cet exemple — vous manipulez un format proche de JSON dans l’application. Plus encore, avec MongoDB, les données échangées avec la base sont elles aussi proches de JSON. Résultat : une expérience de données cohérente, sans compromettre la performance. Moins de temps passé à transformer et formater les données, plus de temps pour soigner l’expérience utilisateur de votre application, plutôt que de vous soucier de la base et des outils.
Dans ce tutoriel, nous allons voir à quel point il est simple d’utiliser MongoDB dans une API GraphQL, cette fois avec TypeScript.
Les prérequis
Pour ce tutoriel, assurez-vous d’avoir les éléments suivants avant de commencer :
Un cluster MongoDB Atlas
Node.js 22+
Le pilote MongoDB Node.js 6.x
Nous ne ferons rien de particulièrement complexe côté base dans ce tutoriel ; n’importe quel niveau de cluster MongoDB conviendra, y compris l’offre gratuite. En revanche, nous n’aborderons pas le provisionnement d’un cluster MongoDB Atlas ici. L’hypothèse est que vous avez déjà configuré les règles utilisateurs et réseau. Besoin d’un coup de pouce ? Consultez la documentation Get Started With Atlas sur le sujet.
Vous aurez besoin d’une application Node.js. Si vous le souhaitez, exécutez les commandes suivantes :
mkdir graphql_example
cd graphql_example
npm init -yPropulsé par
Ces commandes créent un nouveau répertoire de projet et un fichier package.json minimal. Nous utiliserons TypeScript, Express Framework, MongoDB et Apollo dans ce projet. Installez-les avec les commandes suivantes :
npminstall @apollo/server@4 body-parser cors dotenv express@4 graphql mongodb
npminstall @types/cors @types/express@4 @types/node tsx typescript --save-devPropulsé par
Ces commandes installent les dépendances ainsi que les définitions de types nécessaires à TypeScript.
Dernier prérequis : un fichier de configuration TypeScript. À la racine du projet, créez un fichier tsconfig.json avec le JSON suivant :
{"compilerOptions":{"target":"ES2020","module":"commonjs","lib":["ES2020"],"outDir":"./dist","rootDir":"./src","strict":true,"esModuleInterop":true,"skipLibCheck":true,"forceConsistentCasingInFileNames":true,"resolveJsonModule":true,"moduleResolution":"node","declaration":true,"declarationMap":true,"sourceMap":true},"include":["src/**/*"],"exclude":["node_modules","dist"]}Propulsé par
À tout moment, si vous souhaitez voir le projet complet ou l’essayer, vous pouvez le récupérer sur GitHub.
Poser les bases avec Apollo, Express et les dépendances MongoDB
Côté structure de projet, nous allons utiliser les éléments suivants :
src/database/connection.ts
src/database/userService.ts
src/resolvers/index.ts
src/schema/typeDefs.ts
src/types/index.ts
src/index.ts
.env
Le fichier src/index.ts contiendra tout l’amorçage d’Express Framework et d’Apollo : démarrage du serveur, définition des points de terminaison GraphQL, etc. Le fichier src/types/index.ts regroupera nos définitions TypeScript pour manipuler le modèle de données de la base dans l’application. À ne pas confondre avec src/schema/typeDefs.ts qui, bien que similaire, porte sur les définitions de types GraphQL.
Il nous reste donc les répertoires database et resolvers. L’essentiel de la logique applicative et d’accès aux données s’y trouvera, et nous allons y venir rapidement.
Concentrons-nous d’abord sur la mise en place du serveur dans src/index.ts :
Pour commencer, nous importons toutes les dépendances du projet. Ensuite, nous configurons le serveur Express et le middleware Apollo.
const server =newApolloServer({
typeDefs,
resolvers,});Propulsé par
Nous n’avons pas encore renseigné src/schema/typeDefs.ts ni src/resolvers/index.ts, mais cette étape prépare le terrain. Le serveur ne démarrera pas sans erreurs tant que nous n’aurons pas comblé les manques dans ces fichiers.
À noter : cet exemple autorise les requêtes depuis toutes les origines, à cause de :
C’est pratique en développement, mais en production, limitez-vous aux origines nécessaires.
Interagir avec MongoDB depuis l’application TypeScript/Node.js
Nous avons la base du serveur Express et Apollo, mais la connexion MongoDB n’est pas encore configurée dans l’application. Avant de passer au code, ajoutons les informations de connexion dans le fichier .env :
MONGODB_URI="mongodb+srv://<USERNAME>:<PASSWORD>@<HOST>/?appName=devrel-graphql"
DB_NAME="graphql_example"Propulsé par
Remplacez <USERNAME>, <PASSWORD> et <HOST> par les informations de votre cluster MongoDB Atlas, disponibles dans votre tableau de bord MongoDB Atlas.
Ensuite, mettons en place la logique de connexion à la base pour l’application. Dans le fichier **src/database/connection.ts**, ajoutez le code suivant :
import{ MongoClient, Db }from'mongodb';let db: Db |null=null;let client: MongoClient |null=null;exportasyncfunctionconnectToDatabase():Promise<Db>{if(db){return db;}try{constMONGODB_URI= process.env.MONGODB_URI;if(!MONGODB_URI){thrownewError('MONGODB_URI is not set');}constDB_NAME= process.env.DB_NAME||'graphql_example';constAPP_NAME= process.env.APP_NAME||'devrel-graphql';console.log('Connecting to MongoDB...');
client =newMongoClient(MONGODB_URI,{ appName:APP_NAME});await client.connect();
db = client.db(DB_NAME);console.log(Successfully connected to MongoDB database: ${DB_NAME});return db;}catch(error){console.error('MongoDB connection error:', error);throw error;}}exportfunctiongetDatabase(): Db {if(!db){thrownewError('Database not initialized. Call connectToDatabase() first.');}return db;}exportasyncfunctioncloseDatabase():Promise<void>{if(client){await client.close();
db =null;
client =null;console.log('MongoDB connection closed');}}Propulsé par
L’idée ici est de créer une connexion singleton. Il existe bien d’autres approches pour intégrer MongoDB, mais c’est celle que nous retenons pour cette application.
La fonction connectToDatabase lit les informations de connexion depuis le fichier **.env**, se connecte et renvoie la base définie. En cas d’échec, l’erreur est capturée et relancée. Vous ne devriez l’appeler qu’au démarrage du serveur, comme montré précédemment.
La fonction getDatabase sera utilisée à chaque requête vers l’application. Elle récupère l’instance courante de la connexion.
Enfin, pour rester propre, la fonction closeDatabase ferme la connexion lorsque le serveur s’arrête. À vous de voir si vous souhaitez l’inclure, mais c’est une bonne pratique.
Avant de passer à la logique base de données, définissons nos types TypeScript pour nos Data Transfer Objects (DTO) :
Ces interfaces, placées dans **src/types/index.ts**, sont gérées par notre API, mais la logique base doit aussi en tenir compte. Vous allez le voir tout de suite.
Passons aux interactions réelles avec la base pour créer, lire, mettre à jour et supprimer (CRUD). Tout se passe dans src/database/userService.ts. L’idée : les resolvers GraphQL gèrent la logique applicative et appellent les fonctions de ce service, qui lui s’occupe de la base.
Dans le fichier src/database/userService.ts, nous avons le code suivant :
interfaceUserDocument{
_id: ObjectId;
name:string;
email:string;
age?:number;
createdAt: Date;}Propulsé par
Elle ressemble à notre interface User, sans être identique. UserDocument reflète plus fidèlement le modèle de la base. Dans la base, nous utilisons ObjectId, tandis que l’application manipule des chaînes. Dans MongoDB, l’identifiant est _id et non id, comme dans User.
Entrons dans la classe UserService.
Après avoir obtenu un handle sur la collection via le constructor, nous avons la série de fonctions suivante :
toUser
getAllUsers
getUserById
createUser
updateUser
deleteUser
createIndexes
La fonction toUser sert d’aide à la conversion entre User et UserDocument. La base échange des UserDocument ; l’application envoie et reçoit des User.
asynccreateIndexes():Promise<void>{awaitthis.collection.createIndex({ email:1},{ unique:true});awaitthis.collection.createIndex({ createdAt:-1});console.log('Database indexes created');}Propulsé par
La fonction createIndexes crée quelques index d’exemple potentiellement utiles. Par défaut, seul le champ _id est indexé, ce qui suffit pour une démo avec peu de documents. Mais à mesure que l’application grandit, ne négligez pas la création d’index adaptés à vos données et à vos requêtes. Les deux ci-dessus ne sont que des exemples.
Voyons chaque fonction de service, en commençant par getAllUsers :
asyncgetAllUsers():Promise<User[]>{const users =awaitthis.collection.find().toArray();return users.map(doc =>this.toUser(doc));}Propulsé par
Cette fonction récupère tous les documents de la collection, car aucun filtre n’est passé à find. Les résultats sont convertis via toUser puis renvoyés à l’application, c’est-à-dire à nos resolvers Apollo.
asyncgetUserById(id:string):Promise<User |null>{try{const doc =awaitthis.collection.findOne({ _id:newObjectId(id)});return doc ?this.toUser(doc):null;}catch(error){returnnull;}}Propulsé par
La fonction getByUserId est similaire, mais utilise findOne avec un critère de filtre : nous voulons un utilisateur unique par _id. Comme le champ id de l’interface User est une chaîne, nous l’engloberons dans ObjectId pour l’interroger dans MongoDB.
Passons à la création de données dans MongoDB avec createUser :
asynccreateUser(input: CreateUserInput):Promise<User>{const newUser: Omit<UserDocument,'_id'>={
name: input.name,
email: input.email,
age: input.age,
createdAt:newDate(),};const result =awaitthis.collection.insertOne(newUser as UserDocument);const createdUser =awaitthis.collection.findOne({ _id: result.insertedId });if(!createdUser){thrownewError('Failed to create user');}returnthis.toUser(createdUser);}Propulsé par
Après l’appel à insertOne, nous utilisons l’insertedId renvoyé pour relire le document. En GraphQL, lorsqu’une création a lieu, il est courant de pouvoir récupérer l’objet créé dans la même mutation.
La fonction updateUser diffère un peu :
asyncupdateUser(input: UpdateUserInput):Promise<User |null>{try{const updateFields: Partial<Omit<UserDocument,'_id'>>={};if(input.name !==undefined) updateFields.name = input.name;if(input.email !==undefined) updateFields.email = input.email;if(input.age !==undefined) updateFields.age = input.age;const result =awaitthis.collection.findOneAndUpdate({ _id:newObjectId(input.id)},{ $set: updateFields },{ returnDocument:'after'});return result ?this.toUser(result):null;}catch(error){returnnull;}}Propulsé par
Ici, nous autorisons uniquement la mise à jour des champs name, email et age. Tous sont optionnels ; nous ne les ajoutons au $set que s’ils sont présents.
Cela nous mène à findOneAndUpdate.
Le premier paramètre est le filtre : nous mettons à jour uniquement si l’_id correspond. En cas de correspondance, l’opérateur $set remplace ou crée les champs présents dans updateFields. Les champs absents ne sont pas modifiés. Enfin, comme nous « trouvons et mettons à jour », nous fixons returnDocument à after pour obtenir le document modifié en réponse. Avec updateOne, nous n’aurions reçu que des infos d’opération, pas les données.
La dernière opération CRUD consiste à supprimer des données via deleteUser :
asyncdeleteUser(id:string):Promise<boolean>{try{const result =awaitthis.collection.deleteOne({ _id:newObjectId(id)});return result.deletedCount >0;}catch(error){returnfalse;}}Propulsé par
Cette fonction deleteOne rappelle findOne : on filtre par _id. La différence : si correspondance il y a — au maximum un document — il est supprimé.
À ce stade, la logique base est prête. Pour finaliser, connectons-la depuis **src/index.ts** :
// Previous imports here...import{ connectToDatabase, closeDatabase }from'./database/connection';import{ UserService }from'./database/userService';asyncfunctionstartServer(){try{awaitconnectToDatabase();const userService =newUserService();await userService.createIndexes();// The rest of the file here...}Propulsé par
Dans l’extrait ci-dessus, l’essentiel de src/index.ts est omis. L’objectif est de montrer l’appel à connectToDatabase et la création des index. Concrètement, placez ces trois lignes en haut de votre fonction startServer.
Nous pouvons maintenant nous concentrer sur la logique de l’API.
Définir les types GraphQL, les resolvers et la logique métier
Nous avons défini les types TypeScript pour la base et pour la circulation des données dans l’application. En revanche, nous n’avons pas encore créé les définitions de types GraphQL, qui décrivent ce que l’utilisateur peut manipuler via notre API.
Dans le fichier src/schema/typeDefs.ts, ajoutez :
exportconst typeDefs = #graphql
typeUser{
id:ID!
name: String!
email: String!
age: Int
createdAt: String!}typeQuery{
users:[User!]!user(id:ID!): User
}typeMutation{createUser(name: String!, email: String!, age: Int): User!updateUser(id:ID!, name: String, email: String, age: Int): User
deleteUser(id:ID!): Boolean!};Propulsé par
Le type User définit ce que le client est autorisé à demander dans une requête. Ici, il reflète l’interface User, mais ce n’est pas obligatoire. Par exemple, votre interface pourrait contenir un mot de passe que vous ne renverriez jamais au client.
Le type Query déclare les requêtes possibles dans notre API. Chaque requête renvoie un User ou un tableau de User, avec la logique que vous souhaitez intercaler.
Le type Mutation est similaire, mais dédié aux opérations qui modifient les données.
Avec les types GraphQL en place, passons à la logique des resolvers. Dans src/resolvers/index.ts, ajoutez :
import{ UserService }from'../database/userService';import{ User, CreateUserInput, UpdateUserInput }from'../types';exportconst resolvers ={
Query:{
users:async():Promise<User[]>=>{const userService =newUserService();returnawait userService.getAllUsers();},
user:async(_:unknown,{ id }:{ id:string}):Promise<User |null>=>{const userService =newUserService();returnawait userService.getUserById(id);},},
Mutation:{
createUser:async(
_:unknown,{ name, email, age }: CreateUserInput
):Promise<User>=>{const userService =newUserService();returnawait userService.createUser({ name, email, age });},
updateUser:async(
_:unknown,{ id, name, email, age }: UpdateUserInput
):Promise<User |null>=>{const userService =newUserService();returnawait userService.updateUser({ id, name, email, age });},
deleteUser:async(_:unknown,{ id }:{ id:string}):Promise<boolean>=>{const userService =newUserService();returnawait userService.deleteUser(id);},},};Propulsé par
Ces resolvers prolongent simplement ce que nous avons défini dans les types pour les requêtes et mutations.
Regardez la requête users :
users:async():Promise<User[]>=>{const userService =newUserService();returnawait userService.getAllUsers();},Propulsé par
Cette requête GraphQL s’appuie sur UserService pour récupérer tous les utilisateurs. Le User[] est renvoyé au client, mais uniquement les champs demandés et autorisés par les définitions de types GraphQL.
Dans cet exemple, d’autres champs existent, mais le client ne souhaite que name et email. Cela fonctionne grâce aux définitions de types GraphQL côté client. Apollo se charge du reste.
Les mêmes principes s’appliquent à chaque requête et mutation dans **src/resolvers/index.ts**.
createUser:async(
_:unknown,{ name, email, age }: CreateUserInput
):Promise<User>=>{const userService =newUserService();returnawait userService.createUser({ name, email, age });},Propulsé par
Pour la mutation createUser, le client peut transmettre name, email et age. Le resolver fait de même et appelle la fonction createUser du service base. La réponse renvoyée dépend des champs demandés par le client.
Construire et exécuter l’API GraphQL
À ce stade, votre API GraphQL est en bonne voie. Plusieurs solutions existent pour construire et exécuter l’application.
Par exemple, ajoutez ceci à votre **package.json** :
"scripts":{"build":"tsc","start":"node dist/index.js","dev":"tsx src/index.ts","dev:watch":"tsx watch src/index.ts","watch":"tsc -w"},Propulsé par
Avec cette approche, exécutez npm run build pour construire le projet et npm run start pour le lancer. Le port par défaut est 3000 ; vous pouvez le tester via cURL ou l’outil de votre choix.
Conclusion
Vous venez de voir comment créer une API GraphQL avec TypeScript et MongoDB comme base de données. Même si l’exemple est volontairement simple, nous avons mis en place des requêtes et mutations pour réaliser des opérations CRUD. MongoDB s’associe parfaitement avec une API GraphQL, car le format de données reste semblable de bout en bout. Par exemple, le client envoie une requête au format proche de JSON. Les données véhiculées nécessitent peu de transformations, et il en va de même lors des échanges avec la base. L’expérience est fluide : vous pouvez vous concentrer sur l’UX plutôt que sur la manipulation des données pour satisfaire la base.
GraphQL est intéressant car vous interrogez une API comme une base de données, en ne demandant que ce dont vous avez besoin. C’est plus léger et efficace côté client.
Apollo est-il la seule option pour GraphQL ?⌄
Apollo facilite grandement la création d’API GraphQL, mais ce n’est pas la seule option. Les autres solutions sortent toutefois du cadre de ce tutoriel.
Pourquoi associer MongoDB et GraphQL ?⌄
Les deux reposent sur des formats proches de JSON, ce qui évite de convertir sans cesse entre des structures différentes lorsque l’information circule de la base à l’API puis au client.
À quoi sert un resolver ?⌄
Un resolver est une fonction qui indique à GraphQL comment récupérer ou modifier les données pour un champ donné de votre schéma.
Quelle est la différence entre une query et une mutation ?⌄
Les queries servent à lire/récupérer des données (comme des requêtes GET), tandis que les mutations servent à créer, mettre à jour ou supprimer des données (comme POST, PUT, DELETE).
Auteur
Nic Raboy
Nic Raboy est responsable des relations avec les développeurs chez MongoDB, où il dirige une équipe de développeurs Python, Java, C# et PHP qui créent un contenu génial pour aider les développeurs à réussir à inclure MongoDB dans leurs projets. Il a de l'expérience avec Golang et JavaScript et écrit souvent sur ses aventures de développement.
Apprenez l'essentiel de l'architecture d'un entrepôt de données, des composants clés aux meilleures pratiques, pour construire un système de données évolutif et efficace !
Apprenez à utiliser JSON en Python, notamment la sérialisation, la désérialisation, le formatage, l'optimisation des performances, la gestion des API, ainsi que les limites et les alternatives de JSON.
GraphQL est une manière puissante et efficace de concevoir des API. Le client interroge l’API comme il le ferait pour une base de données, et l’API ne renvoie que les données demandées, ce qui réduit souvent la taille des réponses et améliore les temps de réponse. Des temps de réponse faibles sont essentiels aujourd’hui.
Associé à MongoDB, un API GraphQL offre non seulement des réponses rapides, mais aussi un format de données cohérent de bout en bout.
Imaginez ceci : votre client exécute une requête GraphQL dont la structure rappelle le JSON. Lorsque les données arrivent dans votre application — disons en TypeScript dans cet exemple — vous manipulez un format proche de JSON dans l’application. Plus encore, avec MongoDB, les données échangées avec la base sont elles aussi proches de JSON. Résultat : une expérience de données cohérente, sans compromettre la performance. Moins de temps passé à transformer et formater les données, plus de temps pour soigner l’expérience utilisateur de votre application, plutôt que de vous soucier de la base et des outils.
Dans ce tutoriel, nous allons voir à quel point il est simple d’utiliser MongoDB dans une API GraphQL, cette fois avec TypeScript.
Les prérequis
Pour ce tutoriel, assurez-vous d’avoir les éléments suivants avant de commencer :
Nous ne ferons rien de particulièrement complexe côté base dans ce tutoriel ; n’importe quel niveau de cluster MongoDB conviendra, y compris l’offre gratuite. En revanche, nous n’aborderons pas le provisionnement d’un cluster MongoDB Atlas ici. L’hypothèse est que vous avez déjà configuré les règles utilisateurs et réseau. Besoin d’un coup de pouce ? Consultez la documentation Get Started With Atlas sur le sujet.
Vous aurez besoin d’une application Node.js. Si vous le souhaitez, exécutez les commandes suivantes :
Ces commandes créent un nouveau répertoire de projet et un fichier package.json minimal. Nous utiliserons TypeScript, Express Framework, MongoDB et Apollo dans ce projet. Installez-les avec les commandes suivantes :
Ces commandes installent les dépendances ainsi que les définitions de types nécessaires à TypeScript.
Dernier prérequis : un fichier de configuration TypeScript. À la racine du projet, créez un fichier tsconfig.json avec le JSON suivant :
À tout moment, si vous souhaitez voir le projet complet ou l’essayer, vous pouvez le récupérer sur GitHub.
Poser les bases avec Apollo, Express et les dépendances MongoDB
Côté structure de projet, nous allons utiliser les éléments suivants :
Le fichier src/index.ts contiendra tout l’amorçage d’Express Framework et d’Apollo : démarrage du serveur, définition des points de terminaison GraphQL, etc. Le fichier src/types/index.ts regroupera nos définitions TypeScript pour manipuler le modèle de données de la base dans l’application. À ne pas confondre avec src/schema/typeDefs.ts qui, bien que similaire, porte sur les définitions de types GraphQL.
Il nous reste donc les répertoires database et resolvers. L’essentiel de la logique applicative et d’accès aux données s’y trouvera, et nous allons y venir rapidement.
Concentrons-nous d’abord sur la mise en place du serveur dans src/index.ts :
Nous enrichirons ce fichier au fil de l’eau.
Pour commencer, nous importons toutes les dépendances du projet. Ensuite, nous configurons le serveur Express et le middleware Apollo.
Nous n’avons pas encore renseigné src/schema/typeDefs.ts ni src/resolvers/index.ts, mais cette étape prépare le terrain. Le serveur ne démarrera pas sans erreurs tant que nous n’aurons pas comblé les manques dans ces fichiers.
À noter : cet exemple autorise les requêtes depuis toutes les origines, à cause de :
C’est pratique en développement, mais en production, limitez-vous aux origines nécessaires.
Interagir avec MongoDB depuis l’application TypeScript/Node.js
Nous avons la base du serveur Express et Apollo, mais la connexion MongoDB n’est pas encore configurée dans l’application. Avant de passer au code, ajoutons les informations de connexion dans le fichier .env :
Remplacez
<USERNAME>,<PASSWORD>et<HOST>par les informations de votre cluster MongoDB Atlas, disponibles dans votre tableau de bord MongoDB Atlas.Ensuite, mettons en place la logique de connexion à la base pour l’application. Dans le fichier **src/database/connection.ts**, ajoutez le code suivant :
L’idée ici est de créer une connexion singleton. Il existe bien d’autres approches pour intégrer MongoDB, mais c’est celle que nous retenons pour cette application.
La fonction
connectToDatabaselit les informations de connexion depuis le fichier **.env**, se connecte et renvoie la base définie. En cas d’échec, l’erreur est capturée et relancée. Vous ne devriez l’appeler qu’au démarrage du serveur, comme montré précédemment.La fonction
getDatabasesera utilisée à chaque requête vers l’application. Elle récupère l’instance courante de la connexion.Enfin, pour rester propre, la fonction
closeDatabaseferme la connexion lorsque le serveur s’arrête. À vous de voir si vous souhaitez l’inclure, mais c’est une bonne pratique.Avant de passer à la logique base de données, définissons nos types TypeScript pour nos Data Transfer Objects (DTO) :
Ces interfaces, placées dans **src/types/index.ts**, sont gérées par notre API, mais la logique base doit aussi en tenir compte. Vous allez le voir tout de suite.
Passons aux interactions réelles avec la base pour créer, lire, mettre à jour et supprimer (CRUD). Tout se passe dans src/database/userService.ts. L’idée : les resolvers GraphQL gèrent la logique applicative et appellent les fonctions de ce service, qui lui s’occupe de la base.
Dans le fichier src/database/userService.ts, nous avons le code suivant :
Première chose à noter : l’interface suivante :
Elle ressemble à notre interface
User, sans être identique.UserDocumentreflète plus fidèlement le modèle de la base. Dans la base, nous utilisonsObjectId, tandis que l’application manipule des chaînes. Dans MongoDB, l’identifiant est_idet nonid, comme dansUser.Entrons dans la classe
UserService.Après avoir obtenu un handle sur la collection via le
constructor, nous avons la série de fonctions suivante :La fonction
toUsersert d’aide à la conversion entreUseretUserDocument. La base échange desUserDocument; l’application envoie et reçoit desUser.La fonction
createIndexescrée quelques index d’exemple potentiellement utiles. Par défaut, seul le champ_idest indexé, ce qui suffit pour une démo avec peu de documents. Mais à mesure que l’application grandit, ne négligez pas la création d’index adaptés à vos données et à vos requêtes. Les deux ci-dessus ne sont que des exemples.Voyons chaque fonction de service, en commençant par
getAllUsers:Cette fonction récupère tous les documents de la collection, car aucun filtre n’est passé à
find. Les résultats sont convertis viatoUserpuis renvoyés à l’application, c’est-à-dire à nos resolvers Apollo.La fonction
getByUserIdest similaire, mais utilisefindOneavec un critère de filtre : nous voulons un utilisateur unique par_id. Comme le champidde l’interfaceUserest une chaîne, nous l’engloberons dansObjectIdpour l’interroger dans MongoDB.Passons à la création de données dans MongoDB avec
createUser:Après l’appel à
insertOne, nous utilisons l’insertedIdrenvoyé pour relire le document. En GraphQL, lorsqu’une création a lieu, il est courant de pouvoir récupérer l’objet créé dans la même mutation.La fonction
updateUserdiffère un peu :Ici, nous autorisons uniquement la mise à jour des champs
name,emailetage. Tous sont optionnels ; nous ne les ajoutons au$setque s’ils sont présents.Cela nous mène à
findOneAndUpdate.Le premier paramètre est le filtre : nous mettons à jour uniquement si l’
_idcorrespond. En cas de correspondance, l’opérateur$setremplace ou crée les champs présents dansupdateFields. Les champs absents ne sont pas modifiés. Enfin, comme nous « trouvons et mettons à jour », nous fixonsreturnDocumentàafterpour obtenir le document modifié en réponse. AvecupdateOne, nous n’aurions reçu que des infos d’opération, pas les données.La dernière opération CRUD consiste à supprimer des données via
deleteUser:Cette fonction
deleteOnerappellefindOne: on filtre par_id. La différence : si correspondance il y a — au maximum un document — il est supprimé.À ce stade, la logique base est prête. Pour finaliser, connectons-la depuis **src/index.ts** :
Dans l’extrait ci-dessus, l’essentiel de src/index.ts est omis. L’objectif est de montrer l’appel à
connectToDatabaseet la création des index. Concrètement, placez ces trois lignes en haut de votre fonctionstartServer.Nous pouvons maintenant nous concentrer sur la logique de l’API.
Définir les types GraphQL, les resolvers et la logique métier
Nous avons défini les types TypeScript pour la base et pour la circulation des données dans l’application. En revanche, nous n’avons pas encore créé les définitions de types GraphQL, qui décrivent ce que l’utilisateur peut manipuler via notre API.
Dans le fichier src/schema/typeDefs.ts, ajoutez :
Le type
Userdéfinit ce que le client est autorisé à demander dans une requête. Ici, il reflète l’interfaceUser, mais ce n’est pas obligatoire. Par exemple, votre interface pourrait contenir un mot de passe que vous ne renverriez jamais au client.Le type
Querydéclare les requêtes possibles dans notre API. Chaque requête renvoie unUserou un tableau deUser, avec la logique que vous souhaitez intercaler.Le type
Mutationest similaire, mais dédié aux opérations qui modifient les données.Avec les types GraphQL en place, passons à la logique des resolvers. Dans src/resolvers/index.ts, ajoutez :
Ces resolvers prolongent simplement ce que nous avons défini dans les types pour les requêtes et mutations.
Regardez la requête
users:Cette requête GraphQL s’appuie sur
UserServicepour récupérer tous les utilisateurs. LeUser[]est renvoyé au client, mais uniquement les champs demandés et autorisés par les définitions de types GraphQL.Par exemple, si le client envoie cette requête :
Dans cet exemple, d’autres champs existent, mais le client ne souhaite que
nameetemail. Cela fonctionne grâce aux définitions de types GraphQL côté client. Apollo se charge du reste.Les mêmes principes s’appliquent à chaque requête et mutation dans **src/resolvers/index.ts**.
Pour la mutation
createUser, le client peut transmettrename,emailetage. Le resolver fait de même et appelle la fonctioncreateUserdu service base. La réponse renvoyée dépend des champs demandés par le client.Construire et exécuter l’API GraphQL
À ce stade, votre API GraphQL est en bonne voie. Plusieurs solutions existent pour construire et exécuter l’application.
Par exemple, ajoutez ceci à votre **package.json** :
Avec cette approche, exécutez
npm run buildpour construire le projet etnpm run startpour le lancer. Le port par défaut est 3000 ; vous pouvez le tester via cURL ou l’outil de votre choix.Conclusion
Vous venez de voir comment créer une API GraphQL avec TypeScript et MongoDB comme base de données. Même si l’exemple est volontairement simple, nous avons mis en place des requêtes et mutations pour réaliser des opérations CRUD. MongoDB s’associe parfaitement avec une API GraphQL, car le format de données reste semblable de bout en bout. Par exemple, le client envoie une requête au format proche de JSON. Les données véhiculées nécessitent peu de transformations, et il en va de même lors des échanges avec la base. L’expérience est fluide : vous pouvez vous concentrer sur l’UX plutôt que sur la manipulation des données pour satisfaire la base.
Si vous avez rencontré des difficultés, consultez le projet finalisé sur GitHub.