Building a fullstack web3 application with NextJS + The Graph

Are you curious about how to handle data requests when querying the blockchain? In this guide, we will walk through the process of building a web3 full-stack web application, covering tools like graph-client and urql to access and manage data from decentralized networks efficiently. Whether you are a seasoned developer or just starting in the web3 space, this guide will provide the steps to create a front-end app consuming data from the blockchain.

Web3 full-stack development is a relatively new subject. In web2, it’s all about centralized servers and HTTP requests. Consuming data from an API is a standardized process that most full-stack developers begin to learn in their early coding days.

But in decentralized applications, the data we want to consume lives on the blockchain, and querying is complex for several reasons.

  • Scalability: As the number of users and transactions on the blockchain increases, it can become more difficult to query the blockchain promptly.
  • Data structure: The data on the blockchain is often stored in a complex and non-relational format, making it difficult to query and extract specific information.
  • Decentralized nature: The blockchain is a decentralized network, so there is no central point of control, and querying requires a node to which not everyone has access to
  • How do we solve this querying issue?

    Enter The Graph

    The Graph in an indexing protocol used to query decentralized networks, like Ethereum. Instead of querying directly from the blockchain or keeping our centralized server, we can rely on The Graph by creating and deploying a subgraph, which will index a determined contract (or several, more on that later). Each subgraph is a tiny slice of the blockchain that we can query using GraphQL. Why is it better than a centralized solution? The Graph uses a structure of indexers and curators that helps it achieve decentralization and data consistency, making querying the blockchain easier without sacrificing its main benefits. I recommend this article for a deeper understanding of how The Graph works.

    By the end of this tutorial, we should grasp how The Graph works, how to use subgraphs to query and consume data from a frontend, and which tools can be used to make the process simpler.

    Project overview

    In this guide, we will build a simple front-end application that queries data from a subgraph. For our data, we will index ENS contracts. ENS stands for Ethereum Name Service and is a known project that mints .eth domains as ERC721 tokens. I’m choosing this project because it has a lot of movement and transactions, which translates into yummy data that we can use to fuel our front-end app. We will index two ENS smart contracts to create an ENS explorer app that shows the latest transactions and historic transaction amounts per day.

    Pre-requisites:

  • General blockchain knowledge
  • Some React knowledge
  • A grasp of how smart contracts work
  • Having a wallet setup (this is desired, but I’ll provide options to avoid using it if necessary)
  • Tool overview

    Project structure

    For project scaffolding, we will have two folders, one containing our front-end and everything we need for rendering and querying data. The other will include the local version of our subgraph.

    Our first step is to create a folder to keep both the front-end and subgraph as subfolders

    bash code
    mkdir ens-fullstack-app
    cd ens-fullstack-app

    Local subgraph setup

    After creating our parent folder is time to initialize our subgraph; for that, we’ll be using The Graph’s official CLI: graph-cli

    bash code
    $ npm install -g @graphprotocol/graph-cli

    To initialize our subgraph we run the following command:

    bash code
    graph init

    The prompt will guide us trough the various options, here are the recommended settings:

    makefile code
    Protocol: Ethereum
    Product: subgraph studio
    /* subgraph-slug is an identifier for the subgraph, we will need it later in Subgraph Studio */
    Subgraph-slug: ens-explorer
    Directory: just press enter
    Ethereum network: mainnet
    Contract address: 0x283Af0B28c62C092C9727F1Ee09c02CA627EB7F5
    Contract Name: ETHRegistrarController
    Index contract events as entities (Y/n): true
    Add another contract (Y/n): false

    Checking “index contract events as entities” is an excellent choice since it will generate an initial schema based on the contract and creates a mapping for each event the contract emits. This option saves us a lot of work by creating a subgraph structure that can be deployed as-is.

    Let’s explore the generated files:

    The important files are:

  • subgraph.yml
  • - The subgraph config file, generated with all the options we chose during init
  • schema.graphql:
  •  Here are all the entities that can query from our subgraph with their respective properties. Since we chose “index contract events as entities, the initial schema is generated automatically based on the contract we are using. We can also add new entities, which our subgraph will track if needed.
  • mapping file
  • - This file will be generated inside the src folder, with the same name as the contract name. It contains handler functions for each contract event.
  • Before deploying, let’s make a small configuration change: let’s open subgraph.yml and under source, add startBlock

    yaml code
    dataSources:
      - kind: ethereum
        name: ETHRegistrarController
        network: mainnet
        source:
          address: "0x283Af0B28c62C092C9727F1Ee09c02CA627EB7F5"
          abi: ETHRegistrarController
    			# ETHRegistrarController was deployed on block 9380471, querying prior to that makes no sense
          startBlock: 9380471

    Why are we doing this? By default, The Graph indexes from a blockchain’s first block. It’s always a good idea to start at the block where our contract was deployed to avoid indexing blocks that won’t generate entities

    Creating a subgraph in Subgraph Studio

    Go to Subgraph Studio and connect your wallet, you’ll be requested to sign a transaction to ensure that you have actual access to that wallet. Don’t worry, it’s free.

    Creating the subgraph

    Once the wallet is properly connected we can hit the “Create subgraph” button which will open the following UI:

    Enter your details, and keep in mind that “subgraph name” should be the same value you used for your subgraph slug. Once you are done hit “Create Subgraph”.

    Your subgraph is now created and you can access the deploy key:

    Now let’s go back to our terminal and run some commands to generate all code and deploy

    makefile code
    graph auth --studio [YOUR_DEPLOY_KEY]
    graph codegen && graph build
    graph deploy --studio [YOUR_SUBGRAPH_SLUG]

    When requested for a version just type 0.0.1 and hit enter. Once the deploy finished a success message should appear along with an URL for queries. Save it as we will need it to query from the frontend.

    Now if we go back to Subgraph Studio status should have changed to deployed

    We should also see our development query URL (the same link we saved from the CLI) and a tab “Playground” where we can test our queries:

    Keep in mind that synching may take a while, and not all data will be available, but we can still start using the subgraph. If there are no results after a couple of minutes, check the Logs tab and verify that indexing starts at the desired block.

    Our subgraph is now deployed and ready to use, let’s move on to the next task.

    Building the dApp

    Now that our data source is ready and deployed, we can build a decentralized application. For that, we will be using NextJS.

    NextJS is a ReactJS framework that provides a very easy quickstart to creating frontend apps. There are some things we need to take into account while developing:

  • NextJS delivers us with a development server with hot reloading; its default location is 
  • http://localhost:3000/
  • It has a filesystem approach to routing. It provides a pages folder where all page files live, each file corresponding to a route in our app.
  • Global CSS imports are only allowed inside the entry point file _app.js. To import CSS files into other files, NextJS uses CSS modules to ensure each generated HTML has its particular CSS. 
  • More on how CSS modules work
  • .
  • Getting started

    Let’s begin by creating our app with default settings:

    bash code
    npx create-next-app —-typescript

    Follow the prompts in the terminal by accepting the default unless you want to change anything. When finished, a success message should appear. NextJS will also initialize the folder as a repository if you're going to start versioning your changes immediately.

    Now that we have our starting code, we’ll begin by laying out the app's structure and cleaning up a little bit. First, we’ll go to globals.css and replace NextJS-generated content with the following:

    css code
    /* global.css */
    
    :root {
      --general-border: #e7eaf3;
      --font-primary: #001001;
      --general-background: #f8f9fa;
      --blue-accent: #3498db;
    }
    
    * {
      box-sizing: border-box;
      padding: 0;
      margin: 0;
    }
    
    html,
    body {
      max-width: 100vw;
      overflow-x: hidden;
      font-family: "Lucida Sans", "Lucida Sans Regular", "Lucida Grande",
        "Lucida Sans Unicode", Geneva, Verdana, sans-serif;
      background-color: var(--general-background);
    }
    
    a {
      color: var(--blue-accent);
      text-decoration: none;
    }
    
    a:hover {
      text-decoration: underline;
      cursor: pointer;
    }

    This way, we’ll eliminate unused code and have some readymade color variables for our components. For folder structure, I like to keep a components folder with subfolders pointing to all the frontend components. Each subfolder will then contain an index.tsx and style.module.css per component, making the code easier to import.

    So in our dapp folder at the root level, create the component folder, and inside, create a subfolder named Container.

    Let’s now create index.tsx and styles.module.css:

    typescript code
    // dapp/components/Container/index.tsx
    
    import React from "react";
    import style from "./styles.module.css";
    import Link from "next/link";
    
    type ContainerProps = {
      title: string;
      children: React.ReactNode[] | React.ReactNode;
    };
    export default function Container({ title, children }: ContainerProps) {
      return (
        <>
          <nav className={style.navbar}>
            <Link href="/">ENS Explorer</Link>
          </nav>
          <main className={style.mainContainer}>
            <section>
              <h2>{title}</h2>
              {children}
            </section>
          </main>
        </>
      );
    }
    css code
    /* dapp/components/Container/styles.module.css */
    
    .navbar {
      height: 60px;
      border-bottom: 1px solid var(--general-border);
      color: var(--primary-font);
      padding: 20px;
    }
    
    .mainContainer {
      padding: 30px;
      min-height: 100vh;
      color: var(--primary-font);
      width: 100%;
    }
    
    .mainContainer h1 {
      margin-bottom: 20px;
    }
    
    .mainContainer h2 {
      margin-bottom: 20px;
    }

    Here we have a simple layout component that will render a navbar with a <Link> home.

    We also have two dynamic props: title, a text string, and children, a collection of ReactNodes. Because of how NextJS handles routes and limits global CSS, this approach of making a general layout using component composition works well to avoid repeating styles on pages.

    Let’s add this component to our app. In pages/index.ts, replacing NextJS generated homepage with our own:

    typescript code
    // pages/index.tsx
    
    import Head from "next/head";
    import Container from "@/components/Container";
    
    export default function Home() {
      return (
        <>
          <Head>
            <title>ENS Explorer</title>
            <meta name="description" content="ENS Explorer" />
            <meta name="viewport" content="width=device-width, initial-scale=1" />
            <link rel="icon" href="/favicon.ico" />
          </Head>
          <Container title="ENS txs">
          </Container>
        </>
      );
    }

    If we run the project, we’ll see something like this; not very exciting, but it’s a nice start to fill it with data!

    Here we want to showcase a list of the latest transactions of ENS names. For that we need to query from our subgraph

    Querying the subgraph with graph-client and urql

    As I mentioned in the intro, we will be using two things to complement our querying work in nextjs,

  • Graph-client aims to simplify the network aspect of data consumption for dApps by giving us tools for querying subgraphs, schema generation, optimizing queries, and handling things like block tracking, polling, and pagination
  • urql is a GraphQL client library for React that provides tools and clients to handle our queries in the frontend
  • Let’s install all needed dependencies. Inside our dapp folder:

    bash code
    npm install --save-dev @graphprotocol/client-cli

    One great thing is that graph-client generates all its code output on build-time, which means that our application will have access to it at runtime, with no extra steps required. That also means we’ll have to remember to build after changes, or we won’t see them impacting our schema.

    That being said, let’s add an npm script to simplify bulding

    in our dapp’s package.json:

    json code
    "scripts": {
        "dev": "next dev",
        "build": "next build",
        "start": "next start",
        "lint": "next lint",
        "build-graphclient": "graphclient build"
      },

    Now we need to create graph-client’s configuration file. For that, just create a file named .graphclientrc.yml at the root of your project

    yaml code
    sources:
      - name: ETHRegistrarController
        handler:
          graphql:
            endpoint: [YOUR_DEVELOPMENT_QUERY_URL]

    We just added our data source pointing to our subgraph endpoint; if you don’t remember where to find yours go to https://thegraph.com/studio, enter your subgraph, and copy the generated development query URL

    With our configuration file ready is now time to build. We’ll use our freshly added npm script

    bash code
    npm run build-graphclient

    Sigamos en contacto: suscribite a Sin códigos, mi newsletter quincenal. También podés seguirme en redes para estar al tanto de todo mi nuevo contenido