Developing JSON-RPC API for Web Application
JSON-RPC is a lightweight remote procedure call protocol using JSON. Version 2.0 is the current standard. Unlike REST, there's no concept of resources—only methods and parameters. Popular in blockchain ecosystems (Ethereum JSON-RPC, Bitcoin RPC), language servers (LSP), and some payment APIs.
JSON-RPC 2.0 Specification
Request:
{
"jsonrpc": "2.0",
"method": "user.getById",
"params": { "id": 42 },
"id": 1
}
Successful Response:
{
"jsonrpc": "2.0",
"result": { "id": 42, "name": "John Doe", "email": "[email protected]" },
"id": 1
}
Error:
{
"jsonrpc": "2.0",
"error": { "code": -32602, "message": "Invalid params", "data": { "field": "id" } },
"id": 1
}
Notification (no id—no response needed):
{ "jsonrpc": "2.0", "method": "log.event", "params": { "type": "page_view" } }
Batch (multiple requests in one HTTP request):
[
{ "jsonrpc": "2.0", "method": "user.get", "params": { "id": 1 }, "id": 1 },
{ "jsonrpc": "2.0", "method": "user.get", "params": { "id": 2 }, "id": 2 }
]
Error Codes
Standard codes from specification:
| 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) |
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, { db, user: req.user });
return id != null ? { jsonrpc: '2.0', result, id } : null;
} catch (error) {
return id != null
? { jsonrpc: '2.0', error: { code: error.code || -32603, message: error.message }, id }
: null;
}
}));
res.json(responses.filter(r => r != null));
});
When to Use JSON-RPC
Ideal for: blockchain/crypto APIs, language servers, simple internal microservice communication. Not ideal for: public APIs requiring resource semantics, browser clients expecting REST conventions.
Timelines
Basic JSON-RPC server (10–15 methods): 2–3 days. With streaming, batching, advanced error handling: 4–5 days.







