Разработка кастомных REST API эндпоинтов WordPress — наша специализация уже 5+ лет. Мобильное приложение SPA-фронтенда требует от WordPress агрегированных данных с фильтрацией по таксономиям и метаполям. Стандартный /wp/v2/posts не умеет выдавать сумму заказов клиента за период или список проектов с комбинированной сортировкой. Типичная ситуация: без кеширования при 5 000 запросов в час сервер падает. Разработчики часто лепят прямые SQL-запросы, получая N+1 проблем и уязвимости. Наш опыт показал: правильно спроектированный кастомный эндпоинт решает эти задачи за 2–5 дней, снижая нагрузку на базу в 3–5 раз. В этой статье разберём типовые сценарии, стек и архитектуру.
Чтобы избежать таких проблем, мы используем единый интерфейс для всех данных: кастомный REST API эндпоинт агрегирует записи, метаполя и таксономии за один запрос. Это сокращает количество HTTP-вызовов в 5–10 раз и упрощает поддержку фронтенда. Например, для одного из проектов (каталог с 20 000 товаров) мы реализовали эндпоинт /my-plugin/v1/products с фильтрацией по категориям, цене и характеристикам — время ответа сократилось с 4 до 1 секунды.
Ограничения стандартного REST API WordPress
Стандартные маршруты /wp/v2/posts и /wp/v2/pages хороши для чтения записей, но не для:
- Агрегированных данных — сумма заказов клиента за последний месяц.
- Сложных фильтров — комбинация метаполей и таксономий с сортировкой.
- Кастомных операций — создание заказа с проверкой стока и отправкой email.
Без кастомного эндпоинта клиенту приходится делать несколько запросов или использовать небезопасные SQL-запросы. Кастомный эндпоинт с кешированием позволяет сократить время ответа с 4 до 1 секунды.
Регистрация кастомного REST API эндпоинта
Регистрация эндпоинта с GET и POST методами. Подробнее в REST API Handbook.
add_action('rest_api_init', function () {
register_rest_route('my-plugin/v1', '/projects', [
[
'methods' => WP_REST_Server::READABLE,
'callback' => 'my_plugin_get_projects',
'permission_callback' => '__return_true',
'args' => [
'category' => [
'type' => 'string',
'sanitize_callback' => 'sanitize_title',
],
'tech' => [
'type' => 'array',
'items' => ['type' => 'string'],
'sanitize_callback' => function ($value) {
return array_map('sanitize_title', (array) $value);
},
],
'per_page' => [
'type' => 'integer',
'default' => 12,
'minimum' => 1,
'maximum' => 100,
'sanitize_callback' => 'absint',
],
'page' => [
'type' => 'integer',
'default' => 1,
'minimum' => 1,
'sanitize_callback' => 'absint',
],
],
],
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => 'my_plugin_create_project',
'permission_callback' => function () {
return current_user_can('edit_posts');
},
],
]);
register_rest_route('my-plugin/v1', '/projects/(?P<id>\d+)', [
'methods' => WP_REST_Server::READABLE,
'callback' => 'my_plugin_get_project',
'permission_callback' => '__return_true',
'args' => [
'id' => [
'validate_callback' => function ($param) {
return is_numeric($param) && $param > 0;
},
],
],
]);
});
Обработчик GET-запроса с фильтрацией по таксономиям:
function my_plugin_get_projects(WP_REST_Request $request): WP_REST_Response|WP_Error {
$per_page = $request->get_param('per_page');
$page = $request->get_param('page');
$category = $request->get_param('category');
$techs = $request->get_param('tech');
$query_args = [
'post_type' => 'project',
'post_status' => 'publish',
'posts_per_page' => $per_page,
'paged' => $page,
'no_found_rows' => false,
];
$tax_queries = [];
if ($category) {
$tax_queries[] = [
'taxonomy' => 'project_category',
'field' => 'slug',
'terms' => $category,
];
}
if (!empty($techs)) {
$tax_queries[] = [
'taxonomy' => 'tech_stack',
'field' => 'slug',
'terms' => $techs,
'operator' => 'IN',
];
}
if (!empty($tax_queries)) {
$query_args['tax_query'] = array_merge(['relation' => 'AND'], $tax_queries);
}
$query = new WP_Query($query_args);
$projects = [];
foreach ($query->posts as $post) {
$projects[] = my_plugin_format_project($post);
}
$response = new WP_REST_Response($projects, 200);
$response->header('X-WP-Total', $query->found_posts);
$response->header('X-WP-TotalPages', $query->max_num_pages);
return $response;
}
function my_plugin_format_project(WP_Post $post): array {
$thumbnail_id = get_post_thumbnail_id($post->ID);
$thumbnail_url = $thumbnail_id
? wp_get_attachment_image_url($thumbnail_id, 'large')
: null;
return [
'id' => $post->ID,
'slug' => $post->post_name,
'title' => wp_strip_all_tags($post->post_title),
'excerpt' => wp_strip_all_tags(get_the_excerpt($post)),
'url' => get_permalink($post->ID),
'thumbnail' => $thumbnail_url,
'client' => get_post_meta($post->ID, 'project_client', true),
'year' => (int) get_post_meta($post->ID, 'project_year', true),
'categories' => wp_get_post_terms($post->ID, 'project_category', ['fields' => 'slugs']),
'tech_stack' => wp_get_post_terms($post->ID, 'tech_stack', ['fields' => 'slugs']),
'modified' => get_post_modified_time('c', true, $post),
];
}
Обработчик POST с валидацией:
function my_plugin_create_project(WP_REST_Request $request): WP_REST_Response|WP_Error {
$body = $request->get_json_params();
if (empty($body['title'])) {
return new WP_Error('missing_title', 'Заголовок обязателен', ['status' => 422]);
}
$post_id = wp_insert_post([
'post_type' => 'project',
'post_title' => sanitize_text_field($body['title']),
'post_content' => wp_kses_post($body['content'] ?? ''),
'post_status' => 'draft',
'post_author' => get_current_user_id(),
], true);
if (is_wp_error($post_id)) {
return new WP_Error('insert_failed', $post_id->get_error_message(), ['status' => 500]);
}
if (!empty($body['client'])) {
update_post_meta($post_id, 'project_client', sanitize_text_field($body['client']));
}
return new WP_REST_Response(
['id' => $post_id, 'url' => get_permalink($post_id)],
201
);
}
Какой метод аутентификации выбрать?
Для GET-эндпоинтов достаточно публичного доступа. Для создания/изменения данных нужна проверка прав. Сравним методы:
| Метод | Сценарий | Сложность |
|---|---|---|
| Cookie | Запросы из админки | Нулевая (встроен) |
| Application Passwords | Внешние серверные клиенты | Низкая (официальный плагин) |
| JWT | SPA, мобильные приложения | Средняя (плагин или самописный код) |
Пример перехвата Bearer-токена:
add_filter('rest_authentication_errors', function ($result) {
if (!empty($result)) return $result;
$auth_header = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if (!str_starts_with($auth_header, 'Bearer ')) {
return $result;
}
$token = substr($auth_header, 7);
$user_id = my_plugin_validate_jwt($token);
if (is_wp_error($user_id)) {
return $user_id;
}
wp_set_current_user($user_id);
return true;
});
Кеширование ответов REST API
Для тяжёлых запросов используем Transients API. Это снижает нагрузку на БД в 3–5 раз. Пример:
function my_plugin_get_projects(WP_REST_Request $request): WP_REST_Response {
$cache_key = 'projects_' . md5(serialize($request->get_params()));
$cached = get_transient($cache_key);
if ($cached !== false) {
$response = new WP_REST_Response($cached['data'], 200);
$response->header('X-WP-Total', $cached['total']);
$response->header('X-Cache', 'HIT');
return $response;
}
// ... основная логика ...
set_transient($cache_key, ['data' => $projects, 'total' => $total], 5 * MINUTE_IN_SECONDS);
return $response;
}
add_action('save_post_project', function (int $post_id): void {
global $wpdb;
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_projects_%'");
});
Что входит в разработку эндпоинтов
| Этап | Результат |
|---|---|
| Аналитика | Определение эндпоинтов, типов данных, методов аутентификации |
| Проектирование | Схема маршрутов, структура ответов, валидация параметров |
| Реализация | Написание кода, регистрация роутов, обработчики, кеширование |
| Тестирование | Модульные тесты (PHPUnit), ручное тестирование через curl |
| Деплой | Развёртывание на боевом сервере, настройка мониторинга |
| Документация | OpenAPI-схема или инструкция для разработчиков |
Мы гарантируем соблюдение сроков и предоставляем пост-релизную поддержку в течение 30 дней. Свяжитесь с нами для оценки вашего проекта — мы проконсультируем по архитектуре и объёму работ. Закажите разработку кастомных эндпоинтов и сократите время интеграции в 2 раза.
Порядок разработки кастомного REST API
- Аудит — определяем список эндпоинтов, методов (GET/POST/PUT/DELETE) и структуры ответов.
- Проектирование схемы маршрутов — версионирование (
/my-plugin/v1/), аргументы, валидация параметров. - Реализация обработчиков — написание callback-функций, форматирование данных, обработка ошибок.
- Настройка аутентификации — Cookie (для админки), Application Passwords или JWT (для SPA/мобильных).
- Кеширование — Transients API или Redis, инвалидация при изменении данных.
- Тестирование через curl и PHPUnit, документация в OpenAPI-формате.
Стоимость разработки 2–3 кастомных эндпоинтов составляет от 15 000 рублей. Полноценный API с аутентификацией JWT и кешированием — от 40 000 рублей. Это лучше, чем прямые SQL-запросы с N+1 проблемами и уязвимостями к SQL-инъекциям.
Как отладить кастомный REST API WordPress?
Используйте curl -X GET https://site.com/wp-json/my-plugin/v1/projects -v для базовой проверки. Включите WP_DEBUG и WP_DEBUG_LOG в wp-config.php — ошибки PHP попадут в debug.log. Плагин Query Monitor покажет все SQL-запросы, выполненные во время вызова эндпоинта, и поможет выявить N+1 проблемы. Проверьте заголовки ответа: X-WP-Total должен содержать число записей, Content-Type: application/json. При ошибке 401 убедитесь, что permission_callback возвращает true или корректно проверяет права пользователя.
Почему кастомные эндпоинты лучше прямых SQL-запросов?
Кастомный REST API эндпоинт обеспечивает безопасность (фильтрация через WP API), кеширование (Transients/Redis) и версионирование. По нашим данным, переход на кастомные эндпоинты сокращает время на интеграцию в 2 раза и снижает количество ошибок на 60%. Примеры кастомных эндпоинтов: get_projects, create_order и др. Более 5 лет мы разрабатываем WordPress-решения, запущено 30+ проектов с кастомными REST API. Получите консультацию — оценим ваш проект и предложим оптимальное решение.







