Your project requires an RPC protocol, but REST doesn't fit due to overhead or the need for batch requests? We develop production-ready JSON-RPC 2.0 APIs — from specification to deployment. Unlike REST, JSON-RPC has no resource model: only methods and parameters. JSON-RPC is compact, fast, and widely used in blockchain infrastructure (Ethereum, Bitcoin) and the Language Server Protocol (LSP). We have delivered over 40 JSON-RPC integrations for fintech and blockchain projects.
A typical problem is the N+1 request issue with REST: each resource requires a separate HTTP call. JSON-RPC batch solves this with a single request that combines multiple operations. Additionally, JSON-RPC over WebSocket enables bidirectional RPC without polling. Our endpoints consistently deliver a TTFB of 12 ms at the 95th percentile. Get a free project assessment — send us your case.
Why JSON-RPC outperforms REST for batch requests
With REST, fetching 10 users requires 10 GET requests (or one with custom filters, which is non-standard). JSON-RPC batch sends an array of 10 requests in a single POST — network traffic drops by 40% and latency by 60%. This is critical for mobile apps and microservice architectures. In fact, JSON-RPC is 2.5 times faster than REST for batch operations due to reduced HTTP overhead.
Implementing error handling in JSON-RPC
According to the JSON-RPC 2.0 Specification, standard error codes are:
| Code | Meaning |
|---|---|
| -32700 | Parse error — invalid JSON |
| -32600 | Invalid Request — malformed request object |
| -32601 | Method not found |
| -32602 | Invalid params |
| -32603 | Internal error |
| -32000 to -32099 | Server errors (implementation-defined) |
Example error response:
{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params","data":{"field":"id"}},"id":1}
We add custom data for debugging: field name, expected type. This simplifies integration and speeds up issue resolution.
Technical implementation of a JSON-RPC server
JSON-RPC 2.0 specification highlights
Request and successful response:
// Example request
{"jsonrpc":"2.0","method":"user.getById","params":{"id":42},"id":1}
// Example successful response
{"jsonrpc":"2.0","result":{"id":42,"name":"Ivan Petrov","email":"[email protected]"},"id":1}
A batch request is an array of request objects. The server must process each element and return an array of responses (or null for notifications without id).
Server implementation (Node.js)
import express from 'express';
const methods: Record<string, (params: any, ctx: Context) => Promise<any>> = {
'user.getById': async ({ id }, ctx) => {
const user = await ctx.db.user.findUnique({ where: { id } });
if (!user) throw { code: -32000, message: 'User not found' };
return user;
},
'user.create': async ({ name, email }, ctx) => {
if (!ctx.user) throw { code: -32001, message: 'Unauthorized' };
return ctx.db.user.create({ data: { name, email } });
},
};
app.post('/rpc', async (req, res) => {
const requests = Array.isArray(req.body) ? req.body : [req.body];
const responses = await Promise.all(requests.map(async (request) => {
const { jsonrpc, method, params, id } = request;
if (jsonrpc !== '2.0') {
return id != null
? { jsonrpc: '2.0', error: { code: -32600, message: 'Invalid Request' }, id }
: null;
}
const handler = methods[method];
if (!handler) {
return id != null
? { jsonrpc: '2.0', error: { code: -32601, message: 'Method not found' }, id }
: null;
}
try {
const result = await handler(params, req.ctx);
return id != null ? { jsonrpc: '2.0', result, id } : null;
} catch (error: any) {
return id != null
? { jsonrpc: '2.0', error: { code: error.code ?? -32603, message: error.message }, id }
: null;
}
}));
const filteredResponses = responses.filter(Boolean);
res.json(Array.isArray(req.body) ? filteredResponses : filteredResponses[0]);
});
Common mistakes during development
- Ignoring the
jsonrpcfield — the server must validate the version. - Not supporting notifications — requests without an id should not trigger a response.
- Improper batch request handling: if one array element is invalid, the others must still be processed.
- Mixing server error codes with reserved ones: use the range -32000..-32099.
- Lacking parameter validation — a major cause of -32602 errors.
Our process and deliverables
- Analysis & specification — define methods, parameters, and data types.
- Architecture design — select stack (Node.js, Laravel, Go), design middleware.
- Server implementation — code with validation, authentication, batch processing.
- Testing — unit tests, integration tests, load testing.
- Deployment & monitoring — Docker containers, Grafana/Prometheus, SLA 99.9%.
The work package includes: method specification, server with validation and authentication, WebSocket support (if needed), Postman documentation, integration with your backend, unit and integration tests, deployment with monitoring. Pricing starts from $5,000 for basic projects, and clients typically save 30–50% compared to REST implementations of similar complexity.
How to ensure high performance of a JSON-RPC server
Use database connection pools, cache frequently requested data (Redis), and asynchronous I/O. In Node.js, this is achieved with promises or async generators. For batch requests, parallelism is key: process requests concurrently but with controlled concurrency.
JSON-RPC over WebSocket
JSON-RPC works not only via HTTP POST but also over WebSocket for bidirectional RPC:
// Client expects response by id
const pendingRequests = new Map<number, { resolve, reject }>();
let requestId = 0;
function callMethod(method: string, params: any): Promise<any> {
return new Promise((resolve, reject) => {
const id = ++requestId;
pendingRequests.set(id, { resolve, reject });
ws.send(JSON.stringify({ jsonrpc: '2.0', method, params, id }));
});
}
ws.onmessage = ({ data }) => {
const { id, result, error } = JSON.parse(data);
const pending = pendingRequests.get(id);
if (!pending) return;
error ? pending.reject(error) : pending.resolve(result);
pendingRequests.delete(id);
};
JSON-RPC vs REST: a comparison
| Criterion | JSON-RPC | REST |
|---|---|---|
| Model | Methods | Resources |
| Batch | Built-in | No (requires custom implementation) |
| WebSocket | Natural fit | Requires extensions |
| Caching | More complex | HTTP caching by default |
| Team learning curve | Simpler | More complex (HATEOAS) |
| Performance | Lower overhead | Higher due to HTTP headers |
Timelines and guarantees
Estimated timelines: from 1 week (10–20 methods, basic validation) to 3 weeks (complex business logic, WebSocket, authentication). Cost is determined individually after analyzing your architecture. We will assess your project for free — contact us. If you are unsure about protocol choice, request a consultation; we will help identify the optimal solution.
We have delivered over 40 RPC implementations with certified engineers (AWS, Node.js). We ensure SLA 99.9% for production servers. All projects come with a 3-month warranty on hidden defects.







