JSON-RPC, gRPC, and REST are three different protocol styles for calling a blockchain node or indexer. JSON-RPC is a stateless, method-oriented protocol where every request is a JSON object with jsonrpc, method, params, and id, and every method maps to a concrete chain operation such as eth_getBalance or eth_call. gRPC is an HTTP/2-based, Protobuf-serialized RPC framework with typed service definitions and streaming, increasingly used by newer chain SDKs. REST is resource-oriented with HTTP verbs and status codes, convenient for indexed CRUD-style data but a poor fit for live node calls or subscriptions. The three-layer confusion is that transport, messaging style, and payload encoding are independent choices, so 'gRPC vs REST' is not the same axis as 'HTTP vs WebSocket'. This article separates documented protocol behavior from provider-specific behavior and gives you a reproducible method to measure request bytes, response bytes, and wall time against your own endpoint.
The Three Independent Layers of a Blockchain API Call
Most protocol debates collapse three independent decisions into one. The first is transport: HTTP/1.1, HTTP/2, WebSocket, or IPC. The second is messaging style: RPC (method-oriented) versus resource-oriented REST. The third is payload encoding: JSON text versus Protobuf binary. These choices are orthogonal, which is why 'gRPC vs REST' is not the same axis as 'HTTP vs WebSocket'.
gRPC, for example, is defined by the combination of HTTP/2 transport, Protobuf encoding, and an RPC messaging style with typed service definitions. REST is typically HTTP/1.1 or HTTP/2 with JSON encoding and a resource-oriented style. JSON-RPC is usually HTTP or WebSocket transport with JSON encoding and an RPC style. You can mix them: JSON-RPC over WebSocket is still JSON-RPC, and a REST API can return Protobuf.
Keeping the layers separate prevents category errors. When someone says a chain 'supports gRPC', they usually mean a typed service definition exists and is reachable over HTTP/2. When they say a node 'supports WebSocket', they mean a transport that enables push notifications. Those are different capabilities, and you may need both. For a transport-level comparison of JSON-RPC specifically, see JSON-RPC over HTTP, WebSocket, and IPC.
- Transport: how bytes move (HTTP/1.1, HTTP/2, WebSocket, IPC).
- Messaging style: how operations are named (RPC methods vs REST resources).
- Encoding: how payloads are serialized (JSON text vs Protobuf binary).
What JSON-RPC Actually Is for a Blockchain Node
JSON-RPC is a stateless, method-oriented protocol. The JSON-RPC 2.0 specification defines a request object with four members: jsonrpc (the version string), method (the operation name), params (an array or object of arguments), and id (a correlation identifier). The server returns either a result member or a structured error object with code, message, and optional data.
For a blockchain node, each method maps to a concrete chain operation. The Ethereum JSON-RPC specification documents methods such as eth_getBalance, eth_call, eth_getBlockByNumber, and eth_sendRawTransaction. The method name is the contract: there is no URI hierarchy and no HTTP verb semantics to interpret. A POST to the node's single endpoint with a JSON body is the entire interaction model.
Because JSON-RPC is stateless, every request carries everything needed to execute it. That makes it trivially horizontally scalable and easy to proxy, but it also means there is no schema in the protocol itself. Types are documented per method, not enforced by the wire format. This is the root of both its universality and its chattiness: every chain exposes JSON-RPC, but clients must know each method's shape from documentation or a client library.
- Request envelope: {jsonrpc, method, params, id}.
- Response envelope: {jsonrpc, result, id} or {jsonrpc, error, id}.
- No URI hierarchy, no HTTP verb semantics, no built-in schema.
- Universally supported across chains; the de facto default for node access.
What gRPC Is and Why Newer Chain SDKs Adopt It
gRPC is an HTTP/2-based, Protobuf-serialized RPC framework. The gRPC introduction describes it as using HTTP/2 for transport, Protocol Buffers for serialization, and a service definition language for typed contracts. It supports unary calls, server streaming, client streaming, and bidirectional streaming, and it generates client and server code from the service definition.
The practical advantages are typed contracts and lower per-message overhead. Protobuf encodes fields as tagged binary, so a message with a few numeric fields is typically smaller than the equivalent JSON text. Code generation means the client knows the exact request and response types at compile time, which reduces integration errors. Streaming means a single connection can carry a continuous flow of updates.
Newer chain SDKs increasingly expose gRPC APIs, sometimes alongside JSON-RPC. Sui's gRPC API is a documented example of a chain offering a typed gRPC surface. The tradeoff is that gRPC is not callable directly from a browser without a proxy such as gRPC-Web, and it requires code generation as a build step. For backend services and indexers, that cost is often worth paying; for a quick script or a browser dApp, JSON-RPC over HTTP remains simpler.
- Transport: HTTP/2 with multiplexed streams.
- Encoding: Protobuf binary with typed field tags.
- Contract: service definition with generated client/server code.
- Streaming: unary, server, client, and bidirectional.
- Browser limitation: needs gRPC-Web or a proxy to call from a page.
What REST Is and Where It Fits in Blockchain
REST is resource-oriented. URIs identify resources, HTTP verbs express intent (GET, POST, PUT, DELETE), and HTTP status codes communicate outcomes. It is an excellent fit for CRUD-style data: fetch a list of transactions, retrieve a token's metadata, query an indexer for historical balances. The uniform interface makes caching, pagination, and human-readable URLs straightforward.
It is a poor fit for the stateful, method-heavy, subscription-oriented calls a blockchain node exposes. A node's operations are not resources: eth_call is a computation against a specific block state, not a GET on a URI. Modeling it as REST would require inventing a resource hierarchy that does not exist, and you would lose the method-name contract that every chain client already understands.
In practice, REST appears in blockchain as an indexer or explorer API layered on top of a node. The indexer ingests blocks and exposes queryable resources. That is genuinely useful for analytics and dashboards, but it is not a substitute for live node calls. If you need the current state of the chain, you call the node; if you need a historical aggregate, you call the indexer.
- Resource-oriented URIs with HTTP verbs and status codes.
- Strong for indexed, CRUD-style, cacheable data.
- Weak for live node state, method-heavy calls, and subscriptions.
- Commonly implemented as an indexer layer, not a node interface.
Worked Comparison Table Across Protocol Axes
The table below compares the three styles across the axes that matter when choosing a blockchain API. Treat it as documented behavior and general characteristics, not as a benchmark. Provider-specific implementations vary, and you should verify the exact surface your provider exposes.
The 'typical blockchain use' row is the most decision-relevant. JSON-RPC is the default for reading and writing chain state. gRPC is common for backend services and SDKs that want typed contracts and streaming. REST is common for indexers, explorers, and analytics endpoints.
- Request shape — JSON-RPC: single POST with method and params. gRPC: typed request message. REST: URI plus HTTP verb.
- Encoding — JSON-RPC: JSON text. gRPC: Protobuf binary. REST: usually JSON text.
- Streaming — JSON-RPC: not in the base protocol; WebSocket subscriptions are a separate transport feature. gRPC: native unary and streaming. REST: not native; polling or SSE.
- Tooling — JSON-RPC: curl, any HTTP client, no codegen. gRPC: protoc and generated stubs. REST: curl, any HTTP client, OpenAPI optional.
- Browser-friendliness — JSON-RPC: high over HTTP. gRPC: low without gRPC-Web or a proxy. REST: high.
- Typical blockchain use — JSON-RPC: node reads and writes. gRPC: typed SDKs and backend streaming. REST: indexers, explorers, analytics.
Runnable Node.js Example: The Same Balance Three Ways
The example below issues the same logical operation — fetch a balance — three ways. The first is a JSON-RPC POST to a node endpoint. The second is a REST-style GET against an indexer. The third is a conceptual gRPC call using a typed client; it is shown as a stub because the service definition and package name are chain-specific and must come from the chain's SDK.
The script measures request bytes, response bytes, and wall time for each call. It uses only Node.js built-ins for the HTTP paths, so you can run it without installing dependencies. Replace the placeholder endpoints and addresses with your own. The gRPC branch is intentionally commented to avoid fabricating a service definition.
Run it with node balance-compare.js. The output is a small table you can paste into your own results sheet. Because the endpoints differ, the comparison is illustrative of shape and overhead, not a controlled benchmark. For a controlled comparison, point all three at the same provider and the same block height.
// balance-compare.js — Node.js 18+ (built-in fetch)
const NODE_RPC = process.env.NODE_RPC || 'https://your-node-endpoint';
const INDEXER_REST = process.env.INDEXER_REST || 'https://your-indexer-endpoint';
const ADDRESS = process.env.ADDRESS || '0x0000000000000000000000000000000000000000';
function bytesOf(str) { return Buffer.byteLength(str, 'utf8'); }
async function timed(label, fn) {
const start = process.hrtime.bigint();
const { reqBytes, resBytes, note } = await fn();
const end = process.hrtime.bigint();
const ms = Number(end - start) / 1e6;
return { label, reqBytes, resBytes, ms: ms.toFixed(2), note };
}
async function jsonRpcBalance() {
const body = JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'eth_getBalance',
params: [ADDRESS, 'latest']
});
const res = await fetch(NODE_RPC, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body
});
const text = await res.text();
return { reqBytes: bytesOf(body), resBytes: bytesOf(text), note: 'JSON-RPC POST' };
}
async function restBalance() {
const url = `${INDEXER_REST}/v1/addresses/${ADDRESS}/balance`;
const res = await fetch(url, { method: 'GET' });
const text = await res.text();
return { reqBytes: bytesOf(url), resBytes: bytesOf(text), note: 'REST GET' };
}
// Conceptual gRPC branch: requires a chain-specific .proto and generated client.
// async function grpcBalance() {
// const grpc = require('@grpc/grpc-js');
// const protoLoader = require('@grpc/proto-loader');
// const def = protoLoader.loadSync('chain.proto');
// const pkg = grpc.loadPackageDefinition(def);
// const client = new pkg.chain.QueryService(GRPC_ENDPOINT, grpc.credentials.createInsecure());
// const req = { address: ADDRESS };
// const reqBytes = Buffer.from(JSON.stringify(req)).length; // Protobuf size differs
// const res = await new Promise((resolve, reject) =>
// client.getBalance(req, (err, r) => err ? reject(err) : resolve(r))
// );
// return { reqBytes, resBytes: Buffer.from(JSON.stringify(res)).length, note: 'gRPC unary' };
// }
(async () => {
const rows = [];
rows.push(await timed('JSON-RPC', jsonRpcBalance));
rows.push(await timed('REST', restBalance));
console.table(rows);
})();Reproducible Results Table for Your Own Endpoint
The only honest way to compare protocols for your workload is to measure against your own endpoint. The table below is a template. Fill it in with the output of the script above, or with your own instrumentation. Do not treat any published number as universal: latency and payload size depend on the chain, the method, the provider, and the network path.
Run each operation at least 30 times and record the median and the 95th percentile, not just a single sample. Note the block height or slot at the time of measurement, because state changes between runs. If you are comparing providers, hold the method and the block height constant. For a broader evaluation framework, see Evaluating blockchain JSON-RPC API solutions.
The 'schema/typing benefit observed' column is qualitative. Record whether the client caught a type error at build time, whether you needed to consult documentation to construct the request, and whether the response required manual parsing. That is where gRPC's codegen advantage shows up, and it does not appear in a byte count.
- Operation — the logical action, e.g. fetch balance, fetch block, send transaction.
- Protocol — JSON-RPC, gRPC, or REST.
- Request bytes — serialized request size on the wire.
- Response bytes — serialized response size on the wire.
- Wall time — median and p95 over at least 30 runs.
- Schema/typing benefit observed — build-time type safety, documentation lookup, manual parsing.
- Block height or slot — recorded at measurement time to keep runs comparable.
Limitations and Tradeoffs of Each Protocol Style
JSON-RPC lacks a schema and is chattier than binary alternatives, but it is universally supported by every chain. That universality is a real engineering asset: one client pattern works across Ethereum, Polkadot, and most other networks. The cost is that you often need multiple round-trips to assemble a view, and you must trust per-method documentation for types. Batching can reduce round-trips; see JSON-RPC batching best practices.
gRPC is efficient and typed, but it needs code generation and is not callable from a browser without a proxy. The build step is a real cost in CI, and the generated stubs must be regenerated when the service definition changes. For backend services that value type safety and streaming, that cost is usually justified. For a one-off script, it is friction.
REST is convenient for indexers but not for live node calls or subscriptions. It shines when the data is already aggregated and cacheable. It does not replace a WebSocket subscription when you need push notifications: none of these three styles, in their base form, delivers server-initiated events. If you need to react to new blocks or logs in real time, you need a subscription transport, which is a separate decision from the messaging style.
- JSON-RPC: no schema, chattier, but universal across chains.
- gRPC: efficient and typed, but needs codegen and a browser proxy.
- REST: great for indexers, poor for live node calls and subscriptions.
- None of the three replaces WebSocket subscriptions for push notifications.
Troubleshooting Common Protocol-Choice Failures
A frequent failure is calling a REST endpoint expecting live node state. Indexers lag the chain head by design, so a balance fetched from an indexer may be stale. If you need the current state, call the node's JSON-RPC method directly. If you need a historical aggregate, call the indexer. Mixing the two without understanding the lag produces confusing results.
Another failure is attempting a gRPC call from a browser. Standard gRPC requires HTTP/2 trailers that browsers do not expose to JavaScript, so you need gRPC-Web or a proxy. If your dApp must call a typed service directly, budget for that proxy. Otherwise, use JSON-RPC over HTTP, which works from any browser fetch call.
A third failure is assuming JSON-RPC has a schema and sending malformed params. The protocol validates the envelope, not the method arguments. A wrong parameter type may return a structured error or may return a result that is not what you expected. Always validate against the method's documented signature, and use a client library where one exists. For simulation-style calls, see eth_call and state override simulation.
Finally, latency problems are often misattributed to the protocol. If your calls are slow, measure before you switch protocols. The bottleneck is frequently the network path or the provider's load, not JSON versus Protobuf. See Reducing RPC latency for a measurement-first approach.
- Stale indexer data mistaken for live node state.
- Browser gRPC calls failing without gRPC-Web or a proxy.
- Malformed JSON-RPC params passing envelope validation but failing semantically.
- Latency blamed on protocol when the cause is network or provider load.
Choosing a Protocol for Your Blockchain Integration
Start with the operation, not the protocol. If you are reading or writing chain state, JSON-RPC is the default and the safest choice because every chain supports it. If you are building a backend service that benefits from typed contracts and streaming, evaluate the chain's gRPC surface. If you are querying aggregated historical data, use the indexer's REST API.
For most teams, the answer is a combination. Use JSON-RPC for live node calls, a WebSocket subscription for push notifications, gRPC for typed backend services where the chain offers it, and REST for analytics. The protocols are not competitors so much as layers, and the three-layer model from the first section is the tool that keeps them straight.
When you evaluate providers, check which surfaces they expose and how they document them. A provider that offers JSON-RPC, WebSocket, and gRPC under one account reduces integration friction. You can review the available surfaces on the API service page and compare cost models on RPC pricing. For network-specific endpoints, see Ethereum.
- Live node reads and writes: JSON-RPC over HTTP.
- Push notifications: WebSocket subscription.
- Typed backend services: gRPC where the chain offers it.
- Historical aggregates: REST indexer API.
Next Steps and Further Reading
The most useful next step is to run the measurement script against your own endpoints and fill in the results table. That gives you a defensible basis for a protocol decision instead of a guess. Record the block height, run enough samples, and note the qualitative typing benefit alongside the byte counts.
From there, deepen the transport layer with JSON-RPC over HTTP, WebSocket, and IPC, and reduce round-trips with JSON-RPC batching best practices. If you are selecting a provider, the Evaluating blockchain JSON-RPC API solutions guide covers the criteria that matter beyond protocol style.
For a broader index of related material, start at the OnFinality Learn hub. The goal is not to pick a winner among JSON-RPC, gRPC, and REST, but to match each layer of your architecture to the protocol style that fits it.
- Run the measurement script and fill the results table.
- Review transport options for JSON-RPC.
- Apply batching to reduce round-trips.
- Evaluate providers on the surfaces they expose, not just price.