Introduction à l'API GraphQL en Rust
Le développement web moderne exige des performances élevées et une flexibilité dans le traitement des données. Rust est un langage de programmation système, réputé pour sa rapidité et sa sécurité mémoire, tandis que GraphQL est un puissant langage de requêtes permettant aux clients d'obtenir uniquement les données nécessaires. La combinaison de ces technologies offre des API incroyablement rapides et pratiques.
Dans ce tutoriel, nous allons créer une API GraphQL complète en Rust en utilisant les célèbres crates Juniper (pour l'implémentation de GraphQL) et Actix-web (pour le serveur HTTP). Vous apprendrez à configurer le projet, définir le schéma, implémenter les résolveurs et traiter les requêtes.
Cet article est destiné aux développeurs déjà familiers avec les bases de Rust et les concepts fondamentaux des API web. Nous utiliserons l'édition Rust 2021 et les dernières versions stables des dépendances.
Étape 1 : Configuration du projet et des dépendances
Commençons par créer un nouveau projet Rust. Ouvrez un terminal et exécutez :
cargo new graphql-rust-apicd graphql-rust-apiEnsuite, ouvrez le fichier Cargo.toml et ajoutez les dépendances suivantes :
[dependencies]actix-web = "4"actix-rt = "2"juniper = "0.16"serde = { version = "1.0", features = ["derive"] }tokio = { version = "1", features = ["full"] }- actix-web — un framework web asynchrone pour Rust.
- juniper — une bibliothèque pour implémenter GraphQL en Rust.
- serde — pour la sérialisation/désérialisation des données.
- tokio — le runtime asynchrone nécessaire pour Actix.
Après avoir mis à jour les dépendances, exécutez cargo build pour vérifier que tout s'installe correctement.
Étape 2 : Définition du modèle de données et du schéma GraphQL
Pour l'exemple, créons un modèle simple "Livre" avec les champs : id, titre, auteur et année de publication. Créons le fichier src/models.rs :
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]pub struct Book { pub id: i32, pub title: String, pub author: String, pub year: i32,}Définissons maintenant le schéma GraphQL. Créons src/graphql.rs :
use juniper::{FieldResult, GraphQLObject, GraphQLInputObject, RootNode};use crate::models::Book;
// Type GraphQL pour le livre (sortie)#[derive(GraphQLObject)]#[graphql(description = "Un livre")]pub struct BookGQL { pub id: i32, pub title: String, pub author: String, pub year: i32,}
// Type d'entrée pour la création d'un livre#[derive(GraphQLInputObject)]#[graphql(description = "Données d'entrée pour un nouveau livre")]pub struct NewBook { pub title: String, pub author: String, pub year: i32,}
// "Base de données" en mémoireuse std::sync::Mutex;lazy_static::lazy_static! { static ref BOOKS: Mutex<Vec<Book>> = Mutex::new(vec![ Book { id: 1, title: "Rust en action".to_string(), author: "Tim McNamara".to_string(), year: 2021 }, Book { id: 2, title: "Programmation en Rust".to_string(), author: "Jim Blandy".to_string(), year: 2020 }, ]);}
// Requête racine (Query)pub struct QueryRoot;
#[juniper::object]impl QueryRoot { fn books() -> FieldResult<Vec<BookGQL>> { let books = BOOKS.lock().unwrap(); Ok(books.iter().map(|b| BookGQL { id: b.id, title: b.title.clone(), author: b.author.clone(), year: b.year, }).collect()) }
fn book(id: i32) -> FieldResult<BookGQL> { let books = BOOKS.lock().unwrap(); let book = books.iter().find(|b| b.id == id) .ok_or_else(|| juniper::FieldError::new("Livre non trouvé", graphql_value!({"code": "NOT_FOUND"})))?; Ok(BookGQL { id: book.id,
```