API GraphQL avec Rust : tutoriel pratique étape par étape
GraphQL est un langage de requête puissant pour les API, qui permet aux clients de demander uniquement les données dont ils ont besoin. Combiné avec les performances et la sécurité de Rust, vous obtenez une solution idéale pour les services web à forte charge. Dans ce tutoriel, nous allons créer une API GraphQL complète à partir de zéro, en utilisant la crate async-graphql.
Pourquoi Rust ? Il offre un coût d'abstraction nul, une sécurité mémoire et une vitesse impressionnante. GraphQL sur Rust est plus rapide que ses équivalents en JavaScript ou Python, ce qui est crucial pour les systèmes de production.
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_apiAjoutons maintenant les dépendances nécessaires dans le fichier Cargo.toml :
[dependencies]async-graphql = "7.0"async-graphql-actix-web = "7.0"actix-web = "4"actix-cors = "0.7"serde = { version = "1.0", features = ["derive"] }tokio = { version = "1", features = ["full"] }Ces crates fournissent tout le nécessaire : async-graphql — le cœur de GraphQL, actix-web — le serveur web, serde — la sérialisation des données.
2. Création du modèle de données et du schéma
Définissons un modèle simple pour notre API — les livres. Créons les structures et le schéma GraphQL :
use async_graphql::*;use serde::{Serialize, Deserialize};
#[derive(Clone, Debug, Serialize, Deserialize, SimpleObject)]struct Book { id: ID, title: String, author: String, year: i32,}
#[derive(Clone, Debug, Serialize, Deserialize, InputObject)]struct BookInput { title: String, author: String, year: i32,}Notez les attributs #[derive(SimpleObject)] et #[derive(InputObject)]. Le premier génère automatiquement un type GraphQL pour la sortie, le second pour l'entrée de données.
3. Implémentation des résolveurs (Query et Mutation)
Les résolveurs sont des fonctions qui traitent les requêtes. Créons une structure de stockage et les résolveurs :
use std::sync::Mutex;
struct QueryRoot;
#[Object]impl QueryRoot { async fn books(&self, ctx: &Context<'_>) -> Vec<Book> { let storage = ctx.data_unchecked::<Mutex<Vec<Book>>>(); storage.lock().unwrap().clone() }
async fn book(&self, ctx: &Context<'_>, id: ID) -> Option<Book> { let storage = ctx.data_unchecked::<Mutex<Vec<Book>>>(); storage.lock().unwrap().iter().find(|b| b.id == id).cloned() }}
struct MutationRoot;
#[Object]impl MutationRoot { async fn add_book(&self, ctx: &Context<'_>, input: BookInput) -> Book { let mut storage = ctx.data_unchecked::<Mutex<Vec<Book>>>() .lock().unwrap(); let book = Book { id: ID::from(storage.len() + 1), title: input.title, author: input.author, year: input.year, }; storage.push(book.clone()); book }}Nous utilisons Mutex pour un accès thread-safe aux données. Dans un projet réel, vous connecteriez une base de données, mais pour ce tutoriel, c'est une solution idéale.
4. Assemblage et démarrage du serveur
La dernière étape consiste à tout assembler en un serveur fonctionnel. Créons le point d'entrée :
use actix_web::{guard, web, App, HttpServer, HttpRequest, HttpResponse};use actix_cors::Cors;use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse};
async fn index( schema: web::Data<Schema<QueryRoot, MutationRoot, EmptySubscription>>, req: GraphQLRequest,) -> GraphQLResponse { schema.execute(req.into_inner()).await.into()}
#[actix_web::main]async fn main() -> std::