Membangun query yang efisien dengan The Graph: memahami batas query on-chain langsung, struktur subgraph (subgraph.yaml, schema.graphql, mappings), cara deploy, dan query GraphQL dari frontend plus alternatif SubQuery dan Goldsky

Setelah di episode 13 kita menyimpan data off-chain, pada episode ini kita menyelesaikan masalah data on-chain yang belum terjawab: bagaimana menanyakan riwayat on-chain secara efisien. Menampilkan "semua transfer USDC untuk address ini dalam 30 hari" lewat RPC langsung akan menyakitkan — itulah masalah yang dipecahkan indexer.
Mengapa episode ini penting? Karena hampir semua halaman DApp yang menampilkan daftar (history, leaderboard, koleksi NFT user) dibangun di atas indexer. Tanpa The Graph, kalian akan menulis kode yang membaca ribuan blok secara manual — lambat dan mahal.
RPC node menawarkan akses ke state saat ini dan log, tetapi:
Indexer menyelesaikannya dengan memindai blockchain sekali, menyimpan data dalam bentuk query-friendly (biasanya Postgres), lalu menyajikan lewat GraphQL.
Subgraph adalah "index config" untuk The Graph. Tiga file inti:
specVersion: 1.0.0
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum/contract
name: Token
network: sepolia
source:
address: "0x0000000000000000000000000000000000000001"
abi: ERC20
startBlock: 123456
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Transfer
abis:
- name: ERC20
file: ./abis/ERC20.json
eventHandlers:
- event: Transfer(indexed address,indexed address,uint256)
handler: handleTransfer
file: ./src/mapping.tstype Transfer @entity {
id: ID!
from: Bytes!
to: Bytes!
value: BigInt!
blockNumber: BigInt!
blockTimestamp: BigInt!
}Mappings adalah kode yang berjalan saat event terjadi (AssemblyScript, dan Rust untuk ekosistem non-EVM):
import { Transfer as TransferEvent } from "../generated/Token/ERC20"
import { Transfer } from "../generated/schema"
export function handleTransfer(event: TransferEvent): void {
const entity = new Transfer(
event.transaction.hash.concatI32(event.logIndex.toI32()),
)
entity.from = event.params.from
entity.to = event.params.to
entity.value = event.params.value
entity.blockNumber = event.block.number
entity.blockTimestamp = event.block.timestamp
entity.save()
}graph init --studio my-subgraph
cd my-subgraph
graph codegen && graph build
graph deploy --studio my-subgraph --version-label 0.0.1The Graph menyediakan endpoint GraphQL yang bisa diquery seperti API biasa:
import { request } from "graphql-request"
const SUBGRAPH_URL = "https://api.studio.thegraph.com/query/1234/my-subgraph/v0.0.1"
const query = `
query TransferHistory($from: Bytes!) {
transfers(where: { from: $from }, orderBy: blockTimestamp, orderDirection: desc, first: 20) {
id
to
value
blockTimestamp
}
}
`
const data = await request(SUBGRAPH_URL, query, { from: userAddress })Pattern yang harus kalian kenal: query via useQuery dari TanStack Query, dengan key subgraph+params untuk caching. Ini pola yang sama seperti fetching API biasa — bedanya sumber datanya indexer.
The Graph adalah standar de facto, tetapi bukan satu-satunya:
| Alat | Karakter |
|---|---|
| The Graph | Ekosistem terbesar, hosted studio + decentralized network |
| SubQuery | Alternatif yang fokus cepat & sederhana |
| Goldsky | Indexing real-time + streaming, populer di production L2 |
viem getLogs | Untuk query kecil/sekali pakai tanpa indexer |
Untuk data sederhana dan sekali pakai, getLogs cukup; untuk halaman production, gunakan indexer.
Tip
Aturan praktis: jika halaman kalian butuh daftar/filter/aggregasi data on-chain → subgraph. Jika hanya butuh saldo saat ini → read contract biasa sudah cukup. Menaruh indexer untuk hal sepele justru menambah beban yang tidak perlu.
Rangkuman yang sudah kalian pelajari di episode 14:
useQuery.getLogs untuk kebutuhan kecil.Inti yang harus dibawa pulang:
Di episode 15 selanjutnya, kita membahas RPC & infrastructure — perbandingan provider (Alchemy/Infura/QuickNode/Ankr), rate limiting, fallback providers dengan viem, opsi node publik vs dedicated, serta tren light client di 2026. Sampai jumpa di episode 15!