Introduction à l'API GraphQL en Rust
Le développement web moderne nécessite des API flexibles et performantes. GraphQL est un langage de requêtes qui permet aux clients d'obtenir exactement les données dont ils ont besoin, sans redondance. Rust, quant à lui, offre des performances élevées et une sécurité mémoire. Dans ce tutoriel, nous allons créer un serveur GraphQL simple mais complet en Rust en utilisant la bibliothèque async-graphql. Vous apprendrez à configurer le projet, définir les types de données, écrire des résolveurs et lancer le serveur. Cet article est destiné aux développeurs débutants connaissant les bases de Rust.
Configuration du projet et installation des dépendances
Pour commencer, créons un nouveau projet Rust avec Cargo :
cargo new graphql-rust-tutorialcd graphql-rust-tutorialOuvrez le fichier Cargo.toml et ajoutez les dépendances suivantes :
[dependencies]async-graphql = "7.0"async-graphql-actix-web = "7.0"actix-web = "4"serde = { version = "1.0", features = ["derive"] }tokio = { version = "1", features = ["full"] }Explication du choix des bibliothèques :
- async-graphql — la bibliothèque principale pour travailler avec GraphQL en Rust.
- async-graphql-actix-web — l'intégration avec le framework web Actix-web.
- actix-web — un serveur HTTP asynchrone populaire.
- serde — pour la sérialisation/désérialisation des données.
- tokio — le runtime asynchrone.
Exécutez cargo build pour vérifier que toutes les dépendances sont correctement installées.
Définition du schéma GraphQL et des résolveurs
Créons un modèle de données simple — un livre. Définissons la structure et les résolveurs pour les requêtes et les mutations.
Création du modèle de données
Créez le fichier src/models.rs et ajoutez le code suivant :
use async_graphql::*;
#[derive(SimpleObject)]pub struct Book { pub id: ID, pub title: String, pub author: String,}
#[derive(InputObject)]pub struct NewBook { pub title: String, pub author: String,}Ici, SimpleObject génère automatiquement le type GraphQL pour la structure Book, et InputObject pour les données d'entrée de la mutation.
Création du stockage et des résolveurs
Créons le fichier src/resolvers.rs qui contiendra la logique des requêtes et des mutations :
use std::sync::Mutex;use async_graphql::*;use crate::models::{Book, NewBook};
pub struct QueryRoot;
#[Object]impl QueryRoot { async fn books(&self) -> Vec<Book> { let storage = STORAGE.lock().unwrap(); storage.clone() }
async fn book(&self, id: ID) -> Option<Book> { let storage = STORAGE.lock().unwrap(); storage.iter().find(|b| b.id == id).cloned() }}
pub struct MutationRoot;
#[Object]impl MutationRoot { async fn add_book(&self, input: NewBook) -> Book { let mut storage = STORAGE.lock().unwrap(); let new_book = Book { id: ID::from(format!("book_{}", storage.len() + 1)), title: input.title, author: input.author, }; storage.push(new_book.clone()); new_book }}
lazy_static::lazy_static! { static ref STORAGE: Mutex<Vec<Book>> = Mutex::new(vec![]);}Notez que nous utilisons Mutex pour un accès thread-safe au stockage. Dans un projet réel, il est préférable d'utiliser une base de données.
Assemblage du schéma
Maintenant, rassemblons tout dans le fichier src/main.rs :
use actix_web::{web, App, HttpServer, HttpResponse, middleware};use async_graphql::{http::GraphiQLSource, EmptySubscription, Schema};use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse};use std::sync::Arc;
mod models;mod resol