alex-kudrya/laravel-jsonrpc

JSON RPC 2.0 for Laravel

Maintainers

Package info

gitlab.com/alexkudrya91/laravel-jsonrpc

Issues

pkg:composer/alex-kudrya/laravel-jsonrpc

Transparency log

Statistics

Installs: 16

Dependents: 0

Suggesters: 0

Stars: 1

v2.0.0 2026-07-26 00:13 UTC

This package is auto-updated.

Last update: 2026-07-26 08:57:44 UTC


README

Laravel JSON-RPC

JSON-RPC 2.0 for Laravel

Total Downloads Version License PHP Version Require

A Laravel package that exposes one HTTP endpoint for JSON-RPC 2.0 calls, batches, and optional notifications. It dispatches built-in methods, explicitly registered RPC methods, or Laravel controllers.

See the JSON-RPC 2.0 specification for the protocol itself.

Compatibility

This package requires PHP 8.2 or newer and supports Laravel 10, 11, 12, and 13.

Upgrading from v1 is designed to require no code changes for ordinary requests that already contain id and params. See UPGRADE.md for the behavioral differences.

Installation

composer require alex-kudrya/laravel-jsonrpc:^2.0

Laravel package discovery registers the service provider automatically. If discovery is disabled, register it manually:

// config/app.php
'providers' => [
    AlexKudrya\LaravelJsonRpc\Providers\JsonRpcServiceProvider::class,
],

Publishing is optional because the package merges its defaults at runtime:

php artisan vendor:publish \
    --provider=AlexKudrya\\LaravelJsonRpc\\Providers\\JsonRpcServiceProvider

Publish only selected resources:

php artisan vendor:publish \
    --provider=AlexKudrya\\LaravelJsonRpc\\Providers\\JsonRpcServiceProvider \
    --tag=json-rpc-config

php artisan vendor:publish \
    --provider=AlexKudrya\\LaravelJsonRpc\\Providers\\JsonRpcServiceProvider \
    --tag=json-rpc-ai-guidelines

Endpoint and request envelope

The package registers one POST endpoint. Its default path is:

/json-rpc/v1

The path is controlled by json_rpc.api_prefix. RPC methods always belong in the JSON body, not in the URL.

{
  "jsonrpc": "2.0",
  "method": "Product@create",
  "params": {
    "name": "pencil",
    "price": 1.5
  },
  "id": "product-create-1"
}

Successful response:

{
  "jsonrpc": "2.0",
  "result": {
    "id": 42,
    "name": "pencil"
  },
  "id": "product-create-1"
}

Request members:

MemberRequiredDescription
jsonrpcyesMust be the string "2.0".
methodyesBuilt-in name, registry alias, or a convention name such as Product@create.
paramsnoObject or array passed to the controller. Missing params becomes an empty array.
idconfigurableString, integer, or null. The default policy requires the member.

The response echoes the exact string, integer, or null ID. Batch consumers must correlate responses with requests by ID because notification responses are omitted.

Configuration

The main defaults are:

return [
    'api_prefix' => env('JSON_RPC_PREFIX', 'json-rpc/v1'),
    'route_middleware' => ['api'],

    'controllers_root_namespace' => 'App\\Http\\Controllers\\JsonRpc\\',
    'controllers_postfix' => 'Controller',
    'controllers_method_delimiter' => '@',

    'methods' => [],
    'allow_convention_dispatch' => true,

    'id_required' => env('JSON_RPC_ID_REQUIRED', true),
    'max_batch_size' => env('JSON_RPC_MAX_BATCH_SIZE', 100),

    'additional_high_level_parameters' => [],

    'auth_required' => env('JSON_RPC_AUTH', false),
    'auth_handler' => [
        AlexKudrya\LaravelJsonRpc\JsonRpcAuthPlaceholder::class,
        'handle',
    ],
    'no_auth_methods' => [],

    'error_logging' => false,
    'expose_exception_trace' => env(
        'JSON_RPC_EXPOSE_EXCEPTION_TRACE',
        env('APP_DEBUG', false),
    ),
];

An older published config file does not have to be republished: missing top-level keys are supplied by the package defaults.

Method resolution

Methods are resolved in this order:

  1. Package built-ins such as ping.
  2. Exact aliases in json_rpc.methods.
  3. Controller@method convention fallback when allow_convention_dispatch is true.

Explicit registry

An explicit registry is additive and takes precedence over convention dispatch:

// config/json_rpc.php
use App\Http\Controllers\JsonRpc\ProductController;

'methods' => [
    'catalog.product.create' => [ProductController::class, 'create'],
    'Product@create' => [ProductController::class, 'create'],
],

You can require registry-only dispatch:

'allow_convention_dispatch' => false,

Invalid registered targets are treated as server configuration errors. The package does not fall back to convention dispatch for a malformed entry with the same name.

Convention dispatch

With the default configuration:

Product@create

resolves to:

App\Http\Controllers\JsonRpc\ProductController::create()

Subnamespaces remain supported, for example Admin\Product@get. Only existing public non-magic methods are dispatchable. Private, protected, and __* methods are rejected.

Controllers and validation

A controller may receive raw params:

public function create(array $params): array
{
    return ['name' => $params['name']];
}

It may have no arguments:

public function status(): array
{
    return ['status' => 'ok'];
}

For Laravel validation and authorization, extend the package request:

namespace App\Http\Requests\JsonRpc;

use AlexKudrya\LaravelJsonRpc\Requests\JsonRpcRequest;

class CreateProductRequest extends JsonRpcRequest
{
    protected function prepareForValidation(): void
    {
        $this->merge([
            'name' => trim((string) $this->input('name')),
        ]);
    }

    public function rules(): array
    {
        return [
            'name' => ['required', 'string', 'max:255'],
            'price' => ['required', 'numeric', 'min:0'],
        ];
    }
}

The controller can also receive additional container dependencies:

use App\Http\Requests\JsonRpc\CreateProductRequest;
use App\Services\ProductService;

public function create(
    CreateProductRequest $request,
    ProductService $products,
): array {
    $product = $products->create($request->validated());

    return $product->toArray();
}

Input access has two intentional views:

  • $request->params() and $request->param('name') return the original raw JSON-RPC params.
  • $request->input(), $request->all(), and $request->validated() use the normal Laravel FormRequest lifecycle, including prepareForValidation().

Use $request->validated() for persistence. Protocol metadata remains available through $request->method(), $request->method(true), $request->jsonrpc(), and $request->id().

Laravel authorize(), after(), passedValidation(), user/route resolvers, middleware attributes, headers, and the redirector/container lifecycle are preserved.

Notifications and ID policy

The backward-compatible default requires the id member:

'id_required' => true,

A missing ID then returns Invalid Request.

To enable JSON-RPC notifications:

'id_required' => false,

A notification omits id entirely:

{
  "jsonrpc": "2.0",
  "method": "Audit@record",
  "params": {
    "event": "signed-in"
  }
}

The method is executed, but the endpoint returns HTTP 204 with no body. An explicit "id": null is not treated as a notification and receives a normal response.

Batch requests

A batch is a non-empty JSON array. Calls are processed sequentially; one failed call does not prevent later calls.

[
  {
    "jsonrpc": "2.0",
    "method": "Product@get",
    "params": {"id": 1},
    "id": "get-1"
  },
  {
    "jsonrpc": "2.0",
    "method": "Audit@record",
    "params": {"event": "product-read"}
  },
  {
    "jsonrpc": "2.0",
    "method": "ping",
    "id": 300
  }
]

When notifications are enabled, the notification is executed but omitted from the response:

[
  {
    "jsonrpc": "2.0",
    "result": {"id": 1, "name": "pencil"},
    "id": "get-1"
  },
  {
    "jsonrpc": "2.0",
    "result": {"message": "pong"},
    "id": 300
  }
]

Batch rules:

  • An empty batch returns one Invalid Request object.
  • Invalid scalar elements produce their own Invalid Request responses with id: null.
  • A notification-only batch returns HTTP 204.
  • The default max_batch_size is 100; an oversized batch is rejected before dispatch.
  • Set max_batch_size to null to disable the size limit.

Errors

Protocol and server errors use a consistent object:

{
  "jsonrpc": "2.0",
  "error": {
    "code": -32602,
    "message": "Invalid params",
    "data": {
      "name": [
        "The name field is required."
      ]
    }
  },
  "id": "product-create-1"
}
ConditionCodeMessage
Malformed JSON-32700Parse error
Invalid request envelope-32600Invalid Request
Unknown method-32601Method not found
Invalid params or Laravel validation-32602Invalid params
Unhandled controller/configuration failure-32603Internal error
Batch limit exceeded-32000Batch size limit exceeded
Package auth denied-32001Unauthorized
FormRequest authorization denied-32003Forbidden

JSON-RPC errors use HTTP 200. Middleware may still return its own HTTP status before the endpoint is reached.

Domain errors can keep application-specific codes:

use AlexKudrya\LaravelJsonRpc\Exceptions\JsonRpcException;

throw new JsonRpcException(
    message: 'Product is archived',
    code: 1201,
    additional_data: ['product_id' => 42],
);

Authentication and middleware

For new applications, prefer normal Laravel middleware:

'route_middleware' => [
    'api',
    'auth:sanctum',
    'throttle:api',
],
'auth_required' => false,

The middleware stack applies to the single endpoint. It can also attach tenancy, logging, tracing, or other request context.

The package auth handler remains available for backward compatibility and method-level bypasses:

'auth_required' => true,
'auth_handler' => [App\Auth\JsonRpcAuth::class, 'handle'],
'no_auth_methods' => ['ping'],

The configured public handler may accept zero, one, or two arguments:

public function handle(): bool
public function handle(array $input): bool
public function handle(array $input, array $headers): bool

Returning false produces Unauthorized. Handler configuration/runtime failures produce Internal error.

Error logging and diagnostics

Enable package error logging with:

'error_logging' => true,

The package uses the application-defined logging.channels.json_rpc unchanged. If the application has not defined it, the provider installs a daily-file default.

Generic exception messages and traces are hidden by default. Expose traces only in trusted development environments:

'expose_exception_trace' => true,

A logging failure never changes the protocol response.

Swagger / OpenAPI

Document JSON-RPC as one POST endpoint. JsonRpcOpenApi produces method-specific request/result schemas while retaining single and batch shapes:

use AlexKudrya\LaravelJsonRpc\OpenApi\JsonRpcOpenApi;

$openapi = JsonRpcOpenApi::document([
    JsonRpcOpenApi::method(
        method: 'Product@create',
        paramsSchema: [
            'type' => 'object',
            'required' => ['name'],
            'properties' => [
                'name' => ['type' => 'string'],
                'price' => ['type' => 'number'],
            ],
        ],
        resultSchema: [
            'type' => 'object',
            'properties' => [
                'id' => ['type' => 'integer'],
                'name' => ['type' => 'string'],
            ],
        ],
        options: [
            'summary' => 'Create product',
            'params_example' => ['name' => 'pencil', 'price' => 1.5],
        ],
    ),
], [
    'title' => 'Product JSON-RPC API',
    'version' => '2.0.0',
    'id_required' => config('json_rpc.id_required', true),
]);

The generated document includes:

  • optional params;
  • required or optional id according to id_required;
  • string, integer, and null IDs;
  • standard error objects with code, message, and optional data;
  • single request/response and batch shapes;
  • HTTP 204 for notifications.

Duplicate normalized schema/example names and invalid method definitions raise InvalidArgumentException instead of silently overwriting documentation.

Testing

Use the package trait in consumer feature tests:

use AlexKudrya\LaravelJsonRpc\Testing\JsonRpcRequestTrait;

class ProductJsonRpcTest extends TestCase
{
    use JsonRpcRequestTrait;

    public function test_create(): void
    {
        $response = $this->jsonRpcRequest(
            controller: ProductController::class,
            method: 'create',
            params: ['name' => 'pencil'],
            id: 1001,
        );

        $response->assertOk();
        $response->assertJsonPath('result.name', 'pencil');
        $response->assertJsonPath('id', 1001);
    }
}

Notification helper:

config()->set('json_rpc.id_required', false);

$response = $this->jsonRpcNotification(
    controller: AuditController::class,
    method: 'record',
    params: ['event' => 'signed-in'],
);

$response->assertNoContent();

The helper preserves controller subnamespaces and sends requests to the configured endpoint.

Local quality checks

composer validate --strict --no-check-publish
composer check
composer audit