6.8 KiB
6.8 KiB
GraphQL Querying
Query indexed data via GraphQL. Works locally during development or on hosted deployments.
Local endpoint: http://localhost:8080/v1/graphql
Hasura Console: http://localhost:8080 (password: testing)
Checking Indexing Progress
Always check sync status first before assuming data is missing.
Using _meta (Recommended)
curl -s http://localhost:8080/v1/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ _meta { chainId startBlock progressBlock sourceBlock eventsProcessed isReady } }"}'
Or in GraphQL:
{
_meta {
chainId
startBlock
progressBlock
sourceBlock
eventsProcessed
isReady
readyAt
}
}
Fields:
progressBlock- Last fully processed blocksourceBlock- Latest known block from data source (target)eventsProcessed- Total events processedisReady-truewhen historical sync is completereadyAt- Timestamp when sync finished
Example response:
{
"_meta": [
{
"chainId": 1,
"progressBlock": 22817138,
"sourceBlock": 23368264,
"eventsProcessed": 2380000,
"isReady": false
}
]
}
Filter by Chain
{
_meta(where: { chainId: { _eq: 1 } }) {
progressBlock
isReady
}
}
Using chain_metadata
More detailed chain information:
curl -s http://localhost:8080/v1/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ chain_metadata { chain_id start_block latest_processed_block num_events_processed is_hyper_sync } }"}'
Additional fields:
is_hyper_sync- Whether using HyperSync (fast) or RPClatest_fetched_block_number- Latest block fetched from sourcenum_batches_fetched- Number of batches processed
Basic Queries
Query Entities
curl -s http://localhost:8080/v1/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ Transfer(limit: 10) { id from to amount blockNumber } }"}'
With Ordering
{
Transfer(order_by: { blockNumber: desc }, limit: 10) {
id
from
to
amount
}
}
With Filters
{
Transfer(where: { from: { _eq: "0x123..." } }, limit: 100) {
id
from
to
amount
}
}
Filter by Chain (Multichain)
curl -s http://localhost:8080/v1/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ Transfer(where: {chainId: {_eq: 42161}}, limit: 10) { id chainId from to amount } }"}'
Filter Operators
| Operator | Description | Example |
|---|---|---|
_eq |
Equals | {field: {_eq: "value"}} |
_neq |
Not equals | {field: {_neq: "value"}} |
_gt |
Greater than | {amount: {_gt: "100"}} |
_gte |
Greater than or equal | {amount: {_gte: "100"}} |
_lt |
Less than | {amount: {_lt: "100"}} |
_lte |
Less than or equal | {amount: {_lte: "100"}} |
_in |
In list | {chainId: {_in: [1, 10]}} |
_nin |
Not in list | {chainId: {_nin: [1]}} |
_is_null |
Is null | {field: {_is_null: true}} |
_like |
Pattern match | {id: {_like: "1_%"}} |
_ilike |
Case-insensitive pattern | {user: {_ilike: "%abc%"}} |
Important: BigInt values must be quoted strings: {amount: {_gt: "1000000000000000000"}}
Logical Operators
# AND - all conditions must match
where: { _and: [{ chainId: { _eq: 1 } }, { amount: { _gt: "0" } }] }
# OR - any condition matches
where: { _or: [{ from: { _eq: "0x123" } }, { to: { _eq: "0x123" } }] }
# NOT - negate condition
where: { _not: { amount: { _eq: "0" } } }
Pagination
Limit and Offset
{
Transfer(limit: 100, offset: 200) {
id
}
}
Cursor-based (by primary key)
{
Transfer(limit: 100, where: { id: { _gt: "last_seen_id" } }, order_by: { id: asc }) {
id
}
}
Common Query Patterns
Recent Transfers for User
query UserTransfers($address: String!) {
Transfer(
where: {
_or: [
{ from: { _eq: $address } },
{ to: { _eq: $address } }
]
}
order_by: { blockTimestamp: desc }
limit: 50
) {
id
from
to
amount
blockTimestamp
}
}
Polling for Updates
query NewTransfers($lastTimestamp: BigInt!) {
Transfer(where: { blockTimestamp: { _gt: $lastTimestamp } }) {
id
from
to
amount
blockTimestamp
}
}
Get by Primary Key
curl -s http://localhost:8080/v1/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ Transfer_by_pk(id: \"1_0xabc..._0\") { id from to amount } }"}'
Discovering Schema
List All Query Types
curl -s http://localhost:8080/v1/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { queryType { fields { name } } } }"}'
Get Entity Fields
curl -s http://localhost:8080/v1/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ __type(name: \"Transfer\") { fields { name type { name } } } }"}'
Aggregations
Local: Aggregation queries work in Hasura console.
Hosted Service: Aggregations are disabled for performance.
Best Practice: Compute aggregates at indexing time:
# Schema
type GlobalStats {
id: ID!
totalTransfers: Int!
totalVolume: BigDecimal!
}
// Handler - update on each transfer
const stats = await context.GlobalStats.get("global");
context.GlobalStats.set({
...stats,
totalTransfers: stats.totalTransfers + 1,
totalVolume: stats.totalVolume.plus(transferAmount),
});
Then query precomputed values:
{
GlobalStats(where: { id: { _eq: "global" } }) {
totalTransfers
totalVolume
}
}
Hasura Console
Open http://localhost:8080 for the visual interface.
API Tab:
- Execute GraphQL queries
- Explorer shows all entities
- Test queries before frontend integration
Data Tab:
- View database tables directly
- Check
db_write_timestampfor freshness - Manually inspect entities
Fetch from Code
const response = await fetch('http://localhost:8080/v1/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `
query {
Transfer(limit: 10) {
id
from
to
amount
}
}
`
})
});
const data = await response.json();
Best Practices
- Check
_metafirst - Verify indexer progress before assuming data is missing - Fetch only needed fields - Reduces response size
- Use pagination - Never query unlimited results
- Filter on indexed fields - Use
@indexcolumns for faster queries - Poll with timestamps - Fetch only new data for real-time updates
- Precompute aggregates - At indexing time, not query time
- BigInt as strings - Always quote large numbers in filters