7Block Labs
Blockchain Development

ByAUJay

Creating API for Web3 Dapps: REST, GraphQL, and JSON-RPC Compared

Concise summary: For modern Web3 products, you’ll rarely pick a single API pattern. The winning architecture combines JSON-RPC for writes and low-level reads, GraphQL for rich query joins and time‑travel reads, and REST from indexers for cross‑chain, user-centric data—while accounting for 2024–2025 protocol changes like Dencun (EIP‑4844) and partial history expiry (EIP‑4444). (eips.ethereum.org)


Why this matters now

Two protocol shifts changed how dapps should design APIs:

  • Ethereum Dencun (mainnet, March 13, 2024) added blob-carrying transactions (EIP‑4844), new block header fields (blob_gas_used, excess_blob_gas), and a separate “blob gas” fee market. Reads/writes against these new fields flow through the same node APIs you already use—but you must know exactly where to look. (theblock.co)
  • Partial history expiry under EIP‑4444 (rolling out across clients in 2025) means many nodes no longer retain older block bodies and receipts. That directly impacts long-range log scans and historic analytics—pushing more teams toward indexers and subgraphs. (blog.ethereum.org)

Decision-makers should treat API selection as an architecture choice, not a library setting.


TL;DR recommendations

  • Use JSON‑RPC when you need canonical node semantics, writes, mempool subscriptions, or chain-specific methods (e.g., Arbitrum’s arbtrace_*). (eips.ethereum.org)
  • Use GraphQL when you need to join block/tx/receipt/log data efficiently or do time‑travel queries at specific blocks (subgraphs). (eips.ethereum.org)
  • Use REST from indexers when you want product-ready, cross‑chain, wallet-centric endpoints (balances, NFTs, prices) with SLAs and fewer queries to stitch. (docs.moralis.com)

Combine them behind a single backend that handles auth, caching, replay protection, and rate-limits.


JSON-RPC: the canonical node interface

JSON‑RPC (per EIP‑1474) is the lingua franca for Ethereum and EVM L2 nodes. It’s transport-agnostic (HTTP, WebSocket, IPC) and exposes read/write methods: eth_call, eth_sendRawTransaction, eth_getLogs, eth_feeHistory, etc. (eips.ethereum.org)

What’s new/important in 2024–2025:

  • Dencun’s EIP‑4844 extends block headers with blob_gas_used and excess_blob_gas. You’ll see these via standard block retrieval methods; your fee logic for rollups may also consider blob fee dynamics. (eips.ethereum.org)
  • Many providers limit eth_getLogs ranges per request; assume you must page. Example: Alchemy caps block ranges by plan and response size, recommending filters and chunking. Chainstack similarly recommends conservative ranges by network. Build your log scanners accordingly. (alchemy.com)
  • Some clients and providers support eth_getBlockReceipts to fetch all receipts in a block efficiently—far cheaper than N× eth_getTransactionReceipt. Validate availability per client; Geth ≥1.13 and Erigon expose it. (docs.chainstack.com)
  • WebSocket subscriptions eth_subscribe (newHeads, logs, newPendingTransactions) are WS-only and ideal for live UX, but you must handle reorgs and dropped connections. (besu.hyperledger.org)
  • Tags "safe" and "finalized" are now widely documented for default block params—use them for consistency-sensitive reads (balances, nonces) to avoid reorg surprises. Note: some chains (e.g., Base) don’t expose these tags. (ethereum.org)
  • Access lists (EIP‑2930) plus eth_createAccessList can reduce gas and make estimates more reliable for complex txs. (eips.ethereum.org)

Security and hygiene:

  • Do not expose debug_* and trace_* on public endpoints; gate them by IP allowlists/mTLS. Many L2s expose chain‑specific trace namespaces (e.g., Arbitrum arbtrace_*). Treat them like admin APIs. (chainnodes.org)
  • Never ship node credentials to browsers. For public reads, rely on wallet providers (EIP‑1193) or a thin proxy without secrets; for writes and privileged reads, go server‑side with API secrets/JWTs and allowlists. (eips.ethereum.org)

Performance tactics:

  • Batch JSON‑RPC for N independent reads, but cap batch size and payload (~20–25 calls; ≤1 MB) to avoid DOS patterns and provider limits. Use HTTP/2 + keep‑alive. (json-rpc.dev)
  • For historic reads over a year old, do not assume the node has receipts/bodies (EIP‑4444). Use an archive/indexer or subgraph. (blog.ethereum.org)

Example: read blob fee context with EIP‑4844

  • Fetch a block and inspect blob_gas_used/excess_blob_gas via eth_getBlockByNumber; update your L2 posting cost model accordingly. These fields are defined in the spec and surfaced in client responses. (eips.ethereum.org)

GraphQL: precise, composable reads (two flavors)

There are two GraphQLs you’ll meet in Web3:

  1. Client GraphQL (EIP‑1767): Geth and Besu expose read‑only GraphQL endpoints that can reduce overfetching on common queries (e.g., “give me a block and all receipts”). Besu’s GraphQL is HTTP‑only (no WS), so keep subscriptions on JSON‑RPC. Enable flags vary by client. (eips.ethereum.org)

  2. The Graph subgraphs: A higher‑level GraphQL over indexed data you define. You get filtering, ordering, pagination, and time‑travel queries at a specific block number/hash—perfect for analytics, app feeds, and historical state. (thegraph.com)

What’s new/important:

  • Substreams‑powered subgraphs massively accelerate indexing and backfills, letting teams sync datasets that previously took weeks in hours; The Graph cites 70×+ speedups on complex subgraphs (e.g., Uniswap v3) and production‑grade streaming with Firehose. (thegraph.com)
  • Time‑travel GraphQL queries let you confidently query state at block N across chains with reorg semantics documented; prefer finalized or sufficiently old blocks for deterministic results. (thegraph.com)

When to pick GraphQL:

  • You need to join across blocks/txs/logs/accounts without dozens of RPC round‑trips.
  • You need stable historical snapshots (block‑scoped reads) or rich filtering/sorting.
  • You want an API contract that frontends can collaborate on and evolve.

Limitations:

  • Node GraphQL is read‑only; mutations (sending txs) remain JSON‑RPC. Besu’s implementation is HTTP‑only. (besu.hyperledger.org)

Example: “one call, many receipts”

  • Node GraphQL can return all tx receipts for a block in a single query; contrast that with N JSON‑RPC calls if eth_getBlockReceipts isn’t available in your environment. (eips.ethereum.org)

REST from indexers: product‑level, cross‑chain data fast

Indexers (e.g., Moralis, SimpleHash, others) expose REST APIs for wallet balances, NFT portfolios, token prices, holders, transfers, approvals, and more—often across 30+ chains. This is the fastest path to user‑facing features without building your own indexer. (docs.moralis.com)

Concrete capabilities:

  • Wallet balances (native + ERC‑20) and prices in one call; historical snapshots by block. (docs.moralis.com)
  • NFT ownership and summaries for token‑gates. (docs.simplehash.com)
  • Transfers, label enrichment, and decoded method signatures to power activity feeds. (moralis.com)

Tradeoffs:

  • You accept vendor limits and pagination models.
  • You may lose chain‑specific nuance or newest opcodes until provider updates.
  • For strict determinism or audit, fall back to your nodes/subgraphs when needed.

Emerging patterns we deploy for clients

At 7Block Labs, we’re standardizing the following pattern for 2025‑ready dapps:

  • Writes and low‑level reads via JSON‑RPC:
    • Send signed txs with EIP‑1559 fee logic; consider eth_feeHistory + eth_maxPriorityFeePerGas for envelope settings. For complex txs, precompute access lists with eth_createAccessList. (eips.ethereum.org)
    • Subscribe via WebSockets for newHeads/logs; implement reconnection and reorg handling (e.g., buffer last 12 blocks and reconcile on reorg). (besu.hyperledger.org)
    • For log backfills, chunk queries to provider‑recommended ranges and cap response size; dedupe by (blockHash, logIndex). (docs.chainstack.com)
  • Read models via GraphQL:
    • Build subgraphs for your domain entities; use time‑travel reads (block: { number }) for deterministic UIs and backtests. Consider Substreams for heavy protocols to shrink syncs from days to hours. (thegraph.com)
  • User‑centric features via REST indexers:
    • One call for wallet portfolio, approvals, entities/labels, and net worth; cache aggressively at the edge. (moralis.com)

Practical examples (copy/paste ready)

  1. JSON‑RPC log backfill with safety
  • Use “safe” block tags for consistency if your chain supports them; otherwise lag your toBlock by ~2 blocks.
  • Page logs per provider limits (e.g., 2–10k blocks typical) and filter by topics and address to keep payloads small. (ethereum.org)
  1. Read blob gas context post‑Dencun
  • Fetch a recent block:
    • eth_getBlockByNumber -> parse blob_gas_used and excess_blob_gas for analytics and rollup cost dashboards; alert if persistent excess_blob_gas suggests rising blob base fees. (eips.ethereum.org)
  1. Get all receipts in a block efficiently
  • If your client/provider supports it:
    • eth_getBlockReceipts "0x…block" -> iterate receipts to derive contract deployments (receipt.contractAddress != null). (docs.chainstack.com)
  1. Node GraphQL: block with nested receipts (one round‑trip)
  • Query a specific block and select only fields you need; this cuts disk reads vs multiple RPC calls. Supported in Geth/Besu. (eips.ethereum.org)
  1. Subgraph time‑travel read
  • Query your subgraph at a block number/hash to render “state as of” without reorg noise, e.g., for historical portfolio pages. (thegraph.com)
  1. REST: wallet portfolio in one call
  • Hit your indexer’s “wallet tokens” endpoint to pull ERC‑20 balances + prices; cache for 30–60 seconds and decorate with on‑chain deltas via mempool subscriptions. (docs.moralis.com)

Security checklist you can enforce this sprint

  • Browser integration:
    • Use the wallet provider API (EIP‑1193) and request accounts via eth_requestAccounts (EIP‑1102). Never embed node secrets in frontends. (eips.ethereum.org)
  • Backend to providers:
    • Enforce IP/domain allowlists and require API secrets/JWTs where supported; don’t enable secrets for browser origins (CORS will fail and secrets will leak). (support.infura.io)
  • Webhooks/streams:
    • Verify HMAC signatures on webhook payloads from your provider (Alchemy example uses X‑Alchemy‑Signature with HMAC‑SHA256). (alchemy.com)
  • Node hardening:
    • Disable debug/trace on public interfaces; separate admin port from public JSON‑RPC. Lock down WS with auth, and rate‑limit subscription creation. (besu.hyperledger.org)

Handling vendor limits and reorgs (operational playbook)

  • Rate limits and response caps:
    • Expect payload caps (e.g., 150 MB) and chain‑specific block‑range bounds; add automatic range splitting and exponential backoff on 429s/timeouts. (alchemy.com)
  • Reorg safety:
    • For live subscriptions, keep a sliding buffer of last N blocks. On a reorg, reconcile by block hash and re‑emit diffs to downstream consumers. Document UI expectations (e.g., “pending” vs “finalized”).
  • History expiry:
    • For >1‑year history, route to archive nodes or subgraphs. With clients pruning per EIP‑4444, don’t depend on random nodes for deep history. (blog.ethereum.org)

Chain- and stack‑specific nuances

  • L2-specific RPCs:
    • Arbitrum exposes arbtrace_* methods and transitions to debug_trace* after a specific block height—check docs when building debuggers or analytics. (quicknode.com)
  • Fee estimation:
    • Prefer eth_feeHistory + eth_maxPriorityFeePerGas, and fall back to network‑specific gas suggestion APIs where appropriate. (eips.ethereum.org)

Decision guide

Pick the primary API per workload, then add the others tactically:

  • Latency‑sensitive UX (swaps, mints, live positions)
    • JSON‑RPC WS subscriptions for heads/logs; JSON‑RPC for writes; optional REST overlays for portfolio snapshots.
  • Analytics and backtests
    • Subgraphs with time‑travel reads; Substreams for high‑volume protocols; JSON‑RPC for validation. (thegraph.com)
  • Wallets/enterprise dashboards
    • REST for portfolio/labels/prices; JSON‑RPC for precise balances at safe/finalized tags; GraphQL for complex joins. (ethereum.org)

Implementation blueprint (reference architecture)

  • Frontend:
    • EIP‑1193 provider; request accounts via eth_requestAccounts; chain switching via wallet_switchEthereumChain. (eips.ethereum.org)
  • API gateway (your backend):
    • Single ingress for REST/GraphQL/JSON‑RPC proxying.
    • Retry policy per method; batcher for reads; circuit breakers on heavy endpoints.
    • Cache tiers: in‑memory for hot keys (balances), short‑TTL edge cache for REST indexer responses.
    • Observability: tag every call with method, chainId, p95 latency, error code, payload bytes.
  • Data layer:
    • One or more node providers (multi‑region) for JSON‑RPC; WS channel pool for subscriptions.
    • Subgraph deployments for domain entities; Substreams for heavy protocols.
    • Indexer REST for cross‑chain user features.

What to pilot in Q1–Q2 2026

  • Migrate heavy backfills from raw eth_getLogs to:
    • Subgraphs (or Substreams‑powered subgraphs) for protocol‑specific views, or
    • Provider indexed logs endpoints with strict filters and block chunking. (thegraph.com)
  • Add “safe/finalized” read paths for balances and positions to stabilize UI. (ethereum.org)
  • Replace N× eth_getTransactionReceipt with eth_getBlockReceipts where supported; feature-detect at startup. (docs.chainstack.com)
  • Update fee estimator to combine eth_feeHistory with chain‑specific guidance for spikes; precompute access lists on complex transactions. (eips.ethereum.org)

In-depth: what Dencun (EIP‑4844) changes for APIs

  • New fields on every block (blob_gas_used, excess_blob_gas) are visible via standard JSON‑RPC “get block” calls; monitor them if you pay L1 DA fees from an L2. (eips.ethereum.org)
  • Blob data itself is ephemeral (~18 days) and gossiped via blob sidecars; your execution‑layer RPC won’t serve blob contents. Plan observability around costs, not blob bytes. (blog.ethereum.org)

In-depth: history expiry (EIP‑4444) and your data strategy

  • Execution clients can prune old receipts/bodies; you must not assume nodes can answer “give me logs from 2019.” Use archive specialists, subgraphs, or community history networks for deep history. (blog.ethereum.org)
  • Geth/Nethermind document pruning modes and Era1 sources; pin your ops runbooks to client docs and schedule pruning windows. (geth.ethereum.org)

Final take

For serious Web3 products, the optimal API surface is intentionally hybrid:

  • JSON‑RPC for writes, live subscriptions, and exact chain semantics.
  • GraphQL (clients or subgraphs) for efficient, composable reads and historical determinism.
  • REST indexers for cross‑chain, user‑centric features at product speed.

Design your gateway to speak all three—then evolve knobs (chunking, time‑travel, finalized tags) as the protocol landscape changes.


Sources and further reading

  • EIP‑1474 JSON‑RPC spec and EIP‑1193 provider API; use them as your baseline for wallet ↔ dapp ↔ node interactions. (eips.ethereum.org)
  • EIP‑1767 GraphQL; Geth/Besu GraphQL docs (HTTP‑only on Besu). (eips.ethereum.org)
  • Dencun / EIP‑4844: headers, fees, and blob behavior. (eips.ethereum.org)
  • EIP‑4444 partial history expiry; client pruning docs. (blog.ethereum.org)
  • Provider constraints on eth_getLogs and subscription guidance. (alchemy.com)

If you want 7Block Labs to pressure‑test your current API mix, we’ll map your workloads to the above blueprint and ship a backlog of low‑risk, high‑impact changes in under two weeks.

Like what you're reading? Let's build together.

Get a free 30‑minute consultation with our engineering team.

Related Posts

7BlockLabs

Full-stack blockchain product studio: DeFi, dApps, audits, integrations.

7Block Labs is a trading name of JAYANTH TECHNOLOGIES LIMITED.

Registered in England and Wales (Company No. 16589283).

Registered Office address: Office 13536, 182-184 High Street North, East Ham, London, E6 2JA.

© 2025 7BlockLabs. All rights reserved.