כיצד להגדיר REST API עבור MODX: CMS ללא ראש עבור SPA ואפליקציות מובייל

כשהעסק שלך זקוק לאפליקציה ניידת או לחזית SPA מודרנית בעוד התוכן נמצא ב-MODX, REST API הוא חיוני. אנחנו בונים ומגדירים שכבת API שמספקת נתונים בצורה מאובטחת לכל ממשק. הצוות שלנו מוסר את הפרויקט במפתח מלא—מבחירת הארכיטקטורה ועד ליישום ולתמיכה מתמשכת.

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

אלה רק חלק מהסוגים הטכניים של אתרים שאנו עובדים איתם, ולכל אחד מהם יכולים להיות מאפיינים ופונקציונליות ספציפיים משלו, וכן ניתן להתאים אותם לצרכים ולמטרות הספציפיים של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
כיצד להגדיר REST API עבור MODX: CMS ללא ראש עבור SPA ואפליקציות מובייל
בינוני
~3-5 ימים

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1359
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113
  • פיתוח אתר עבור SBH Partners
    פיתוח אתר עבור SBH Partners
    1201
  • פיתוח אתר עבור Red Pear
    פיתוח אתר עבור Red Pear
    619

בניית REST API עבור MODX: מערכת CMS ללא ראש עבור SPA ואפליקציות מובייל

תארו לעצמכם: יש לכם אתר מסחר אלקטרוני על MODX שפועל כבר 5 שנים, והשיווק דורש אפליקציה מובייל, בעוד שמפתחים צריכים JSON API כדי לחבר חזית React. או אתר כרטיס ביקור שבו עורכי תוכן עורכים מאמרים בפאנל הניהול, אבל הם מתפרסמים בבוט טלגרם ובמערכת CMS ללא ראשויקיפדיה. MODX היא פלטפורמה חזקה, אבל חסר לה ממשק JSON מובנה. פתרונות: מחבר מותאם אישית, חבילת modREST, או יישום מלא באמצעות סניפטים עם header('Content-Type: application/json'). עם למעלה מ-70 פרויקטי MODX מוצלחים ו-15 שנות ניסיון, אנו עוזרים לכם לבחור את האפשרות האופטימלית ולהקים API אינטרנטי מותאם אישית במפתח אחד.

בעיות שאנו פותרים

  • חוסר ב-API סטנדרטי. MODX אינו מספק ממשק RESTful – צריך לכתוב שכבה משלכם. טעות אופיינית היא ניסיון להוציא JSON דרך משאב MODX רגיל, מה שמדליף נתוני רינדור.
  • אינטגרציה עם חזיתות מודרניות. ללא API, אי אפשר לחבר SPA, אפליקציות מובייל או ארכיטקטורת JAMstack. טעות: רבים מנסים להשתמש ב-MODX כמנוע תבניות, ומערבבים לוגיקת תצוגה ו-API.
  • אימות ואבטחה. API פתוח הוא סיכון; יש צורך בטוקנים, CORS ובדיקות הרשאות. 90% מהבעיות מתחילות בהגדרת CORS שגויה או באחסון סודות בקוד. לאחר קאשינג, 95% מקריאות ה-API מסתיימות תוך פחות מ-200ms.

הסיבה ש-MODX חסר REST API מובנה

MODX תוכנן במקור כ-CMS מסורתי עם תבניות. REST API לא היה חלק מהפונקציונליות הבסיסית. עם זאת, הארכיטקטורה מאפשרת הוספה גמישה של כל נקודת קצה באמצעות פרוססורים וסניפטים. אנו משתמשים בגמישות זו כדי ליצור API מלא ללא אובדן ביצועים.

איך אנחנו עושים את זה

אנו מנתחים את מבנה התוכן שלכם, קובעים את נקודות הקצה הנדרשות (5–12 בממוצע לפרויקט טיפוסי), ובוחרים שיטת יישום. אנו משתמשים ב-PHP 8.2+, xPDO עבור ORM, JWT (ספריית firebase/php-jwt) לאימות, ו-Redis לקאשינג. אנו גם מיישמים אידמפוטנטיות HTTP בנקודות קצה POST/PUT כדי להבטיח ניסיונות חוזרים בטוחים, והגבלת קצב באמצעות אלגוריתם דלי טוקנים. תוצאה: TTFB יורד ב-60% – מ-800 ms ל-120 ms, כמו בפרויקט האחרון לחנות מקוונת על Next.js (12 נקודות קצה: קטלוג, מוצר, פילטרים, חיפוש, עגלה).

השוואת אפשרויות יישום

אפשרות מורכבות מהירות (בקשות/שנייה) גמישות תמיכה
סניפט מותאם אישית נמוכה 150 נמוכה ניהול עצמי
פרוססור MODX בינונית 300 בינונית דיבאג מובנה
חבילת modREST נמוכה 200 בינונית מוגבלת
מחלקה מותאמת אישית עם xPDO גבוהה 500 גבוהה מלאה

לייצור, אנו ממליצים על מחלקה מותאמת אישית – שליטה ואבטחה מקסימליים. יישום מבוסס פרוססור מהיר פי 3 מסניפט לבקשות מרובות.

יישום JSON API: דוגמאות קוד

מחבר מותאם אישית באמצעות סניפט

צרו משאב עם סוג תוכן application/json וסניפט מטפל:

// Сниппет: ApiProducts
// Ресурс: /api/products/ (contentType: application/json, published, cacheable: нет)
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');

$action = $_GET['action'] ?? 'list';
$id = (int)($_GET['id'] ?? 0);
$limit = min((int)($_GET['limit'] ?? 20), 100);
$offset = (int)($_GET['offset'] ?? 0);

switch ($action) {
    case 'get':
        echo json_encode(getProduct($modx, $id));
        break;
    case 'list':
    default:
        echo json_encode(getProducts($modx, $limit, $offset));
        break;
}

function getProducts($modx, $limit, $offset): array {
    $c = $modx->newQuery('modResource');
    $c->where(['parent' => 5, 'published' => 1, 'deleted' => 0]);
    $c->limit($limit, $offset);
    $c->sortby('menuindex', 'ASC');

    $total = $modx->getCount('modResource', $c);
    $resources = $modx->getCollection('modResource', $c);

    $items = [];
    foreach ($resources as $resource) {
        $items[] = [
            'id' => $resource->id,
            'title' => $resource->get('pagetitle'),
            'slug' => $resource->get('alias'),
            'description' => $resource->get('introtext'),
            'price' => (float)$resource->getTVValue('price'),
            'image' => $resource->getTVValue('product_image'),
            'url' => $modx->makeUrl($resource->id, '', '', 'full'),
        ];
    }

    return [
        'total' => $total,
        'limit' => $limit,
        'offset' => $offset,
        'items' => $items,
    ];
}

גישה: // Сниппет: ApiProducts // Ресурс: /api/products/ (contentType: application/json, published, cacheable: нет) header('Content-Type: application/json; charset=utf-8'); header('Access-Control-Allow-Origin: *'); $action = $_GET['action'] ?? 'list'; $id = (int)($_GET['id'] ?? 0); $limit = min((int)($_GET['limit'] ?? 20), 100); $offset = (int)($_GET['offset'] ?? 0); switch ($action) { case 'get': echo json_encode(getProduct($modx, $id)); break; case 'list': default: echo json_encode(getProducts($modx, $limit, $offset)); break; } function getProducts($modx, $limit, $offset): array { $c = $modx->newQuery('modResource'); $c->where(['parent' => 5, 'published' => 1, 'deleted' => 0]); $c->limit($limit, $offset); $c->sortby('menuindex', 'ASC'); $total = $modx->getCount('modResource', $c); $resources = $modx->getCollection('modResource', $c); $items = []; foreach ($resources as $resource) { $items[] = [ 'id' => $resource->id, 'title' => $resource->get('pagetitle'), 'slug' => $resource->get('alias'), 'description' => $resource->get('introtext'), 'price' => (float)$resource->getTVValue('price'), 'image' => $resource->getTVValue('product_image'), 'url' => $modx->makeUrl($resource->id, '', '', 'full'), ]; } return [ 'total' => $total, 'limit' => $limit, 'offset' => $offset, 'items' => $items, ]; } .

API מלא באמצעות מחלקה

// core/components/myapi/processors/products/getlist.class.php
class ProductsGetListProcessor extends modProcessor {
    public function process(): string {
        $limit = min((int)$this->getProperty('limit', 20), 100);
        $offset = (int)$this->getProperty('offset', 0);
        $search = $this->getProperty('search', '');
        $c = $this->modx->newQuery('modResource');
        $c->where(['parent' => 5, 'published' => 1]);
        if ($search) {
            $c->where(['pagetitle:LIKE' => "%{$search}%"]);
        }
        $total = $this->modx->getCount('modResource', $c);
        $c->limit($limit, $offset);
        $collection = $this->modx->getCollection('modResource', $c);
        $list = [];
        foreach ($collection as $resource) {
            $list[] = $this->prepareResource($resource);
        }
        return $this->outputArray($list, $total);
    }

    private function prepareResource($resource): array {
        return [
            'id' => $resource->id,
            'title' => $resource->get('pagetitle'),
            'price' => $resource->getTVValue('price'),
        ];
    }
}

אימות API

// Проверка API-ключа в заголовке
$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
$validKey = $modx->getOption('myapi.secret_key');
if (!hash_equals($validKey, $apiKey)) {
    http_response_code(401);
    echo json_encode(['error' => 'Unauthorized']);
    exit;
}

// JWT верификация (с библиотекой firebase/php-jwt через Composer)
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

$token = str_replace('Bearer ', '', $_SERVER['HTTP_AUTHORIZATION'] ?? '');
try {
    $decoded = JWT::decode($token, new Key($modx->getOption('jwt_secret'), 'HS256'));
    $userId = $decoded->sub;
} catch (Exception $e) {
    http_response_code(401);
    echo json_encode(['error' => 'Invalid token']);
    exit;
}

הגדרת CORS

// Плагин CORS
// Событие: OnHandleRequest
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    header('Access-Control-Allow-Origin: https://frontend.yourdomain.com');
    header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
    header('Access-Control-Allow-Headers: Content-Type, Authorization, X-API-Key');
    header('Access-Control-Max-Age: 86400');
    http_response_code(204);
    exit;
}
if (strpos($_SERVER['REQUEST_URI'], '/api/') === 0) {
    header('Access-Control-Allow-Origin: https://frontend.yourdomain.com');
}

Webhooks על שינויי תוכן

// Плагин: ContentWebhook
// Событие: OnDocFormSave
$webhookUrl = $modx->getOption('webhook_url');
if (empty($webhookUrl)) return;

$payload = json_encode([
    'event' => $mode === modSystemEvent::MODE_NEW ? 'created' : 'updated',
    'id' => $resource->id,
    'alias' => $resource->get('alias'),
    'published' => (bool)$resource->get('published'),
]);

// Асинхронная отправка (fire and forget)
$ch = curl_init($webhookUrl);
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_TIMEOUT => 3,
    CURLOPT_RETURNTRANSFER => true,
]);
curl_exec($ch);
curl_close($ch);

תהליך ומה כלול

  1. ניתוח – אנו לומדים את מבנה התוכן הנוכחי, מגדירים נקודות קצה ומודל נתונים. תוצאה: מפרט API.
  2. עיצוב – אנו מתכננים את ה-REST API: נתיבים, שיטות, פורמטי תגובה, סכמות אימות (JWT או מפתח API).
  3. יישום – אנו כותבים סניפטים או פרוססורים, מגדירים CORS, מוסיפים webhooks להודיע לחזית על שינויים.
  4. בדיקות – אנו בודקים עומס (עד 1000 בקשות/שנייה), שגיאות, אבטחה (Postman/Insomnia + בדיקות אוטומטיות).
  5. פריסה – אנו פורסים לייצור, מגדירים קאשינג (Redis, TTL=10 שניות), ניטור (New Relic).

מה כלול: נקודת קצה לקריאה וכתיבת תוכן (CRUD), אימות, תיעוד README עם דוגמאות בקשות, הגדרת CORS, webhooks, העברת גישה והדרכה למפתח שלכם, אחריות ל-30 יום. חיסכון בזמן: עד 70% בהשוואה לפיתוח מאפס, מה שמהווה עבור פרויקט טיפוסי הפחתת עלויות משמעותית. אחריות להחזר כספי על כל עבודת ה-API.

לוח זמנים ועלות

היקף העבודה לוח זמנים מחיר התחלתי
JSON API בסיסי (3–5 נקודות קצה, קריאה בלבד) 3–4 ימים $500
CRUD מלא עם אימות ו-webhooks 7–10 ימים $1,500
אינטגרציה מורכבת (10+ נקודות קצה, קאשינג, ניטור) החל משבועיים $3,000

העלות מחושבת באופן אישי – אנו נעריך את הפרויקט שלכם תוך יום עסקים אחד. התקציב הממוצע להקמה משתנה בהתאם למורכבות. לשם השוואה, פיתוח דומה מאפס יקר פי 2–3.

דרישות טכניות להפעלת API - PHP 8.1+ - MODX 3.x - Redis או Memcached לקאשינג - Composer לניהול תלות (JWT, Monolog) - תעודת HTTPS

טעויות יישום אופייניות

  • CORS לא מוגדר – החזית לא יכולה לקרוא את ה-API מהדפדפן. בדקו כותרות ו-preflight OPTIONS.
  • אימות חלש – מפתחות API ב-URL (העבירו אותם בכותרות) וחוסר HTTPS. השתמשו ב-GET /api/products/?limit=10&offset=0 להשוואת מפתחות.
  • שאילתת N+1 – בעת שליפת רשימת משאבים ללא טעינת TVs מוקדמת. הוסיפו // core/components/myapi/processors/products/getlist.class.php class ProductsGetListProcessor extends modProcessor { public function process(): string { $limit = min((int)$this->getProperty('limit', 20), 100); $offset = (int)$this->getProperty('offset', 0); $search = $this->getProperty('search', ''); $c = $this->modx->newQuery('modResource'); $c->where(['parent' => 5, 'published' => 1]); if ($search) { $c->where(['pagetitle:LIKE' => "%{$search}%"]); } $total = $this->modx->getCount('modResource', $c); $c->limit($limit, $offset); $collection = $this->modx->getCollection('modResource', $c); $list = []; foreach ($collection as $resource) { $list[] = $this->prepareResource($resource); } return $this->outputArray($list, $total); } private function prepareResource($resource): array { return [ 'id' => $resource->id, 'title' => $resource->get('pagetitle'), 'price' => $resource->getTVValue('price'), ]; } } או השתמשו ב-JOIN.
  • התעלמות מקאש – כל בקשה פוגעת במסד הנתונים, ומגדילה את TTFB. הגדירו קאשינג של Redis או פרוססור.
  • חוסר פאג'ינציה – עם 50,000 פריטים התגובה יכולה להיות מעל 10 MB. השתמשו ב-limit ו-offset.

איך לבחור אפשרות יישום?

הבחירה תלויה במשימות שלכם: להוצאת נתונים פשוטה באתר סטטי – סניפט מותאם אישית מספיק. ל-SPA עם הרשאות – פרוססורים או מחלקות. אם אינכם בטוחים, צרו קשר – נייעץ ונעזור לכם להחליט.

קבלו ייעוץ והזמינו הקמת REST API במפתח אחד. הניסיון שלנו: למעלה מ-70 פרויקטים מוצלחים על MODX, מומחים מוסמכים מאז 2010, מחזור מלא מניתוח ועד פריסה.