Performance Optimization & Best Practices
API Performance Matters
Slow API responses hurt user experience and waste resources. Let's optimize.
Caching Strategies
Transient Caching
Cache expensive queries:
function get_popular_posts_api() {
$cache_key = 'popular_posts_api';
$cached = get_transient($cache_key);
if ($cached !== false) {
return $cached;
}
// Expensive query
$posts = new WP_Query([
'posts_per_page' => 10,
'meta_key' => 'views_count',
'orderby' => 'meta_value_num',
'order' => 'DESC'
]);
$data = array_map(function($post) {
return [
'id' => $post->ID,
'title' => $post->post_title,
'views' => get_post_meta($post->ID, 'views_count', true)
];
}, $posts->posts);
// Cache for 1 hour
set_transient($cache_key, $data, HOUR_IN_SECONDS);
return $data;
}Object Cache
For more persistent caching:
function get_task_stats() {
$cache_key = 'task_stats';
$stats = wp_cache_get($cache_key, 'taskmanager');
if ($stats !== false) {
return $stats;
}
global $wpdb;
$stats = $wpdb->get_results(
"SELECT status, COUNT(*) as count
FROM {$wpdb->prefix}tasks
GROUP BY status"
);
wp_cache_set($cache_key, $stats, 'taskmanager', 300);
return $stats;
}
// Invalidate on changes
function invalidate_task_cache() {
wp_cache_delete('task_stats', 'taskmanager');
delete_transient('popular_posts_api');
}
add_action('task_created', 'invalidate_task_cache');
add_action('task_updated', 'invalidate_task_cache');HTTP Caching Headers
Let browsers and CDNs cache responses:
register_rest_route('myplugin/v1', '/public-data', [
'methods' => 'GET',
'callback' => function() {
$data = get_public_data();
$response = new WP_REST_Response($data);
// Cache for 5 minutes
$response->header('Cache-Control', 'public, max-age=300');
// ETag for conditional requests
$etag = md5(serialize($data));
$response->header('ETag', $etag);
return $response;
},
'permission_callback' => '__return_true'
]);Reducing Response Size
Select Only Needed Fields
// Instead of SELECT *
$tasks = $wpdb->get_results(
"SELECT id, title, status FROM {$wpdb->prefix}tasks"
);Filter Response Data
function prepare_task_response($task, $context = 'view') {
$data = [
'id' => $task->id,
'title' => $task->title,
'status' => $task->status
];
// Include extra data only when editing
if ($context === 'edit') {
$data['created_at'] = $task->created_at;
$data['created_by'] = $task->created_by;
}
return $data;
}Pagination
Never return unlimited results:
'args' => [
'per_page' => [
'default' => 10,
'maximum' => 100, // Hard limit
'minimum' => 1
]
]Query Optimization
Use Proper Indexes
-- Add indexes for frequently queried columns
ALTER TABLE wp_tasks ADD INDEX status_idx (status);
ALTER TABLE wp_tasks ADD INDEX created_by_idx (created_by);
ALTER TABLE wp_tasks ADD INDEX created_at_idx (created_at);Avoid N+1 Queries
// โ Bad: N+1 queries
function get_tasks_with_users_bad() {
$tasks = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}tasks");
foreach ($tasks as &$task) {
// One query per task!
$task->user = get_userdata($task->created_by);
}
return $tasks;
}
// โ
Good: Single join query
function get_tasks_with_users_good() {
global $wpdb;
return $wpdb->get_results("
SELECT t.*, u.display_name as user_name, u.user_email
FROM {$wpdb->prefix}tasks t
LEFT JOIN {$wpdb->users} u ON t.created_by = u.ID
ORDER BY t.created_at DESC
LIMIT 20
");
}Rate Limiting
Prevent abuse:
function check_rate_limit(WP_REST_Request $request) {
$user_id = get_current_user_id();
$ip = $_SERVER['REMOTE_ADDR'];
$key = "rate_limit_{$user_id}_{$ip}";
$requests = get_transient($key) ?: 0;
if ($requests >= 100) { // 100 requests per minute
return new WP_Error(
'rate_limited',
'Too many requests. Please try again later.',
['status' => 429]
);
}
set_transient($key, $requests + 1, MINUTE_IN_SECONDS);
return true;
}
// Apply to endpoint
register_rest_route('myplugin/v1', '/expensive-operation', [
'methods' => 'POST',
'callback' => 'do_expensive_operation',
'permission_callback' => 'check_rate_limit'
]);Best Practices Summary
| Practice | Why |
|---|---|
| Use proper HTTP methods | Semantic clarity, caching |
| Return appropriate status codes | Client error handling |
| Validate all input | Security |
| Sanitize output | XSS prevention |
| Paginate collections | Performance |
| Version your API | Backward compatibility |
| Document endpoints | Developer experience |
| Use consistent naming | snake_case for JSON keys |
Security Checklist
// โ
Always validate & sanitize
$title = sanitize_text_field($request->get_param('title'));
// โ
Use proper capabilities
if (!current_user_can('manage_options')) {
return new WP_Error('forbidden', 'Access denied', ['status' => 403]);
}
// โ
Verify ownership
if ($task->created_by !== get_current_user_id()) {
return new WP_Error('forbidden', 'Not your task', ['status' => 403]);
}
// โ
Escape output
return [
'title' => esc_html($task->title),
'url' => esc_url($task->url)
];Monitoring
Log slow queries and errors:
add_action('rest_request_after_callbacks', function($response, $handler, $request) {
global $wpdb;
// Log slow requests
$time = timer_stop();
if ($time > 1.0) { // Over 1 second
error_log(sprintf(
'Slow REST API: %s %s took %.2fs',
$request->get_method(),
$request->get_route(),
$time
));
}
// Log DB query count
if ($wpdb->num_queries > 50) {
error_log(sprintf(
'High query count: %s had %d queries',
$request->get_route(),
$wpdb->num_queries
));
}
return $response;
}, 10, 3);Congratulations!
You've completed the WordPress REST API Mastery tutorial!
What You've Learned
- โ REST API architecture and endpoints
- โ Advanced querying with filters and embedding
- โ Multiple authentication methods
- โ Create, Update, Delete operations
- โ Building custom endpoints
- โ JavaScript frontend integration
- โ Performance optimization
Next Steps
- Build a headless app with Next.js or React
- Create a mobile app using React Native
- Integrate external services like CRMs or email platforms
- Build custom Gutenberg blocks that use the API
๐ฏ Tutorial Complete! You're now proficient in the WordPress REST API and can build powerful integrations.