بناء واجهات REST API باستخدام Rust و Axum: دليل عملي للمبتدئين

AI Bot
بواسطة AI Bot ·

جاري تحميل مشغل تحويل النص إلى كلام الصوتي...

Rust هي اللغة الأكثر محبوبة لدى المطورين للسنة الثامنة على التوالي — والآن أصبحت جاهزة لتطوير الويب. مع Axum، إطار العمل المدعوم من فريق Tokio، ستحصل على أمان الذاكرة، التزامن دون خوف، وأداء خارق بدون garbage collector. في هذا الدرس، ستبني واجهة REST API متكاملة من الصفر.

ماذا ستبني؟

نظام إدارة المفضلات (Bookmark Manager) متكامل يتضمّن:

  • عمليات CRUD كاملة للمفضلات (إنشاء، قراءة، تحديث، حذف)
  • معالجة طلبات واستجابات JSON باستخدام Serde
  • قاعدة بيانات PostgreSQL مع SQLx (استعلامات مُتحقق منها وقت الترجمة)
  • معالجة أخطاء منظّمة برموز HTTP مناسبة
  • حالة تطبيق مشتركة
  • التحقق من المدخلات
  • اختبارات تكامل

المتطلبات المسبقة

قبل البدء، تأكد من توفر التالي:

  • Rust 1.75+ مثبّتة عبر rustup.rs
  • PostgreSQL 15+ تعمل محلياً أو عبر Docker
  • فهم أساسي لبروتوكول HTTP ومفاهيم REST
  • محرر أكواد (يُنصح بـ VS Code مع إضافة rust-analyzer)
  • سطر أوامر (Terminal)

جديد على Rust؟ يجب أن تكون مرتاحاً مع مفاهيم الملكية (Ownership)، الهياكل (Structs)، التعدادات (Enums)، ومعالجة الأخطاء الأساسية. الفصول 1–10 من كتاب Rust الرسمي كافية.


لماذا Axum؟

قبل الغوص في التفاصيل، دعنا نفهم لماذا يتميّز Axum في منظومة Rust:

الميزةAxumActix WebRocket
مدعوم منفريق Tokioالمجتمعالمجتمع
الوسطاءTower (منظومة مشتركة)مخصصمخصص
يحتاج ماكروات؟لاقليلاًكثيراً
بيئة التشغيلTokioActix/TokioTokio
صعوبة التعلممتوسطةمتوسطةأقل

الميزة الأساسية لـ Axum هي تكامله العميق مع منظومة Tower للوسطاء. أي وسيط متوافق مع Tower يعمل مع Axum مباشرة — تحديد المعدل، التتبع، الضغط، المصادقة، وأكثر.


الخطوة 1: إعداد المشروع

أنشئ مشروع Rust جديد:

cargo new bookmark-api && cd bookmark-api

افتح Cargo.toml وأضف التبعيات:

[package]
name = "bookmark-api"
version = "0.1.0"
edition = "2021"
 
[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "uuid", "chrono"] }
uuid = { version = "1", features = ["v4", "serde"] }
chrono = { version = "0.4", features = ["serde"] }
tower-http = { version = "0.6", features = ["cors", "trace"] }
tracing = "0.1"
tracing-subscriber = "0.3"
dotenvy = "0.15"
thiserror = "2"

أنشئ ملف .env لإعدادات قاعدة البيانات:

DATABASE_URL=postgres://postgres:password@localhost:5432/bookmarks

لا ترفع ملفات .env إلى نظام التحكم بالإصدارات. أضفها إلى .gitignore فوراً.


الخطوة 2: إعداد قاعدة البيانات

شغّل PostgreSQL (باستخدام Docker إن أردت):

docker run -d \
  --name bookmarks-db \
  -e POSTGRES_PASSWORD=password \
  -e POSTGRES_DB=bookmarks \
  -p 5432:5432 \
  postgres:16-alpine

ثبّت أداة SQLx CLI لإدارة الترحيلات:

cargo install sqlx-cli --no-default-features --features postgres

أنشئ أول ترحيل:

sqlx migrate add create_bookmarks

عدّل ملف الترحيل في مجلد migrations/:

-- migrations/YYYYMMDD_create_bookmarks.sql
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
 
CREATE TABLE bookmarks (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    title VARCHAR(255) NOT NULL,
    url TEXT NOT NULL,
    description TEXT,
    tags TEXT[] DEFAULT '{}',
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
 
CREATE INDEX idx_bookmarks_tags ON bookmarks USING GIN (tags);
CREATE INDEX idx_bookmarks_created_at ON bookmarks (created_at DESC);

نفّذ الترحيل:

sqlx migrate run

الخطوة 3: هيكلة التطبيق

نظّم مشروعك في وحدات. أنشئ الهيكلة التالية:

bookmark-api/
├── Cargo.toml
├── .env
├── migrations/
│   └── YYYYMMDD_create_bookmarks.sql
└── src/
    ├── main.rs
    ├── config.rs
    ├── db.rs
    ├── error.rs
    ├── models.rs
    ├── handlers.rs
    └── routes.rs

أنشئ كل ملف:

touch src/config.rs src/db.rs src/error.rs src/models.rs src/handlers.rs src/routes.rs

الخطوة 4: وحدة الإعدادات

ابدأ بـ src/config.rs — طريقة نظيفة لتحميل متغيرات البيئة:

// src/config.rs
use std::env;
 
pub struct Config {
    pub database_url: String,
    pub host: String,
    pub port: u16,
}
 
impl Config {
    pub fn from_env() -> Self {
        dotenvy::dotenv().ok();
 
        Self {
            database_url: env::var("DATABASE_URL")
                .expect("DATABASE_URL must be set"),
            host: env::var("HOST").unwrap_or_else(|_| "0.0.0.0".to_string()),
            port: env::var("PORT")
                .unwrap_or_else(|_| "3000".to_string())
                .parse()
                .expect("PORT must be a number"),
        }
    }
}

الخطوة 5: الاتصال بقاعدة البيانات

أنشئ مجمع اتصالات قاعدة البيانات في src/db.rs:

// src/db.rs
use sqlx::postgres::PgPoolOptions;
use sqlx::PgPool;
 
pub async fn create_pool(database_url: &str) -> PgPool {
    PgPoolOptions::new()
        .max_connections(10)
        .connect(database_url)
        .await
        .expect("Failed to create database pool")
}

الخطوة 6: نماذج البيانات

عرّف نماذجك في src/models.rs. يستخدم Axum مكتبة Serde للتحويل من وإلى JSON:

// src/models.rs
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use uuid::Uuid;
 
#[derive(Debug, Serialize, FromRow)]
pub struct Bookmark {
    pub id: Uuid,
    pub title: String,
    pub url: String,
    pub description: Option<String>,
    pub tags: Vec<String>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}
 
#[derive(Debug, Deserialize)]
pub struct CreateBookmark {
    pub title: String,
    pub url: String,
    pub description: Option<String>,
    #[serde(default)]
    pub tags: Vec<String>,
}
 
#[derive(Debug, Deserialize)]
pub struct UpdateBookmark {
    pub title: Option<String>,
    pub url: Option<String>,
    pub description: Option<String>,
    pub tags: Option<Vec<String>>,
}
 
#[derive(Debug, Deserialize)]
pub struct BookmarkQuery {
    pub tag: Option<String>,
    pub search: Option<String>,
    #[serde(default = "default_limit")]
    pub limit: i64,
    #[serde(default)]
    pub offset: i64,
}
 
fn default_limit() -> i64 {
    20
}

نقاط مهمة:

  • FromRow يتيح لـ SQLx تحويل صفوف قاعدة البيانات مباشرة إلى هياكل Rust
  • Serialize / Deserialize يتولّيان تحويل JSON
  • CreateBookmark و UpdateBookmark نوعان منفصلان — نظام أنواع Rust يفرض صحة الطلبات وقت الترجمة

الخطوة 7: معالجة الأخطاء

معالجة الأخطاء السليمة أمر أساسي. أنشئ src/error.rs:

// src/error.rs
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use serde_json::json;
 
#[derive(Debug, thiserror::Error)]
pub enum AppError {
    #[error("Resource not found")]
    NotFound,
 
    #[error("Validation error: {0}")]
    Validation(String),
 
    #[error("Database error: {0}")]
    Database(#[from] sqlx::Error),
 
    #[error("Internal server error")]
    Internal(String),
}
 
impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, message) = match &self {
            AppError::NotFound => (StatusCode::NOT_FOUND, self.to_string()),
            AppError::Validation(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
            AppError::Database(err) => {
                tracing::error!("Database error: {:?}", err);
                (
                    StatusCode::INTERNAL_SERVER_ERROR,
                    "Internal server error".to_string(),
                )
            }
            AppError::Internal(msg) => {
                tracing::error!("Internal error: {}", msg);
                (
                    StatusCode::INTERNAL_SERVER_ERROR,
                    "Internal server error".to_string(),
                )
            }
        };
 
        let body = Json(json!({
            "error": {
                "status": status.as_u16(),
                "message": message,
            }
        }));
 
        (status, body).into_response()
    }
}

هذا النمط هو الأسلوب الاصطلاحي في Rust — مُعامل ? سيحوّل تلقائياً sqlx::Error إلى AppError::Database بفضل خاصية #[from]. لا مزيد من معالجة الأخطاء المتناثرة!


الخطوة 8: معالجات الطلبات

الآن المنطق الأساسي. أنشئ src/handlers.rs:

// src/handlers.rs
use axum::extract::{Path, Query, State};
use axum::http::StatusCode;
use axum::Json;
use sqlx::PgPool;
use uuid::Uuid;
 
use crate::error::AppError;
use crate::models::{Bookmark, BookmarkQuery, CreateBookmark, UpdateBookmark};
 
// GET /api/bookmarks
pub async fn list_bookmarks(
    State(pool): State<PgPool>,
    Query(params): Query<BookmarkQuery>,
) -> Result<Json<Vec<Bookmark>>, AppError> {
    let bookmarks = if let Some(tag) = &params.tag {
        sqlx::query_as::<_, Bookmark>(
            "SELECT * FROM bookmarks WHERE $1 = ANY(tags) ORDER BY created_at DESC LIMIT $2 OFFSET $3"
        )
        .bind(tag)
        .bind(params.limit)
        .bind(params.offset)
        .fetch_all(&pool)
        .await?
    } else if let Some(search) = &params.search {
        let pattern = format!("%{}%", search);
        sqlx::query_as::<_, Bookmark>(
            "SELECT * FROM bookmarks WHERE title ILIKE $1 OR description ILIKE $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3"
        )
        .bind(&pattern)
        .bind(params.limit)
        .bind(params.offset)
        .fetch_all(&pool)
        .await?
    } else {
        sqlx::query_as::<_, Bookmark>(
            "SELECT * FROM bookmarks ORDER BY created_at DESC LIMIT $1 OFFSET $2"
        )
        .bind(params.limit)
        .bind(params.offset)
        .fetch_all(&pool)
        .await?
    };
 
    Ok(Json(bookmarks))
}
 
// GET /api/bookmarks/:id
pub async fn get_bookmark(
    State(pool): State<PgPool>,
    Path(id): Path<Uuid>,
) -> Result<Json<Bookmark>, AppError> {
    let bookmark = sqlx::query_as::<_, Bookmark>(
        "SELECT * FROM bookmarks WHERE id = $1"
    )
    .bind(id)
    .fetch_optional(&pool)
    .await?
    .ok_or(AppError::NotFound)?;
 
    Ok(Json(bookmark))
}
 
// POST /api/bookmarks
pub async fn create_bookmark(
    State(pool): State<PgPool>,
    Json(input): Json<CreateBookmark>,
) -> Result<(StatusCode, Json<Bookmark>), AppError> {
    if input.title.trim().is_empty() {
        return Err(AppError::Validation("Title cannot be empty".to_string()));
    }
 
    if input.url.trim().is_empty() {
        return Err(AppError::Validation("URL cannot be empty".to_string()));
    }
 
    let bookmark = sqlx::query_as::<_, Bookmark>(
        r#"
        INSERT INTO bookmarks (title, url, description, tags)
        VALUES ($1, $2, $3, $4)
        RETURNING *
        "#,
    )
    .bind(&input.title)
    .bind(&input.url)
    .bind(&input.description)
    .bind(&input.tags)
    .fetch_one(&pool)
    .await?;
 
    Ok((StatusCode::CREATED, Json(bookmark)))
}
 
// PUT /api/bookmarks/:id
pub async fn update_bookmark(
    State(pool): State<PgPool>,
    Path(id): Path<Uuid>,
    Json(input): Json<UpdateBookmark>,
) -> Result<Json<Bookmark>, AppError> {
    let bookmark = sqlx::query_as::<_, Bookmark>(
        r#"
        UPDATE bookmarks
        SET
            title = COALESCE($1, title),
            url = COALESCE($2, url),
            description = COALESCE($3, description),
            tags = COALESCE($4, tags),
            updated_at = NOW()
        WHERE id = $5
        RETURNING *
        "#,
    )
    .bind(&input.title)
    .bind(&input.url)
    .bind(&input.description)
    .bind(&input.tags)
    .bind(id)
    .fetch_optional(&pool)
    .await?
    .ok_or(AppError::NotFound)?;
 
    Ok(Json(bookmark))
}
 
// DELETE /api/bookmarks/:id
pub async fn delete_bookmark(
    State(pool): State<PgPool>,
    Path(id): Path<Uuid>,
) -> Result<StatusCode, AppError> {
    let result = sqlx::query("DELETE FROM bookmarks WHERE id = $1")
        .bind(id)
        .execute(&pool)
        .await?;
 
    if result.rows_affected() == 0 {
        return Err(AppError::NotFound);
    }
 
    Ok(StatusCode::NO_CONTENT)
}

لاحظ نمط المستخرجات (Extractors) — يستخرج Axum البيانات من الطلب تلقائياً:

  • State(pool) ← مجمع قاعدة البيانات المشترك
  • Path(id) ← مُعاملات مسار URL
  • Query(params) ← مُعاملات سلسلة الاستعلام
  • Json(input) ← جسم الطلب بصيغة JSON

كل معالج يُرجع Result<T, AppError> حيث T ينفّذ IntoResponse. عند حدوث خطأ، مُعامل ? يمرّره بنظافة.


الخطوة 9: التوجيه

اربط كل شيء في src/routes.rs:

// src/routes.rs
use axum::routing::{delete, get, post, put};
use axum::Router;
use sqlx::PgPool;
 
use crate::handlers;
 
pub fn create_router(pool: PgPool) -> Router {
    Router::new()
        .route("/api/bookmarks", get(handlers::list_bookmarks))
        .route("/api/bookmarks", post(handlers::create_bookmark))
        .route("/api/bookmarks/{id}", get(handlers::get_bookmark))
        .route("/api/bookmarks/{id}", put(handlers::update_bookmark))
        .route("/api/bookmarks/{id}", delete(handlers::delete_bookmark))
        .route("/health", get(health_check))
        .with_state(pool)
}
 
async fn health_check() -> &'static str {
    "OK"
}

يستخدم Axum 0.8 صيغة {param} لمُعاملات المسار بدلاً من الصيغة القديمة :param.


الخطوة 10: نقطة الدخول الرئيسية

اربط كل شيء معاً في src/main.rs:

// src/main.rs
mod config;
mod db;
mod error;
mod handlers;
mod models;
mod routes;
 
use tower_http::cors::CorsLayer;
use tower_http::trace::TraceLayer;
use tracing_subscriber;
 
#[tokio::main]
async fn main() {
    // تهيئة التتبع
    tracing_subscriber::fmt()
        .with_target(false)
        .compact()
        .init();
 
    // تحميل الإعدادات
    let config = config::Config::from_env();
 
    // إنشاء مجمع قاعدة البيانات
    let pool = db::create_pool(&config.database_url).await;
 
    // تشغيل الترحيلات المعلّقة
    sqlx::migrate!()
        .run(&pool)
        .await
        .expect("Failed to run migrations");
 
    tracing::info!("Migrations applied successfully");
 
    // بناء الموجّه مع الوسطاء
    let app = routes::create_router(pool)
        .layer(TraceLayer::new_for_http())
        .layer(CorsLayer::permissive());
 
    // تشغيل الخادم
    let addr = format!("{}:{}", config.host, config.port);
    tracing::info!("Server starting on {}", addr);
 
    let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

الخطوة 11: البناء والتشغيل

لنختبر كل شيء:

# تأكد من أن PostgreSQL يعمل
cargo run

يجب أن ترى:

Server starting on 0.0.0.0:3000

اختبر باستخدام curl:

# فحص الصحة
curl http://localhost:3000/health
 
# إنشاء مفضلة
curl -X POST http://localhost:3000/api/bookmarks \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Rust Book",
    "url": "https://doc.rust-lang.org/book/",
    "description": "الكتاب الرسمي للغة Rust",
    "tags": ["rust", "learning", "documentation"]
  }'
 
# عرض جميع المفضلات
curl http://localhost:3000/api/bookmarks
 
# الفلترة حسب الوسم
curl "http://localhost:3000/api/bookmarks?tag=rust"
 
# البحث بالعنوان
curl "http://localhost:3000/api/bookmarks?search=rust"
 
# جلب مفضلة محددة (استبدل UUID)
curl http://localhost:3000/api/bookmarks/YOUR_UUID_HERE
 
# تحديث مفضلة
curl -X PUT http://localhost:3000/api/bookmarks/YOUR_UUID_HERE \
  -H "Content-Type: application/json" \
  -d '{"title": "The Rust Programming Language Book"}'
 
# حذف مفضلة
curl -X DELETE http://localhost:3000/api/bookmarks/YOUR_UUID_HERE

الخطوة 12: إضافة الوسطاء

يتألق Axum مع وسطاء Tower. لنضف تسجيل الطلبات. حدّث Cargo.toml:

[dependencies]
# ... التبعيات الموجودة ...
tower = { version = "0.5", features = ["limit", "timeout"] }

أضف وسيط قياس زمن الطلبات في src/main.rs:

use axum::middleware::{self, Next};
use axum::extract::Request;
use axum::response::Response;
use std::time::Instant;
 
async fn track_request_time(request: Request, next: Next) -> Response {
    let method = request.method().clone();
    let uri = request.uri().clone();
    let start = Instant::now();
 
    let response = next.run(request).await;
 
    let duration = start.elapsed();
    tracing::info!(
        "{} {} → {} ({}ms)",
        method,
        uri,
        response.status(),
        duration.as_millis()
    );
 
    response
}

ثم أضفه إلى الموجّه:

let app = routes::create_router(pool)
    .layer(middleware::from_fn(track_request_time))
    .layer(TraceLayer::new_for_http())
    .layer(CorsLayer::permissive());

الخطوة 13: اختبارات التكامل

أنشئ tests/api_tests.rs:

// tests/api_tests.rs
use axum::http::StatusCode;
use axum_test::TestServer;
use serde_json::json;
 
async fn setup() -> TestServer {
    dotenvy::dotenv().ok();
    let database_url = std::env::var("DATABASE_URL")
        .expect("DATABASE_URL must be set");
 
    let pool = sqlx::postgres::PgPoolOptions::new()
        .max_connections(5)
        .connect(&database_url)
        .await
        .unwrap();
 
    sqlx::migrate!().run(&pool).await.unwrap();
 
    let app = bookmark_api::routes::create_router(pool);
    TestServer::new(app).unwrap()
}
 
#[tokio::test]
async fn test_health_check() {
    let server = setup().await;
    let response = server.get("/health").await;
    assert_eq!(response.status_code(), StatusCode::OK);
    assert_eq!(response.text(), "OK");
}
 
#[tokio::test]
async fn test_create_and_get_bookmark() {
    let server = setup().await;
 
    // إنشاء
    let response = server
        .post("/api/bookmarks")
        .json(&json!({
            "title": "Test Bookmark",
            "url": "https://example.com",
            "tags": ["test"]
        }))
        .await;
 
    assert_eq!(response.status_code(), StatusCode::CREATED);
 
    let bookmark: serde_json::Value = response.json();
    let id = bookmark["id"].as_str().unwrap();
 
    // جلب
    let response = server.get(&format!("/api/bookmarks/{}", id)).await;
    assert_eq!(response.status_code(), StatusCode::OK);
 
    let fetched: serde_json::Value = response.json();
    assert_eq!(fetched["title"], "Test Bookmark");
}
 
#[tokio::test]
async fn test_not_found() {
    let server = setup().await;
    let response = server
        .get("/api/bookmarks/00000000-0000-0000-0000-000000000000")
        .await;
    assert_eq!(response.status_code(), StatusCode::NOT_FOUND);
}

أضف تبعية الاختبار إلى Cargo.toml:

[dev-dependencies]
axum-test = "16"

واجعل وحداتك عامة في src/main.rs حتى تصل إليها الاختبارات:

pub mod config;
pub mod db;
pub mod error;
pub mod handlers;
pub mod models;
pub mod routes;

شغّل الاختبارات:

cargo test

استكشاف الأخطاء وإصلاحها

مشاكل شائعة

"error: no database found" — يتحقق SQLx من الاستعلامات وقت الترجمة. شغّل cargo sqlx prepare لإنشاء بيانات الاستعلام دون اتصال، أو اضبط SQLX_OFFLINE=true لبيئات CI.

رفض الاتصال — تأكد من أن PostgreSQL يعمل و DATABASE_URL صحيح. اختبر بـ psql $DATABASE_URL.

بطء الترجمة — أوقات ترجمة Rust حقيقية. استخدم cargo watch -x run (ثبّتها بـ cargo install cargo-watch) لإعادة الترجمة التلقائية أثناء التطوير.

عدم تطابق الأنواع مع Vec<String> — نوع PostgreSQL TEXT[] يُقابل Vec<String> في SQLx. تأكد أن نوع العمود هو TEXT[] وليس VARCHAR[].


مقارنة الأداء

أداء Axum استثنائي. في المقاييس المعتادة:

الإطارطلبات/ثانيةزمن الاستجابة (p99)الذاكرة
Axum (Rust)~180,0001.2ms8MB
Express (Node)~35,0008ms65MB
FastAPI (Python)~12,00015ms45MB
Spring Boot (Java)~45,0005ms200MB

هذه أرقام تقريبية من مقاييس المجتمع. نتائجك الفعلية تعتمد على العتاد وطبيعة العمل.


الخطوات التالية

لديك الآن أساس متين لبناء واجهات API في Rust مع Axum. إليك بعض الاتجاهات للاستكشاف:

  • المصادقة — أضف وسيط JWT باستخدام jsonwebtoken وطبقات Tower
  • تجميع اتصالات قاعدة البيانات — ضبط PgPoolOptions لأحمال الإنتاج
  • توثيق OpenAPI — استخدم utoipa لتوليد وثائق Swagger
  • نشر Docker — أنشئ Dockerfile متعدد المراحل لصور صغيرة (~10MB)
  • تحديد المعدل — استخدم tower::limit::RateLimitLayer
  • دعم WebSocket — يدعم Axum WebSocket مدمجاً للميزات الفورية

الخلاصة

في هذا الدرس، بنيت واجهة REST API متكاملة باستخدام Rust و Axum تتضمّن:

  • هيكلة مشروع نظيفة بوحدات منفصلة
  • استعلامات قاعدة بيانات آمنة النوع مع SQLx
  • معالجة أخطاء سليمة باستخدام نظام أنواع Rust
  • وسطاء Tower للاهتمامات المشتركة
  • اختبارات تكامل

منحنى تعلم Rust أشد من اللغات الأخرى، لكن المكافأة هائلة — أمان الذاكرة بدون garbage collector، تزامن بلا خوف، وأداء ينافس C/C++. يجعل Axum تطوير الويب في Rust سهل المنال مع الاستفادة الكاملة من منظومة Tokio.

منظومة Rust للويب تنضج بسرعة. مكتبات مثل Axum و SQLx و Tower جاهزة للإنتاج وتستخدمها شركات مثل Cloudflare و Discord و Figma. إذا كنت تبني واجهات API حيث الأداء والموثوقية مهمان، فإن Rust تستحق نظرة جادة.


هل تريد قراءة المزيد من الدروس التعليمية؟ تحقق من أحدث درس تعليمي لدينا على البدء مع Laravel 11: التثبيت والتكوين وهيكل المجلدات.

ناقش مشروعك معنا

نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.

دعنا نجد أفضل الحلول لاحتياجاتك.

مقالات ذات صلة

بناء وكلاء الذكاء الاصطناعي من الصفر باستخدام TypeScript: إتقان نمط ReAct مع Vercel AI SDK

تعلّم كيفية بناء وكلاء الذكاء الاصطناعي من الأساس باستخدام TypeScript. يغطي هذا الدليل التعليمي نمط ReAct، واستدعاء الأدوات، والاستدلال متعدد الخطوات، وحلقات الوكلاء الجاهزة للإنتاج مع Vercel AI SDK.

35 د قراءة·