Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Jsonrpc Server Sdk Laravel Package

yoanm/jsonrpc-server-sdk

A framework-agnostic PHP SDK for building JSON-RPC 2.0 servers: define methods, dispatch requests and notifications to handlers, and return spec-compliant responses, with middleware and flexible configuration for integrating the protocol into your app.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and read its source before wiring anything into Laravel — it is a small, low-traffic library (5 stars, no dependents), so the README plus src/ is the real documentation.

composer require yoanm/jsonrpc-server-sdk

Look at these files first, in order:

  1. vendor/yoanm/jsonrpc-server-sdk/README.md — the canonical usage example.
  2. vendor/yoanm/jsonrpc-server-sdk/src/Server/ServerFactory.php — the entry point; confirm the exact create() signature for your installed version.
  3. vendor/yoanm/jsonrpc-server-sdk/src/Server/Server.php — handleRequest(string $request): string is the only method that matters day-to-day.

The package is transport-agnostic: you give it a raw JSON-RPC request string, it gives you back a response string. HTTP is entirely your problem, which is exactly why it drops into Laravel cleanly.

A minimal endpoint:

// routes/api.php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Yoanm\JsonRpcServerSdk\Server\ServerFactory;

Route::post('/rpc', function (Request $request) {
    $server = ServerFactory::create([
        'system.ping' => fn () => 'pong',
        'math.add'    => fn (array $params) => $params['a'] + $params['b'],
    ]);

    $payload = $server->handleRequest($request->getContent());

    if ($payload === '') {
        return response()->noContent(); // notification-only request
    }

    return response($payload, 200, ['Content-Type' => 'application/json']);
});

First real use case: an internal service-to-service endpoint where a mobile app or another backend wants to call several operations through one URL, with a predictable error envelope, without building a REST surface for each one.

curl -s -X POST http://localhost/api/rpc \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","method":"math.add","params":{"a":2,"b":3},"id":1}'

Implementation Patterns

Move the method map out of the route closure. Put it in config/jsonrpc.php and build the server in a dedicated controller so the route stays thin:

// app/Http/Controllers/JsonRpcController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Yoanm\JsonRpcServerSdk\Server\ServerFactory;

class JsonRpcController extends Controller
{
    public function __invoke(Request $request)
    {
        $methods = collect(config('jsonrpc.methods'))
            ->map(fn (string $class) => fn (array $params = []) => app($class)($params))
            ->all();

        $payload = ServerFactory::create($methods)->handleRequest($request->getContent());

        return $payload === ''
            ? response()->noContent()
            : response($payload, 200, ['Content-Type' => 'application/json']);
    }
}

Resolve handlers from the container. The big win over a hand-rolled dispatcher is that app(Handler::class) gives you constructor injection for free (repositories, HTTP clients, config). Each handler is a single __invoke(array $params): mixed class — easy to unit test and easy to autowire.

Normalize positional vs. named params early. JSON-RPC 2.0 allows "params": [1, 2] or "params": {"a": 1, "b": 2}; the SDK passes them through, so decide as a team which one you support and reject the other, or write a small adapter that maps an ordered array onto named arguments.

Handle both named and positional consistently in one place, not in every handler:

private function normalize(array $params, array $order): array
{
    if (array_is_list($params)) {
        return array_combine($order, $params);
    }
    return $params;
}

Map application exceptions onto JSON-RPC error codes inside handlers. The protocol reserves -32768 to -32000 for spec errors (-32700 parse, -32600 invalid request, -32601 method not found, -32602 invalid params, -32603 internal). Use application codes above that range (or the -32000..-32099 server-error band) and keep a single mapping table in config so clients can rely on it.

Use Laravel middleware for everything cross-cutting. Auth (auth:sanctum or an API-key middleware), rate limiting, and request logging all belong on the route. The SDK knows nothing about any of it, which is the point.

Log the raw body before dispatch. Because the SDK converts malformed input into a JSON-RPC error object rather than throwing, Laravel's exception reporter will never see bad requests. If you want visibility into client bugs, Log::debug('rpc', ['body' => $request->getContent()]) before handleRequest().

Notifications are a first-class case. A request without id must produce no response, and a batch of only notifications produces an empty string. The response()->noContent() branch above is not optional — returning a body breaks clients that assert on 204.

Test with raw content, not postJson() helpers alone. $this->call('POST', '/api/rpc', [], [], [], ['CONTENT_TYPE' => 'application/json'], $json) gives you exact control over the body the SDK sees. Assert on decoded JSON, not string equality, because key ordering and float formatting are not guaranteed.

Gotchas and Tips

  • The SDK never sets status codes or headers. Every response is 200 OK with the JSON-RPC error inside the body. That is correct per spec, but it means Laravel Telescope, Sentry, and your access logs will show 200s for error traffic. Add a middleware or log channel that inspects the response envelope if you need error dashboards.
  • Use $request->getContent(), not $request->all(). Re-encoding the parsed array round-trips through PHP and can change numeric types, drop duplicate keys, and mangle large integers. Always hand the SDK the raw string.
  • Watch the empty-string case for batches. A batch of pure notifications typically comes back as an empty string, not []. Guard with if ($payload === '') before returning JSON, otherwise you emit an invalid Content-Type: application/json empty body.
  • Don't let Laravel's exception handler swallow protocol errors. If you throw a ValidationException inside a handler, it becomes an HTML 422 instead of a JSON-RPC -32602. Catch and translate at the handler boundary, or register a renderable callback that produces a JSON-RPC error envelope when the request path is /api/rpc.
  • Params validation is not included. The core SDK is intentionally thin. Either validate inside each handler with Laravel's Validator and map failures to -32602, or pull in a companion validator package from the same author (yoanm/jsonrpc-params-validator). Decide explicitly; silent null params are a common source of confusing client errors.
  • Use consistent JSON encoding flags. The SDK's output flags may differ from Laravel's default response()->json(). If you post-process the string, use JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE consistently, and never json_decode + json_encode the SDK output just to "clean it up".
  • Octane and long-running workers. Closures created per request are fine, but if you bind a Server instance as a singleton in a service provider, the method map (and any captured state) is frozen for the worker's lifetime. Bind it as a scoped binding, or build it per request as in the example.
  • Batch semantics are subtle. Response order does not need to match request order, notifications inside a batch must not produce entries, and an empty batch is itself an invalid request. If you write integration tests, cover all three — they are where clients usually disagree with servers.
  • Pin the version. Last release was 2025-12-13 and the project has very little downstream usage, so semver surprises are not widely reported by others. Use "yoanm/jsonrpc-server-sdk": "^<installed-major>" and read the changelog before upgrading.
  • Put the SDK behind your own interface. Define App\JsonRpc\Dispatcher with one dispatch(string $payload): string method and let the controller depend on that. It costs twenty lines and means swapping the SDK for a hand-rolled dispatcher — or adding an HTTP/PSR-7 adapter from the same ecosystem — is a one-file change rather than a rewrite.
  • Extension points worth knowing: the factory typically accepts a custom method resolver and/or params validator alongside the method list; check ServerFactory for the exact option array on your version. If you need per-method middleware (e.g. logging only admin.* methods), implement it as decorators around your handler classes rather than fighting the SDK's dispatch loop.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings