Azwar's Blog
Building RESTful APIs: WordPress and Laravel API Development Guide
Hi everyone! I'm Azwar, a WordPress and Laravel developer with extensive experience in API development. In this comprehensive guide, I'll walk you through building robust RESTful APIs using both WordPress REST API and Laravel. APIs are essential for modern web applications and mobile apps.

RESTful APIs are the backbone of modern web applications, enabling communication between different systems and platforms. Whether you're building APIs for mobile apps, third-party integrations, or microservices, understanding API development is crucial.
Understanding RESTful APIs
REST Principles
- Stateless: Each request contains all necessary information
- Client-Server: Separation of concerns between client and server
- Cacheable: Responses can be cached for better performance
- Uniform Interface: Consistent resource identification and manipulation
- Layered System: Architecture can be layered for scalability
HTTP Methods
- GET: Retrieve data
- POST: Create new resources
- PUT: Update entire resources
- PATCH: Partial updates
- DELETE: Remove resources
WordPress REST API Development
1. Basic WordPress REST API Setup
<?php /** * Plugin Name: Custom REST API * Description: Custom REST API endpoints for WordPress * Version: 1.0.0 * Author: Azwar */ // Prevent direct access if (!defined('ABSPATH')) { exit; } class Custom_REST_API { public function __construct() { add_action('rest_api_init', [$this, 'register_routes']); } public function register_routes() { // Register custom endpoints register_rest_route('custom-api/v1', '/posts', [ [ 'methods' => WP_REST_Server::READABLE, 'callback' => [$this, 'get_posts'], 'permission_callback' => [$this, 'get_posts_permissions_check'], ], [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [$this, 'create_post'], 'permission_callback' => [$this, 'create_post_permissions_check'], ] ]); register_rest_route('custom-api/v1', '/posts/(?P<id>\d+)', [ [ 'methods' => WP_REST_Server::READABLE, 'callback' => [$this, 'get_post'], 'permission_callback' => '__return_true', ], [ 'methods' => WP_REST_Server::EDITABLE, 'callback' => [$this, 'update_post'], 'permission_callback' => [$this, 'update_post_permissions_check'], ], [ 'methods' => WP_REST_Server::DELETABLE, 'callback' => [$this, 'delete_post'], 'permission_callback' => [$this, 'delete_post_permissions_check'], ] ]); } public function get_posts_permissions_check() { return true; // Public endpoint } public function create_post_permissions_check() { return current_user_can('publish_posts'); } public function update_post_permissions_check($request) { $post_id = $request['id']; return current_user_can('edit_post', $post_id); } public function delete_post_permissions_check($request) { $post_id = $request['id']; return current_user_can('delete_post', $post_id); } public function get_posts($request) { $args = [ 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => $request->get_param('per_page') ?: 10, 'paged' => $request->get_param('page') ?: 1, ]; if ($request->get_param('category')) { $args['category_name'] = sanitize_text_field($request->get_param('category')); } if ($request->get_param('search')) { $args['s'] = sanitize_text_field($request->get_param('search')); } $query = new WP_Query($args); $posts = []; if ($query->have_posts()) { while ($query->have_posts()) { $query->the_post(); $posts[] = $this->format_post_data(get_post()); } } wp_reset_postdata(); return new WP_REST_Response([ 'success' => true, 'data' => $posts, 'total' => $query->found_posts, 'total_pages' => $query->max_num_pages, ], 200); } public function get_post($request) { $post_id = $request['id']; $post = get_post($post_id); if (!$post || $post->post_status !== 'publish') { return new WP_Error('post_not_found', 'Post not found', ['status' => 404]); } return new WP_REST_Response([ 'success' => true, 'data' => $this->format_post_data($post), ], 200); } public function create_post($request) { $params = $request->get_params(); // Validate required fields if (empty($params['title']) || empty($params['content'])) { return new WP_Error('missing_fields', 'Title and content are required', ['status' => 400]); } $post_data = [ 'post_title' => sanitize_text_field($params['title']), 'post_content' => wp_kses_post($params['content']), 'post_status' => 'publish', 'post_author' => get_current_user_id(), ]; if (!empty($params['excerpt'])) { $post_data['post_excerpt'] = sanitize_textarea_field($params['excerpt']); } $post_id = wp_insert_post($post_data); if (is_wp_error($post_id)) { return new WP_Error('post_creation_failed', 'Failed to create post', ['status' => 500]); } // Handle categories if (!empty($params['categories'])) { $categories = array_map('intval', $params['categories']); wp_set_post_categories($post_id, $categories); } // Handle tags if (!empty($params['tags'])) { $tags = array_map('sanitize_text_field', $params['tags']); wp_set_post_tags($post_id, $tags); } return new WP_REST_Response([ 'success' => true, 'data' => $this->format_post_data(get_post($post_id)), ], 201); } public function update_post($request) { $post_id = $request['id']; $params = $request->get_params(); $post_data = ['ID' => $post_id]; if (!empty($params['title'])) { $post_data['post_title'] = sanitize_text_field($params['title']); } if (!empty($params['content'])) { $post_data['post_content'] = wp_kses_post($params['content']); } if (!empty($params['excerpt'])) { $post_data['post_excerpt'] = sanitize_textarea_field($params['excerpt']); } $updated_post_id = wp_update_post($post_data); if (is_wp_error($updated_post_id)) { return new WP_Error('post_update_failed', 'Failed to update post', ['status' => 500]); } return new WP_REST_Response([ 'success' => true, 'data' => $this->format_post_data(get_post($post_id)), ], 200); } public function delete_post($request) { $post_id = $request['id']; $result = wp_delete_post($post_id, true); if (!$result) { return new WP_Error('post_deletion_failed', 'Failed to delete post', ['status' => 500]); } return new WP_REST_Response([ 'success' => true, 'message' => 'Post deleted successfully', ], 200); } private function format_post_data($post) { return [ 'id' => $post->ID, 'title' => $post->post_title, 'content' => $post->post_content, 'excerpt' => $post->post_excerpt, 'slug' => $post->post_name, 'status' => $post->post_status, 'date' => $post->post_date, 'modified' => $post->post_modified, 'author' => [ 'id' => $post->post_author, 'name' => get_the_author_meta('display_name', $post->post_author), ], 'categories' => wp_get_post_categories($post->ID, ['fields' => 'names']), 'tags' => wp_get_post_tags($post->ID, ['fields' => 'names']), 'featured_image' => get_the_post_thumbnail_url($post->ID, 'full'), ]; } } new Custom_REST_API();
2. WordPress API Authentication
<?php // JWT Authentication for WordPress REST API class JWT_Authentication { public function __construct() { add_filter('rest_authentication_errors', [$this, 'authenticate_request']); add_action('rest_api_init', [$this, 'register_auth_endpoints']); } public function authenticate_request($result) { if ($result !== null) { return $result; } $auth_header = $_SERVER['HTTP_AUTHORIZATION'] ?? ''; if (empty($auth_header) || !preg_match('/Bearer\s+(.*)$/i', $auth_header, $matches)) { return null; } $token = $matches[1]; try { $decoded = JWT::decode($token, $this->get_secret_key(), ['HS256']); $user = get_user_by('id', $decoded->user_id); if (!$user) { return new WP_Error('invalid_token', 'Invalid token', ['status' => 401]); } wp_set_current_user($user->ID); return null; } catch (Exception $e) { return new WP_Error('invalid_token', 'Invalid token', ['status' => 401]); } } public function register_auth_endpoints() { register_rest_route('auth/v1', '/login', [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [$this, 'login'], 'permission_callback' => '__return_true', ]); register_rest_route('auth/v1', '/register', [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [$this, 'register'], 'permission_callback' => '__return_true', ]); } public function login($request) { $username = $request->get_param('username'); $password = $request->get_param('password'); $user = wp_authenticate($username, $password); if (is_wp_error($user)) { return new WP_Error('invalid_credentials', 'Invalid credentials', ['status' => 401]); } $token = $this->generate_token($user); return new WP_REST_Response([ 'success' => true, 'token' => $token, 'user' => [ 'id' => $user->ID, 'username' => $user->user_login, 'email' => $user->user_email, 'display_name' => $user->display_name, ], ], 200); } private function generate_token($user) { $payload = [ 'user_id' => $user->ID, 'username' => $user->user_login, 'exp' => time() + (24 * 60 * 60), // 24 hours ]; return JWT::encode($payload, $this->get_secret_key(), 'HS256'); } private function get_secret_key() { return defined('JWT_SECRET_KEY') ? JWT_SECRET_KEY : wp_salt('auth'); } } new JWT_Authentication();
Laravel API Development
1. Laravel API Resource Controllers
<?php // app/Http/Controllers/Api/PostController.php class PostController extends Controller { public function __construct() { $this->middleware('auth:sanctum')->except(['index', 'show']); } public function index(Request $request) { $query = Post::with(['author', 'categories', 'tags']) ->where('status', 'published'); // Search functionality if ($request->has('search')) { $query->where(function ($q) use ($request) { $q->where('title', 'like', '%' . $request->search . '%') ->orWhere('content', 'like', '%' . $request->search . '%'); }); } // Category filter if ($request->has('category')) { $query->whereHas('categories', function ($q) use ($request) { $q->where('slug', $request->category); }); } // Pagination $posts = $query->orderBy('created_at', 'desc') ->paginate($request->get('per_page', 15)); return PostResource::collection($posts); } public function store(PostRequest $request) { $post = Post::create([ 'title' => $request->title, 'content' => $request->content, 'excerpt' => $request->excerpt, 'author_id' => auth()->id(), 'status' => 'published', ]); // Handle categories if ($request->has('categories')) { $post->categories()->attach($request->categories); } // Handle tags if ($request->has('tags')) { $post->tags()->attach($request->tags); } return new PostResource($post); } public function show(Post $post) { $post->load(['author', 'categories', 'tags', 'comments']); return new PostResource($post); } public function update(PostRequest $request, Post $post) { $this->authorize('update', $post); $post->update([ 'title' => $request->title, 'content' => $request->content, 'excerpt' => $request->excerpt, ]); // Update categories if ($request->has('categories')) { $post->categories()->sync($request->categories); } // Update tags if ($request->has('tags')) { $post->tags()->sync($request->tags); } return new PostResource($post); } public function destroy(Post $post) { $this->authorize('delete', $post); $post->delete(); return response()->json([ 'message' => 'Post deleted successfully' ]); } }
2. Laravel API Resources
<?php // app/Http/Resources/PostResource.php class PostResource extends JsonResource { public function toArray($request) { return [ 'id' => $this->id, 'title' => $this->title, 'content' => $this->content, 'excerpt' => $this->excerpt, 'slug' => $this->slug, 'status' => $this->status, 'featured_image' => $this->featured_image_url, 'created_at' => $this->created_at->toISOString(), 'updated_at' => $this->updated_at->toISOString(), 'author' => new UserResource($this->whenLoaded('author')), 'categories' => CategoryResource::collection($this->whenLoaded('categories')), 'tags' => TagResource::collection($this->whenLoaded('tags')), 'comments_count' => $this->comments_count, 'likes_count' => $this->likes_count, 'can' => [ 'update' => auth()->user()?->can('update', $this->resource), 'delete' => auth()->user()?->can('delete', $this->resource), ], ]; } } // app/Http/Resources/UserResource.php class UserResource extends JsonResource { public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->when(auth()->id() === $this->id, $this->email), 'avatar' => $this->avatar_url, 'created_at' => $this->created_at->toISOString(), ]; } }
3. Laravel API Authentication with Sanctum
<?php // app/Http/Controllers/Api/AuthController.php class AuthController extends Controller { public function register(RegisterRequest $request) { $user = User::create([ 'name' => $request->name, 'email' => $request->email, 'password' => Hash::make($request->password), ]); $token = $user->createToken('auth_token')->plainTextToken; return response()->json([ 'user' => new UserResource($user), 'token' => $token, 'token_type' => 'Bearer', ], 201); } public function login(LoginRequest $request) { $user = User::where('email', $request->email)->first(); if (!$user || !Hash::check($request->password, $user->password)) { return response()->json([ 'message' => 'Invalid credentials' ], 401); } $token = $user->createToken('auth_token')->plainTextToken; return response()->json([ 'user' => new UserResource($user), 'token' => $token, 'token_type' => 'Bearer', ]); } public function logout(Request $request) { $request->user()->currentAccessToken()->delete(); return response()->json([ 'message' => 'Logged out successfully' ]); } public function me(Request $request) { return new UserResource($request->user()); } }
4. Laravel API Validation
<?php // app/Http/Requests/PostRequest.php class PostRequest extends FormRequest { public function authorize() { return true; } public function rules() { $rules = [ 'title' => 'required|string|max:255', 'content' => 'required|string|min:10', 'excerpt' => 'nullable|string|max:500', 'categories' => 'nullable|array', 'categories.*' => 'exists:categories,id', 'tags' => 'nullable|array', 'tags.*' => 'exists:tags,id', ]; if ($this->isMethod('PUT') || $this->isMethod('PATCH')) { $rules = array_map(function ($rule) { return str_replace('required|', '', $rule); }, $rules); } return $rules; } public function messages() { return [ 'title.required' => 'A title is required for the post.', 'content.required' => 'Post content is required.', 'content.min' => 'Post content must be at least 10 characters.', 'categories.*.exists' => 'One or more selected categories do not exist.', 'tags.*.exists' => 'One or more selected tags do not exist.', ]; } }
API Documentation
1. WordPress API Documentation
<?php // Add API documentation endpoint register_rest_route('custom-api/v1', '/docs', [ 'methods' => WP_REST_Server::READABLE, 'callback' => function() { return new WP_REST_Response([ 'openapi' => '3.0.0', 'info' => [ 'title' => 'Custom WordPress API', 'version' => '1.0.0', 'description' => 'Custom REST API for WordPress', ], 'paths' => [ '/posts' => [ 'get' => [ 'summary' => 'Get all posts', 'parameters' => [ [ 'name' => 'per_page', 'in' => 'query', 'schema' => ['type' => 'integer', 'default' => 10], ], [ 'name' => 'page', 'in' => 'query', 'schema' => ['type' => 'integer', 'default' => 1], ], ], 'responses' => [ '200' => [ 'description' => 'Successful response', 'content' => [ 'application/json' => [ 'schema' => [ 'type' => 'object', 'properties' => [ 'success' => ['type' => 'boolean'], 'data' => ['type' => 'array'], ], ], ], ], ], ], ], ], ], ], 200); }, 'permission_callback' => '__return_true', ]);
2. Laravel API Documentation with L5-Swagger
<?php // Install L5-Swagger: composer require darkaonline/l5-swagger // app/Http/Controllers/Api/PostController.php /** * @OA\Info( * title="Laravel API", * version="1.0.0", * description="Laravel REST API documentation" * ) */ class PostController extends Controller { /** * @OA\Get( * path="/api/posts", * summary="Get all posts", * tags={"Posts"}, * @OA\Parameter( * name="per_page", * in="query", * description="Number of posts per page", * required=false, * @OA\Schema(type="integer", default=15) * ), * @OA\Parameter( * name="search", * in="query", * description="Search term", * required=false, * @OA\Schema(type="string") * ), * @OA\Response( * response=200, * description="Successful operation", * @OA\JsonContent( * type="object", * @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/Post")), * @OA\Property(property="links", type="object"), * @OA\Property(property="meta", type="object") * ) * ) * ) */ public function index(Request $request) { // Implementation } /** * @OA\Post( * path="/api/posts", * summary="Create a new post", * tags={"Posts"}, * security={{"bearerAuth":{}}}, * @OA\RequestBody( * required=true, * @OA\JsonContent(ref="#/components/schemas/PostRequest") * ), * @OA\Response( * response=201, * description="Post created successfully", * @OA\JsonContent(ref="#/components/schemas/Post") * ), * @OA\Response( * response=422, * description="Validation error" * ) * ) */ public function store(PostRequest $request) { // Implementation } } /** * @OA\Schema( * schema="Post", * @OA\Property(property="id", type="integer"), * @OA\Property(property="title", type="string"), * @OA\Property(property="content", type="string"), * @OA\Property(property="excerpt", type="string"), * @OA\Property(property="created_at", type="string", format="date-time"), * @OA\Property(property="updated_at", type="string", format="date-time") * ) */ /** * @OA\Schema( * schema="PostRequest", * required={"title", "content"}, * @OA\Property(property="title", type="string", maxLength=255), * @OA\Property(property="content", type="string", minLength=10), * @OA\Property(property="excerpt", type="string", maxLength=500) * ) */
Best Practices for API Development
- Use Proper HTTP Status Codes: Return appropriate status codes for different scenarios
- Implement Pagination: Always paginate large datasets
- Version Your APIs: Use versioning to maintain backward compatibility
- Validate Input: Always validate and sanitize input data
- Implement Rate Limiting: Prevent API abuse with rate limiting
- Use HTTPS: Always use HTTPS for API communication
- Document Your APIs: Provide comprehensive documentation
- Handle Errors Gracefully: Return meaningful error messages
- Implement Caching: Cache frequently accessed data
- Monitor API Usage: Track API performance and usage
Conclusion
Building robust RESTful APIs requires careful planning, proper authentication, validation, and documentation. Whether you're using WordPress REST API or Laravel, following these best practices will help you create secure, scalable, and maintainable APIs.
Remember to always prioritize security, performance, and user experience when developing APIs.