Next.js: Fetching data with graphql-request and SWR

A simple and lightweight strategy.

ยท

6 min read

In this tutorial we are going to use SWR and graphql-request library to fetch data from a GraphQL API.

Personally I choose these tools because they are really simple to use together and I haven't seem many examples with them.

Before getting started I'll give a brief introduction about the techs we are going to use.

  • SWR is an acronym of stale while revalidate. It is an awesome strategy to fetch data from the client, because SWR will automatically cache the data for us and will revalidate the data if it becomes stale.

  • GraphQL is a query language for APIs. With GraphQL you can query exactly data you want to receive in the client avoiding underfetching and overfetching problems.

  • graphql-request is a GraphQL client. It supports browsers and node.js. Unlike other clients like Relay or Apollo, graphql-request has no integrations with frameworks or built-in cache (this is the reason we are using SWR after all). This might sound like a drawback, but graphql-request is really easy to use and works nice for small applications.

Note: You can always access the source code here.

1. Starting the Application

In order to fetch the data we are going to start a Next.js project with a ready to use GraphQL api. You can clone the repository or you can follow this tutorial showing how to setup a GraphQL API with Next.js.

After downloading and the dependencies installed you can run npm run dev to start the app. If you navigate to localhost:3000/api/graphql you will be redirected to Apollo Explorer where you can create queries.

NOTE: This API is storing data in memory, so it will be deleted every time you stop the application.

2. Installing the Dependencies

npm i graphql graphql-request swr

3. Creating the first Request

We will import the useSWR hook to create our request. This hook can be imported from SWR library and it accepts three parameters as follows:

const response = useSWR(key, fetcher, options)
  • key is an unique identifier for the request.
  • fetcher (optional) is an asynchronous function that receives the key as parameter and retrieves the data from the API.
  • options (optional) is an object to add custom options to this hook.

Note: We are not going to talk about options anymore, you can check the available options here if you want.

In our case, the key will be the graphQL query and the fetcher will use the request method from graphql-request library. So let's add this to our index page.

// pages/index.tsx

import type { NextPage } from "next";
import { request, gql } from "graphql-request";
import useSWR from "swr";
import { Manga } from "../types/mangas";

const fetcher = (query: string) => request("/api/graphql", query);

const MANGAS_QUERY = gql`
  query Mangas {
    mangas {
      id
      name
    }
  }
`;

type FetchResponse = {
  mangas: Manga[];
};

const Home: NextPage = () => {
  const { data, error } = useSWR<FetchResponse>(MANGAS_QUERY, fetcher);

  return (
    <div>
      <ul>
         {data?.mangas.map((manga) => (
           <li key={manga.id}>
             <h3>{manga.name}</h3>
           </li>
         ))}
      </ul>
      {error && <span>Oops! Something went wrong.</span>}
    </div>
  );
};

export default Home;

As you can notice in the code above, we are requiring only the name and the id since we are going to use only this information to list the data.

If you run the app and navigate to localhost:3000/ you will be able to see the manga names on the screen.

NOTE: I suggest you to not create the queries directly on the code, you can use Apollo Explorer instead.

4. Provide the Fetcher Globally.

We are going o use the same fetcher through our application, thus to avoid passing the fetcher every time to the useSWR hook we can add a configuration in the _app.tsx.

// pages/_app.tsx

import type { AppProps } from "next/app";
import { SWRConfig } from "swr";
import request from "graphql-request";

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <SWRConfig
      value={{
        fetcher: (query: string) =>
          request("/api/graphql", query),
      }}
    >
      <Component {...pageProps} />
    </SWRConfig>
  );
}

export default MyApp;

After doing this, there is no need to specify the fetcher on useSWR hook anymore since it will be available globally.

// pages/index.tsx
{...}
const Home: NextPage = () => {
  const { data, error } = useSWR<FetchResponse>(MANGAS_QUERY);
{...}

5. Fetching Data with Variables.

Now we want to fetch data for every single manga, so let's create a dynamic route on pages/manga/[id].tsx. Next.js will automatically create a dynamic route and we can have access to the id property.

// pages/manga/[id].tsx

import { NextPage } from "next";
import { useRouter } from "next/router";

const Manga: NextPage = () => {
  const { query } = useRouter();
  return <div>{query.id}</div>;
};

export default Manga;

Lets refactor our index page to redirect to this dynamic route clicking in the manga name.

// pages/index.tsx

{ ... }
const Home: NextPage = () => {
  const { data, error } = useSWR<FetchResponse>(MANGAS_QUERY);

   return (
    <div>
      <ul>
        {data?.mangas.map((manga) => (
          <li key={manga.id}>
            <Link href={`/manga/${manga.id}`}>
              <a>
                <h3>{manga.name}</h3>
              </a>
            </Link>
          </li>
        ))}
      </ul>
      {error && <span>Oops! Something went wrong.</span>}
    </div>
  );
};
{ ... }

Now if you click in the manga link you will be redirected to the manga page and the manga id will be shown in the url param. Now it is possible to use this param to fetch information about the manga.

// pages/manga/[id].tsx

import { gql } from "graphql-request";
import { NextPage } from "next";
import { useRouter } from "next/router";
import useSWR from "swr";

const MANGA_QUERY = gql`
  query Mangas($mangaId: ID!) {
    mangaById(id: $mangaId) {
      name
      author
      score
      demographic
    }
  }
`;

const Manga: NextPage = () => {
  const {
    query: { id },
  } = useRouter();

  const variables = { mangaId: id };
  const { data, error } = useSWR([MANGA_QUERY, variables]);

  return (
    <div>


      {data?.mangaById && (
        <div>
          <h3>{data.mangaById.name}</h3>
          <p>Author: {data.mangaById.author}</p>
          <p>Score: {data.mangaById.score}</p>
          <p>Demographic: {data.mangaById.demographic}</p>
        </div>
      )}
      {error && <span>Oops! Something went wrong</span>}
    </div>
  );
};

export default Manga;

Here you can notice that we are passing an array as the first argument of the useSWR hook. By default the useSWR pass the key as argument to the fetcher, but we can pass an array with multiple arguments instead.

Opening this page on the browser will always show the error message. This is happening because the fetcher is not receiving the variables and passing it to the request method. So lets fix this adding the variables to the fetcher.

// pages/_app.tsx

{ ... }
function MyApp({ Component, pageProps }: AppProps) {
  return (
    <SWRConfig
      value={{
        fetcher: (query: string, variables) =>
          request("/api/graphql", query, variables),
      }}
    >
      <Component {...pageProps} />
    </SWRConfig>
  );
}

{ ... }

6. Conclusion

We were able to create requests with and without variables easily with SWR and graphql-request.

This is a simple and lightweight strategy to be implemented in small applications. SWR is responsible to cache and revalidate the data and graphql-request is very useful to simplify the requests.

ย