مقدمة عن GraphQL API باستخدام Rust
يتطلب تطوير الويب الحديث واجهات برمجة تطبيقات مرنة وعالية الأداء. GraphQL هي لغة استعلام تتيح للعملاء الحصول على البيانات التي يحتاجونها بالضبط دون تكرار. من ناحية أخرى، يوفر Rust أداءً عاليًا وأمانًا للذاكرة. في هذا الدليل التعليمي، سننشئ خادم GraphQL بسيطًا ولكنه متكامل باستخدام Rust ومكتبة async-graphql. ستتعلم كيفية إعداد المشروع، تعريف أنواع البيانات، كتابة المحللات (Resolvers)، وتشغيل الخادم. المقالة موجهة للمطورين المبتدئين الذين لديهم معرفة أساسية بـ Rust.
إعداد المشروع وتثبيت التبعيات
للبدء، سننشئ مشروع Rust جديد باستخدام Cargo:
cargo new graphql-rust-tutorialcd graphql-rust-tutorialافتح ملف Cargo.toml وأضف التبعيات التالية:
[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"] }شرح اختيار المكتبات:
- async-graphql — المكتبة الرئيسية للعمل مع GraphQL في Rust.
- async-graphql-actix-web — التكامل مع إطار العمل الويب Actix-web.
- actix-web — خادم HTTP غير متزامن شائع.
- serde — لتسلسل/إلغاء تسلسل البيانات.
- tokio — بيئة تشغيل غير متزامنة.
نفذ الأمر cargo build للتأكد من تثبيت جميع التبعيات بشكل صحيح.
تعريف مخطط GraphQL والمحللات (Resolvers)
سننشئ نموذج بيانات بسيطًا - كتاب. سنعرف الهيكل والمحللات للاستعلامات والتحويرات (Mutations).
إنشاء نموذج البيانات
أنشئ ملف src/models.rs وأضف الكود التالي:
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,}هنا، يقوم SimpleObject تلقائيًا بإنشاء نوع GraphQL للهيكل Book، و InputObject لبيانات الإدخال الخاصة بالتحوير.
إنشاء المخزن والمحللات
سننشئ ملف src/resolvers.rs الذي سيحتوي على منطق الاستعلامات والتحويرات:
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![]);}لاحظ: نستخدم Mutex للوصول الآمن إلى المخزن عبر الخيوط. في مشروع حقيقي، من الأفضل استخدام قاعدة بيانات.
تجميع المخطط
الآن، سندمج كل شيء في ملف 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