1/5
## Connecting Indexed Blockchain Data to Your Frontend In modern web3 applications, efficiently displaying blockchain data on a frontend is crucial. Directly querying the blockchain for historical information like NFT listings or sales is often slow and resource-intensive. This is where blockchain indexers like rindexer shine. Having previously set up rindexer to listen for smart contract events (`ItemListed`, `ItemBought`, `ItemCanceled`) and store them in a PostgreSQL database, our next step is to bridge this indexed data to our Next.js frontend, specifically to display recently listed NFTs on our marketplace homepage. ## Introducing the rindexer GraphQL API rindexer simplifies data retrieval by automatically providing a GraphQL API alongside its indexing service. When you run rindexer using the command `rindexer start all`, it not only starts the indexer process but also spins up a GraphQL server, typically accessible at `http://localhost:3001/graphql` by default. GraphQL is a powerful query language for APIs that allows clients (like our Next.js frontend) to request *exactly* the data they need, and nothing more. This contrasts with traditional REST APIs, which often return fixed data structures, potentially leading to over-fetching or requiring multiple requests. rindexer generates the GraphQL schema automatically based on the events you've configured it to index, making your indexed data readily available through this structured API. ## Exploring the Schema with the GraphQL Playground Before writing frontend code, it's essential to understand the structure of the data available through the GraphQL API. rindexer provides an invaluable tool for this: the GraphQL Playground. Access it by navigating to `http://localhost:3001/playground` in your browser while the rindexer GraphQL server is running (`rindexer start all`). The Playground offers several key features: 1. **Interactive Query Building:** You can write GraphQL queries directly in the left panel and execute them against your live indexed data by pressing the "Play" button. The results appear in the middle panel. 2. **Schema Exploration:** The "Schema" tab on the right allows you to inspect the complete GraphQL schema. You can browse available queries, mutations (if any), and data types, including all the fields defined for your indexed events. Viewing the Schema Definition Language (SDL) provides a concise, textual representation of the entire API structure, which is useful for understanding data relationships and even for providing context to AI code generation tools. 3. **Documentation:** Auto-generated documentation for each field and type is often available directly within the schema browser. Let's try a basic query to fetch listed items: ```graphql query MyFirstQuery { allItemListeds { # Query generated by rindexer for ItemListed events nodes { # Access the list of results nftAddress # Request the NFT contract address tokenId # Request the token ID price # Request the listing price seller # Request the seller's address } } } ``` Executing this in the Playground will return a JSON object containing a list (`nodes`) of the listed items with the specified fields, pulled directly from your indexed PostgreSQL data. Experimenting here helps you confirm data availability and refine your queries before integrating them into your application. ## Crafting a Query for Actively Listed NFTs Our goal is to display NFTs that are currently listed for sale. This means we need not only the `ItemListed` events but also information about whether those items were subsequently bought or canceled. We can fetch all this related data efficiently in a single GraphQL request. We'll construct a query that: 1. Fetches the most recent `ItemListed` events, ordered by block number and transaction index. 2. Fetches all `ItemBought` events. 3. Fetches all `ItemCanceled` events. The frontend will later use this combined data to filter out listed items that also appear in the bought or canceled lists for the same `nftAddress` and `tokenId`. Here's the GraphQL query we'll use, stored in a TypeScript constant: ```typescript const GET_RECENT_NFTS = ` query GetMarketplaceData { # Fetch the latest 20 listed items, newest first allItemListeds(first: 20, orderBy: [BLOCK_NUMBER_DESC, TX_INDEX_DESC]) { nodes { rindexerId # Unique ID from rindexer seller nftAddress price tokenId contractAddress # Smart contract emitting the event txHash blockNumber } } # Fetch all cancellation events (for filtering) allItemCanceleds { # Matches the event name indexed by rindexer nodes { nftAddress tokenId } } # Fetch all purchase events (for filtering) allItemBoughts { # Matches the event name indexed by rindexer nodes { tokenId nftAddress } } } `; ``` This query fetches the 20 most recent listings (`first: 20`, `orderBy: [BLOCK_NUMBER_DESC, TX_INDEX_DESC]`) along with *all* cancellation and purchase events. We fetch all bought/canceled events because a recently listed item could have been canceled or bought in a much older transaction if it was listed previously. Note the specific query names like `allItemListeds`, `allItemCanceleds`, `allItemBoughts` are generated by rindexer based on your event names. You can confirm these exact names in the GraphQL Playground's schema explorer. ## Proxying GraphQL Requests in Next.js To avoid hardcoding the rindexer GraphQL endpoint (`http://localhost:3001/graphql`) directly in our frontend code and to manage different URLs for development and production environments, we can use Next.js's built-in rewrite functionality. This acts as a proxy. Modify your `next.config.js` (or `next.config.ts`) file to include a rewrite rule: ```javascript // next.config.js const nextConfig = { // ... other configurations async rewrites() { return [ { source: '/api/graphql', // The path your frontend will use destination: process.env.GRAPHQL_API_URL || 'http://localhost:3001/graphql', // The actual rindexer GraphQL endpoint }, ]; }, // ... other configurations }; module.exports = nextConfig; ``` This configuration tells Next.js that any request made from the frontend to `/api/graphql` should be forwarded to the URL specified in the `GRAPHQL_API_URL` environment variable, or fallback to `http://localhost:3001/graphql` if the variable isn't set. Now, our frontend components can consistently fetch data from `/api/graphql` regardless of the underlying backend location. Remember to restart your Next.js development server after changing this configuration. ## Fetching GraphQL Data in a React Component With the query defined and the proxy set up, we can now create a function within our frontend codebase (e.g., near the `RecentlyListed.tsx` component) to actually fetch the data. We'll use the standard browser `fetch` API. ```typescript async function fetchNFTs() { // We'll add type safety next const response = await fetch('/api/graphql', { // Target the proxied endpoint method: 'POST', headers: { 'Content-Type': 'application/json', // Essential for GraphQL 'Accept': 'application/json', }, body: JSON.stringify({ query: GET_RECENT_NFTS, // Pass our defined GraphQL query string // variables: {} // Add if your query uses GraphQL variables }), }); if (!response.ok) { // Handle HTTP errors (e.g., network issues, server errors) console.error("HTTP Error:", response.status, response.statusText); throw new Error(`HTTP error! status: ${response.status}`); } const jsonResponse = await response.json(); if (jsonResponse.errors) { // Handle GraphQL errors (e.g., syntax errors in the query) console.error("GraphQL Errors:", jsonResponse.errors); throw new Error(`GraphQL error: ${jsonResponse.errors.map((e: any) => e.message).join(', ')}`); } return jsonResponse; // Return the parsed JSON data (contains a 'data' key) } ``` This asynchronous function sends a POST request to our proxied endpoint `/api/graphql`. The GraphQL query is sent within the `body` of the request, structured as a JSON object with a `query` key. The function parses the JSON response and includes basic error handling for both HTTP and GraphQL-specific errors. ## Ensuring Type Safety with TypeScript Interfaces To leverage TypeScript's benefits like autocompletion and compile-time error checking, we should define interfaces that match the expected structure of the data returned by our `GET_RECENT_NFTS` query. ```typescript // Describes the detailed fields for a listed item interface NFTItem { rindexerId: string; seller: string; nftAddress: string; price: string; // Note: Blockchain values are often strings (BigInts) tokenId: string; contractAddress: string; txHash: string; blockNumber: string; // Often a string, may need parsing to number/BigInt } // Describes the minimal data needed for bought/cancelled items (for filtering) interface BoughtCancelled { nftAddress: string; tokenId: string; } // Describes the overall structure of the GraphQL response JSON interface NFTQueryResponse { data: { allItemListeds: { nodes: NFTItem[]; }; allItemCanceleds: { // Ensure these names match your actual schema/query nodes: BoughtCancelled[]; }; allItemBoughts: { // Ensure these names match your actual schema/query nodes: BoughtCancelled[]; }; }; // Optional: include 'errors' field if you want to type GraphQL errors errors?: Array<{ message: string; [key: string]: any }>; } // Now, update the fetch function signature async function fetchNFTs(): Promise<NFTQueryResponse> { // ... (fetch implementation as above) ... const jsonResponse = await response.json(); // ... (error handling as above) ... return jsonResponse as NFTQueryResponse; // Assert the type } ``` By defining `NFTItem`, `BoughtCancelled`, and `NFTQueryResponse`, we clearly document the expected data shape and enable TypeScript to catch potential inconsistencies if the API response structure changes or if we misuse the data in our component. We update the `fetchNFTs` function signature to return a `Promise<NFTQueryResponse>`. ## Preparing for Advanced Data Handling We now have a robust way to fetch the necessary data (`ItemListed`, `ItemBought`, `ItemCanceled` events) from our rindexer GraphQL API into our Next.js application using standard `fetch` and TypeScript. The next logical steps involve: 1. **Client-Side Filtering:** Implementing the logic within the `RecentlyListed.tsx` component (or a related hook) to process the data returned by `fetchNFTs`. This involves iterating through `allItemListeds.nodes` and removing any item whose `nftAddress` and `tokenId` combination also exists in either `allItemCanceleds.nodes` or `allItemBoughts.nodes`. 2. **State Management:** Integrating a data-fetching and state management library like `@tanstack/react-query`. This library simplifies handling loading states, error states, caching, background refetching, and synchronizing the fetched data with the React component's state, significantly reducing boilerplate code compared to managing `fetch` calls manually with `useState` and `useEffect`. Many projects using libraries like Wagmi might already have `@tanstack/react-query` installed as a dependency. While AI tools like DeepSeek can assist in generating boilerplate code (especially if provided with the GraphQL schema SDL and component structure), remember to carefully review and adapt any AI-generated code to ensure it aligns perfectly with your project's requirements, chosen libraries (like `fetch` vs. `graphql-request`), and coding standards.
A practical guide to connecting indexed blockchain data to your frontend. Learn how to leverage rindexer's GraphQL API to fetch indexed blockchain events for your Next.js application. Master schema exploration, query crafting, proxy setups, and type-safe data fetching techniques.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Course Overview
About the course
How to build full-stack web3 applications on ZKsync
JavaScript/TypeScript: viem, wagmi, synpress
Nodejs and pnpm
rindexer
Circle Compliance Engine and USDC
Fleek site hosting and CLI
How to build a static and dynamic React/Next.js site
How to leverage AI to code faster and more securely
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
DeFi Developer
$75,000 - $200,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Security researcher
$49,999 - $120,000 (avg. salary)
Last updated on May 15, 2025
Solidity Developer
Full-Stack Web3 Development Crash CourseDuration: 1h 12min
Duration: 1h 39min
Duration: 3h 08min
Duration: 1h 44min
Course Overview
About the course
How to build full-stack web3 applications on ZKsync
JavaScript/TypeScript: viem, wagmi, synpress
Nodejs and pnpm
rindexer
Circle Compliance Engine and USDC
Fleek site hosting and CLI
How to build a static and dynamic React/Next.js site
How to leverage AI to code faster and more securely
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
DeFi Developer
$75,000 - $200,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Security researcher
$49,999 - $120,000 (avg. salary)
Last updated on May 15, 2025