Vivian Voss

The Replacement: REST with JSON-RPC

architecture web

The Replacement ■ Episode 06

In the year 2000, Roy Fielding submitted his doctoral dissertation at the University of California, Irvine. It described an architectural style called Representational State Transfer. It was thoughtful, well-reasoned, and rooted in the design principles of the early web. We read it, nodded sagely, and then spent the next quarter-century turning it into a religion.

Five verbs. Fifty-odd status codes. URL hierarchies deep enough to require a map. HATEOAS, which nobody implements but everyone pretends to understand. OpenAPI specifications longer than the application code they describe. All of this ceremony in service of a single, rather modest question: the client wants something from the server.

The Thirty-Line Specification

In 2005, the JSON-RPC specification appeared. It is thirty lines long. The entire protocol fits on a napkin, and not a large one. The idea is disarmingly simple: call a method, get a result.

One endpoint. One HTTP verb. One format.

POST /api
{
    "method": "getUser",
    "params": { "id": 42 },
    "id": 1
}

The response:

{
    "result": { "id": 42, "name": "Alice" },
    "id": 1
}

No verb selection. No status code philosophy. No URL hierarchy to maintain, document, and argue about in code reviews. You send what you want. You receive what you asked for. The protocol is so thin you can forget it is there, which is precisely what a good protocol should aspire to.

A Study in Legibility

Consider a real-world request: you want the items from order number seven, but only the name and price fields, and you want the product details included.

REST:

GET /users/42/orders/7/items?include=product&fields=name,price

JSON-RPC:

{
    "method": "getOrderItems",
    "params": {
        "orderId": 7,
        "fields": ["name", "price"]
    }
}

The first is a puzzle. You read it left to right, parsing path segments like a compiler, mentally separating resource identifiers from query parameters, wondering whether include is a standard feature or a custom invention. The second is a sentence. A method name that says what it does, parameters that say what they contain. A junior developer can read it on their first day. A senior developer can debug it without consulting the API documentation.

The Ceremony Gap REST 5 HTTP verbs 50+ status codes URL hierarchies HATEOAS (in theory) 3,000-line OpenAPI spec JSON-RPC 1 verb: POST 1 status code: 200 Method names 30-line spec Spec for a medium API: 3,000 lines Entire protocol: 30 lines Same question: "Client wants something from server."

Where REST Earns Its Keep

REST is not without merit, and intellectual honesty demands we say so. Public APIs benefit from REST conventions because every developer already knows them. Browser caching works beautifully with GET requests and proper cache headers. File downloads map naturally to resource URLs. Legacy systems that already speak REST do not need rewriting for the sake of architectural purity. If your API is consumed by unknown third parties who expect standard HTTP semantics, REST is the path of least friction.

These are genuine strengths, not consolation prizes.

Where JSON-RPC Wins

Internal service communication. Complex operations that do not map cleanly to CRUD verbs. Mobile and SPA backends where you control both ends of the wire. Batch requests, which JSON-RPC handles natively by sending an array of calls. Any situation where the question is not "which resource?" but "what operation?"

The moment your API verbs start looking tortured, you know the model is wrong. POST /users/42/password-resets is not a resource. It is a verb pretending to be a noun because the framework demands it. {"method": "resetPassword", "params": {"userId": 42}} says what it means without the costume.

Request Flow REST Verb? URL? Status? 3 decisions before you start JSON-RPC method + params 1 decision. Done. Response 200? 201? 204? 404? 409? 422? result or error. Always 200. Complexity should live in the application, not the transport.

The Specification Gap

The JSON-RPC specification is thirty lines. The OpenAPI specification for a medium-sized REST API is three thousand. That ratio is not an exaggeration. It is arithmetic.

Three thousand lines of YAML describing URL patterns, query parameter types, response schemas for every status code, authentication flows, pagination conventions, and error formats. All of it machine-readable, none of it machine-necessary. The server already knows what it accepts. The client already knows what it sends. The specification exists to bridge the gap between the two, but the gap only exists because REST created it.

JSON-RPC closes the gap by not opening it. The method name is the documentation. The parameters are the schema. The response is either a result or an error. You do not need three thousand lines of YAML to explain what getUser does.

REST started as a dissertation. We turned it into a religion. JSON-RPC is thirty lines, one verb, and a method name. Sometimes the simplest protocol is the one that gets out of the way.