The Graph vs Direct RPC: When to Use Each for Blockchain Data
Every developer building on Ethereum eventually asks this question: should I use The Graph or direct RPC calls to get my blockchain data?
The answer is not one or the other. The Graph and RPC endpoints are tools designed for different jobs. Most production DeFi applications use both. The skill is knowing which one handles which part of your data layer.
What Each Tool Actually Does
Direct RPC (JSON-RPC) Direct RPC calls go straight to a blockchain node. You ask the node a question (what is this address’s balance, what did this contract return, what events were emitted in this block range) and the node answers using its local copy of the blockchain state.
RPC is the raw connection to the blockchain. Everything else is built on top of it.
The Graph The Graph is an indexing layer that sits between your application and the raw blockchain. Developers write “subgraphs,” configurations that tell The Graph which contracts to watch, which events to index, and how to transform that raw data into queryable entities.
Once deployed, your subgraph is continuously updated as new blocks come in. You query it via GraphQL instead of JSON-RPC.
What Direct RPC Is Good At
Current state queries
RPC is instant for reading the current state of any contract or address. No indexing required. The node has the data locally.
// Current token balance — fast, no indexing needed
const balance = await contract.balanceOf(walletAddress);
// Current pool price — direct from contract state
const [reserve0, reserve1] = await pool.getReserves();
// Any view function on any contract, any chain
const result = await contract.anyViewFunction(params);
Transaction submission and confirmation
Only RPC can submit transactions. The Graph is read-only.
// This only works via RPC
const tx = await contract.swap(amountIn, amountOutMin, path, to, deadline);
await tx.wait();
Real-time event streaming
WebSocket subscriptions give you instant notification of new events as they happen: no polling, no delay.
const wsProvider = new ethers.WebSocketProvider(WSS_ENDPOINT);
const contract = new ethers.Contract(POOL_ADDRESS, POOL_ABI, wsProvider);
// Fires instantly when a swap occurs
contract.on('Swap', (sender, amount0In, amount1In, amount0Out, amount1Out) => {
updatePrice(amount0Out, amount1Out);
});
Simple historical queries (recent blocks)
For recent history (last few thousand blocks), eth_getLogs via RPC is fast and simple.
const logs = await provider.getLogs({
address: TOKEN_ADDRESS,
topics: [transferTopic],
fromBlock: currentBlock - 1000, // last ~3 hours
toBlock: 'latest',
});
Multi-chain, any contract
RPC works with any contract on any chain, instantly, without deployment. New contract deployed five minutes ago? Call it via RPC with no setup.
Where Direct RPC Falls Short
Deep historical queries across many blocks
eth_getLogs becomes slow and unreliable for very large block ranges. Most providers time out at 10,000–100,000 blocks per query. Scanning millions of blocks for all historical transfer events of a token requires chunking and takes significant time.
Complex data relationships
Joining data across multiple events and contracts via RPC requires application-level logic. Getting all positions for a protocol (which involves reading across multiple user interactions, fee accruals, and state changes) means dozens of calls and significant data assembly code.
Aggregated statistics
“What is the total volume of all Uniswap pools in the last 30 days?” requires processing millions of Swap events. Via RPC this means scanning millions of blocks, decoding events, aggregating values, a job that takes minutes even optimized.
Pagination and filtering
eth_getLogs does not support pagination natively. If a filter returns 10,000 events, you get all 10,000 at once. Filtering beyond topics is not possible at the RPC layer. You do it in your application after fetching.
What The Graph Is Good At
Historical data at any depth
Once a subgraph is deployed and synced, querying 3 years of historical data takes the same time as querying last week’s data. The Graph has already processed and stored it.
{
swaps(
where: { timestamp_gte: 1672531200 }
orderBy: timestamp
orderDirection: desc
first: 1000
) {
id
timestamp
amount0In
amount1Out
amountUSD
}
}
Aggregated metrics
The Graph can compute and store aggregated values as new events arrive. TVL, volume, fee APR: pre-computed and instantly queryable.
{
pools(orderBy: totalValueLockedUSD, orderDirection: desc, first: 10) {
id
token0 { symbol }
token1 { symbol }
totalValueLockedUSD
volumeUSD
feesUSD
}
}
Complex entity relationships
Subgraphs let you define entities and relationships. A “position” entity can be built from dozens of events (mint, burn, collect, transfer) and queried as a single object.
{
position(id: "0x...tokenId") {
owner
pool { token0 { symbol } token1 { symbol } }
liquidity
depositedToken0
depositedToken1
collectedFeesToken0
collectedFeesToken1
}
}
Pagination and filtering
GraphQL supports pagination (first, skip, after) and filtering on any indexed field. Results are already processed. No post-processing in your application.
Where The Graph Falls Short
Latency
The Graph typically has 1–5 minute latency behind the chain tip. It indexes new blocks but is never perfectly real-time. For a live price display that needs data from the last few seconds, The Graph is too slow.
Setup time
Writing, deploying, and syncing a subgraph takes hours to days. Initial sync for a full protocol history can take days or weeks. RPC is instant. No deployment required.
Cost
Querying The Graph Network costs GRT tokens. Hosted service is being deprecated. For high-query-volume applications (dashboards serving many users), The Graph query costs can be significant.
Any contract, instantly
If a protocol launches a new contract, it is immediately accessible via RPC. Adding it to a subgraph requires a subgraph update, redeployment, and potentially re-syncing.
Private or custom chains
The Graph supports a set of chains. Chains not supported require running your own Graph node, significant infrastructure. RPC works on any EVM chain instantly.
The Decision Framework
Use this to decide for each piece of data your application needs:
Question: What data do I need?
↓
Is it current state (balance, price, contract state)?
→ YES → Direct RPC (eth_call)
Is it real-time events (watch for new swaps, transfers)?
→ YES → WebSocket subscription via RPC
Is it recent history (last few hours/days of events)?
→ YES → eth_getLogs via RPC
Is it deep historical data (months/years, millions of events)?
→ YES → The Graph
Is it aggregated stats (TVL, volume, APR)?
→ YES → The Graph
Does it involve complex entity relationships?
→ YES → The Graph
Does it need to work immediately without deployment?
→ YES → Direct RPC
Does the chain have an existing subgraph?
→ NO → Direct RPC (or build your own indexer)
Most Production DeFi Apps Use Both
A typical DeFi interface has multiple data layers, each best served by a different tool:
| UI Element | Data Source | Why |
|---|---|---|
| Current token prices | RPC (WebSocket) | Real-time, sub-second |
| Your current positions | RPC (eth_call) | Current state, instant |
| Protocol TVL / volume | The Graph | Historical aggregation |
| Recent swap history | RPC (eth_getLogs) | Last few hours, fast |
| Full position history | The Graph | Deep historical data |
| Transaction submission | RPC only | Only RPC can write |
| 30-day fee APR | The Graph | Pre-computed aggregation |
The read-write split is absolute: everything that writes to the chain goes through RPC. The read-only split depends on freshness vs depth requirements.
When to Use Only RPC
- Your application only needs current state (no historical data required)
- The contract is new and has no subgraph
- The chain is not supported by The Graph
- You need real-time data with sub-second freshness
- Initial sync time for a subgraph is not acceptable
- Query costs are a concern at scale
When to Use Only The Graph
- Your application is primarily analytics or a historical dashboard
- You need aggregated metrics (TVL, volume, APR across time)
- The data relationships are complex and span many events
- An existing subgraph already covers your protocol
- You do not need real-time data
FAQ
Is The Graph replacing RPC?
No. The Graph is an indexing layer built on top of RPC. The Graph’s own indexers use RPC to read blockchain data and build their indexes. You cannot submit transactions through The Graph, and real-time data (sub-minute freshness) always requires direct RPC.
What is a subgraph?
A subgraph is a configuration file that tells The Graph which smart contracts to watch, which events to index, and how to transform raw event data into queryable entities. Once deployed to The Graph Network, it is continuously updated as new blocks arrive.
Can I use The Graph for any chain?
The Graph supports a growing list of EVM-compatible chains including Ethereum, Arbitrum, Base, Polygon, Optimism, and others. Chains not yet supported require running your own Graph node or using alternative indexing solutions. Check thegraph.com for the current list.
What is the latency difference?
Direct RPC via WebSocket subscriptions can surface new events within seconds of a block being mined. The Graph typically lags 1–5 minutes behind the chain tip. For most analytics use cases this is acceptable. For real-time trading interfaces, it is not.
What if I need deep historical data but also real-time updates?
Use both. The Graph for historical context (position history, aggregated metrics, past performance). RPC WebSocket for real-time updates as new blocks arrive. This hybrid approach is the standard architecture for production DeFi interfaces.
BoltRPC provides JSON-RPC and WebSocket access to 20+ blockchain networks: the RPC layer your application needs for real-time reads, transaction submission, and everything The Graph does not handle.
Start your free 2-week trial: trial.boltrpc.io
Related: Ethereum RPC Methods Guide | WebSocket vs HTTP for Blockchain RPC