mrldavies / wp-rest-fluent
A fluent routing layer for the WordPress REST API with middleware, groups and response formatting.
Requires
- php: >=8.1
Requires (Dev)
- pestphp/pest: ^4.4
README
A fluent routing layer for the WordPress REST API with middleware, groups and response formatting.
Inspired by Laravel-style routing and middleware patterns, adapted for WordPress.
Requirements
- PHP >= 8.1
- WordPress (must run inside a WP environment)
If using Roots Sage, v10 or above is required.
Installation
Install via Composer:
composer require mrldavies/wp-rest-fluent
Basic Usage
Import the class:
use Mrldavies\WpRestFluent\Rest;
Define your routes:
Rest::get('/hello/{name:alpha}') ->handler(function ($request) { return [ 'data' => ['message' => "Hello {$request['name']}"], 'status' => 200, ]; }) ->formatter();
Register routes during boot:
add_action('plugins_loaded', function () { Rest::registerRoutes(); });
Using with Roots Sage (v10+)
If using Roots Sage v10 or above (Acorn-based), register routes inside a service provider.
Example:
<?php namespace App\Providers; use Roots\Acorn\Sage\SageServiceProvider; use Mrldavies\WpRestFluent\Rest; class ThemeServiceProvider extends SageServiceProvider { public function register() { Rest::registerRoutes(); parent::register(); } public function boot() { parent::boot(); } }
Ensure your provider is registered in config/app.php.
This works because Acorn bootstraps WordPress hooks correctly within the container lifecycle.
HTTP Methods
Rest::get('/endpoint') Rest::post('/endpoint') Rest::put('/endpoint') Rest::patch('/endpoint') Rest::delete('/endpoint')
Route Parameters
You can define typed parameters using curly braces:
Rest::get('/product/{id:int}') Rest::get('/user/{name:alpha}') Rest::get('/optional/{slug?}')
Supported built-in types
int→[0-9]+alpha→[a-zA-Z]+- default (no type) →
[a-zA-Z0-9-+_]+
Example:
Rest::get('/invoice/{ref}')
Optional parameter:
Rest::get('/category/{slug?}')
Advanced / Raw Regex Routes
If you require full regex control, you can bypass the curly-brace syntax entirely and use native WordPress-style patterns:
Rest::get('/legacy/age(?:/(?P<id>[0-9]+))')
This gives complete control over route matching.
Response Handling
Your handler may return:
Array
return [ 'data' => $payload, 'status' => 200, ];
Object
return (object)[ 'data' => $payload, 'status' => 200, ];
Returning WP Native Responses
You may also return:
WP_REST_ResponseWP_Error
These will be passed through untouched.
Formatter
Enable formatted responses:
->formatter();
Output shape:
{
"data": {...},
"status": 200,
"success": true
}
Mapping Custom Response Shapes
If your handler returns a different structure:
return [ 'payload' => [...], 'code' => 418 ];
Map it:
Rest::get('/example') ->map('payload', 'code') ->handler(fn() => externalCall()) ->formatter();
Middleware
Attach middleware to routes:
use Mrldavies\WpRestFluent\Middleware\RateLimitMiddleware; Rest::get('/limited') ->middleware([new RateLimitMiddleware(3, 1)]) ->handler(fn() => ['data' => ['ok' => true], 'status' => 200]) ->formatter();
Middleware follows the same conceptual structure as Laravel middleware.
Laravel middleware documentation: https://laravel.com/docs/middleware
Your middleware must implement:
public function handle($request, $next)
To continue the chain:
return $next($request);
Middleware may return:
WP_REST_ResponseWP_Error- Or allow execution to continue
Middleware execution order is LIFO (last attached runs closest to the handler).
Shipped Middleware
RateLimitMiddleware
The package includes a simple transient-based rate limiter.
Example:
Rest::get('/limited') ->middleware([new RateLimitMiddleware(5, 1)]) // 5 requests per 1 minute ->handler(...) ->formatter();
Features:
- Per-IP limiting
- Per-user limiting when logged in
- Sliding window
- Returns HTTP 429 with
Retry-Afterheader
The rate limiter is provided as a convenience and can be replaced or extended.
Route Groups
Group routes under shared configuration:
Rest::group(['prefix' => 'v2'], function () { Rest::get('/users') ->handler(fn() => ['data' => [], 'status' => 200]) ->formatter(); });
You may also group middleware and permissions:
Rest::group([ 'prefix' => 'admin', 'middleware' => [new RateLimitMiddleware(10, 1)], ], function () { Rest::get('/dashboard') ->permissions(fn() => current_user_can('manage_options')) ->handler(...) ->formatter(); });
Permissions
Attach permission callbacks:
Rest::get('/admin') ->permissions(function () { return current_user_can('manage_options'); }) ->handler(fn() => ['data' => ['ok' => true], 'status' => 200]) ->formatter();
Permission callbacks must return:
truefalse- or
WP_Error
Custom Namespace Prefix
Default namespace prefix is v1.
Override per-route:
Rest::get('/custom') ->prefix('v9') ->handler(...)
Debugging Routes
Inspect registered routes:
Rest::debugRoutes();
Notes
- Designed specifically for WordPress REST API.
- Requires WordPress runtime.
- Middleware architecture mirrors Laravel's pipeline concept.
- Supports array and object response normalization.
- Allows full custom WordPress regex routes when needed.
License
MIT