avto-dev/json-rpc-laravel
Laravel integration for JSON-RPC 2.0: register RPC methods, expose HTTP endpoints, validate parameters, handle errors and middleware, and call remote JSON-RPC services from your app with a simple framework-native API.
This is an RPC-style routing package for Laravel — it maps named methods to HTTP endpoints rather than resourceful REST routes. The v2.8.0 release adds controller-action targets, which means the familiar Laravel controller DX now applies.
composer require <vendor>/<package>
php artisan vendor:publish --tag=<package>-config
config/<package>.php — endpoint prefix, middleware stack, error handling mode, and (usually) a methods array.routes/*.php or a dedicated routes/rpc.php where RpcRouter calls live.vendor/<vendor>/<package>/src/RpcRouter.php — to confirm the exact signature of on() for your installed version.// routes/rpc.php
RpcRouter::on('user.get', [UserController::class, 'get']);
class UserController
{
public function get(Request $request): array
{
return ['user' => User::findOrFail($request->integer('id'))->only(['id', 'name'])];
}
}
curl -X POST https://app.test/rpc -H 'Content-Type: application/json' \
-d '{"method":"user.get","params":{"id":1}}'
If you are already on v2.x, upgrading is a composer update plus a scan of your RpcRouter::on() calls — no code changes are required to stay on closures.
Prefer controller actions over closures for anything non-trivial. As of v2.8.0 the [SomeController::class, 'method'] form is supported, so you get:
authorize() policies.RpcRouter::on('orders.create', [OrderController::class, 'create']);
RpcRouter::on('orders.cancel', [OrderController::class, 'cancel']);
Keep the routing map as an explicit registry. Centralise all RpcRouter::on() calls in one file rather than scattering them, so the surface area of your RPC API is auditable in a single diff. Group by domain prefix (orders.*, users.*) for readability.
Version your namespace early. Name methods v1.orders.create from day one. Renaming later is a breaking client change and this package gives you no routing-level versioning for free.
Layer middleware at the router, not per-method. Authentication, tenancy resolution, and request-ID propagation belong in the config's middleware stack so every method inherits them; per-method authorization belongs in the controller (policies, gates).
Use controllers as thin protocol adapters. The RPC envelope — parameter extraction, response shaping, error codes — should be the only thing a controller action knows about. Delegate to action/service classes so the same logic remains callable from a queue job or CLI command.
Testing pattern:
$this->postJson('/rpc', ['method' => 'orders.create', 'params' => [...]])
->assertOk()
->assertJsonPath('result.id', fn ($id) => is_int($id));
Assert against the envelope, not just the payload — the shape is part of your contract.
composer why-not <vendor>/<package> 2.8.0 before opening an issue.BindingResolutionException at call time rather than boot time, the method name is usually a typo — the router may not validate it until dispatch.public. Protected/private methods fail at dispatch, not at registration.View, RedirectResponse, or StreamedResponse from a controller action is usually meaningless here — return arrays or JsonSerializable objects, and let the package own the envelope.{user} placeholder to bind against; extract IDs from params yourself and call findOrFail, then translate ModelNotFoundException into your error envelope.phpstan/phpstan ^2.1.22 and phpunit ^10.5 || ^11.0 || ^12.0 only affect maintainers and your own CI. Align your CI matrix with PHPUnit 12 only if you are ready to drop PHPUnit 10 support elsewhere.src/ for a MethodResolver or similar contract), bind your own implementation to add cross-cutting concerns — metrics, argument coercion, feature flags — instead of wrapping every controller action by hand.method string and the target callable on every dispatch at debug level during development. RPC failures are frequently "method not registered" rather than logic bugs, and the default error response often does not distinguish the two.How can I help you explore Laravel packages today?