Client-side data fetching with GraphQL and Next.js API Routes.

Learn how to fetch data client-side in your Next.js app using a third-party GraphQL endpoint.

Published2023-02-23
Reading Time3 min read

In a typical Next.js app, you'll likely fetch page data server-side using either getStaticProps or getServerSideProps. However, what if you need to fetch data client-side? You might not need every piece of data to be server-side rendered or statically generated, so this is a reasonable question to ask. Moreover, if you use a GraphQL client like Apollo or Urql, data fetching hooks such as useQuery()useQuery() will attempt to fetch data client-side by default whenever their cache for a query is empty or stale. In this post, we'll walk through a realistic data fetching scenario using a third-party GraphQL endpoint in a Next.js app and how to fetch data client-side while keeping private API keys hidden from the client.

  1. You have a Next.js app that uses a third-party GraphQL endpoint.
  2. You want to fetch this data client-side.
  3. You need to access this data using a private API key and don't want to expose it to the client.

Step 1: Configure your GraphQL client

For this example, we'll use GitHub's GraphQL API and graphql-request, but you could use any method you prefer. Here's how we create a GraphQL client with the necessary information to make our request to GitHub:

// graphql/client.ts
 
export const createGithubClient = () => {
  return new GraphQLClient('https://api.github.com/graphql', {
    headers: {
      Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
    },
  });
};
// graphql/client.ts
 
export const createGithubClient = () => {
  return new GraphQLClient('https://api.github.com/graphql', {
    headers: {
      Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
    },
  });
};

We pass in an Authorization header referencing our GITHUB_TOKEN environment variable, which will only be available on the server and not the client. It's best practice to keep API keys and secrets obscured from the client, and environment variables are an effective way to do so.

Step 2: Create our API route

Next, we create a Next.js API route to handle all requests to the GitHub GraphQL API from the client. Here's the code:

// pages/api/github.ts
 
import { createGithubClient } from '@/graphql/client';
import { Variables } from 'graphql-request';
import type { NextApiRequest, NextApiResponse } from 'next';
 
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
  // Verify that the request method is POST
  if (req.method !== 'POST') {
    res.status(405).end();
    return;
  }
 
  // Extract the query and variables from the request body
  const { query, variables } = req.body as {
    query: string;
    variables?: Variables;
  };
 
  try {
    // Initialize our GraphQL client
    const client = createGithubClient();
 
    // Make the request to GitHub's GraphQL API using our client
    const data = await client.request(query, variables);
 
    // Send the response data back to the client
    res.status(200).send(data);
  } catch (error) {
    // eslint-disable-next-line no-console
    console.error('error:', error);
    res.status(200).send('Server Error');
  }
};
 
export default handler;
// pages/api/github.ts
 
import { createGithubClient } from '@/graphql/client';
import { Variables } from 'graphql-request';
import type { NextApiRequest, NextApiResponse } from 'next';
 
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
  // Verify that the request method is POST
  if (req.method !== 'POST') {
    res.status(405).end();
    return;
  }
 
  // Extract the query and variables from the request body
  const { query, variables } = req.body as {
    query: string;
    variables?: Variables;
  };
 
  try {
    // Initialize our GraphQL client
    const client = createGithubClient();
 
    // Make the request to GitHub's GraphQL API using our client
    const data = await client.request(query, variables);
 
    // Send the response data back to the client
    res.status(200).send(data);
  } catch (error) {
    // eslint-disable-next-line no-console
    console.error('error:', error);
    res.status(200).send('Server Error');
  }
};
 
export default handler;

With this code, any requests to /api/github will be handled by this API route.

Step 3: Fetch data client-side

We'll use Urql for this example but you could use Apollo or any other data fetching method. Here's how we set up the client in our _app.tsx file:

// pages/_app.tsx
 
import { createClient, Provider } from 'urql';
 
const client = createClient({
  // The url of our API route
  url: '/api/github',
});
 
const MyApp = ({ Component, pageProps }: AppProps) => {
  return (
    <Provider value={client}>
      <Component {...pageProps} />
    </Provider>
  );
};
// pages/_app.tsx
 
import { createClient, Provider } from 'urql';
 
const client = createClient({
  // The url of our API route
  url: '/api/github',
});
 
const MyApp = ({ Component, pageProps }: AppProps) => {
  return (
    <Provider value={client}>
      <Component {...pageProps} />
    </Provider>
  );
};

Now we can use the useQuery hook to fetch data client-side. Here's an example of in our index.tsx file:

// pages/index.tsx
 
import { useQuery } from 'urql';
 
const query = `
  query { 
    user(login: "h-jennings") {
      name
      bio
    }
  }
`;
 
const Home = () => {
  const [{ fetching, error, data }] = useQuery({ query });
 
  if (fetching) {
    return <div>Loading...</div>;
  }
 
  if (error) {
    return <div>Oh no... {result.error.message}</div>;
  }
 
  const { user } = data;
  const { name, bio } = user;
 
  return (
    <div>
      <h1>{name}</h1>
      <p>{bio}</p>
    </div>
  );
};
 
export default Home;
// pages/index.tsx
 
import { useQuery } from 'urql';
 
const query = `
  query { 
    user(login: "h-jennings") {
      name
      bio
    }
  }
`;
 
const Home = () => {
  const [{ fetching, error, data }] = useQuery({ query });
 
  if (fetching) {
    return <div>Loading...</div>;
  }
 
  if (error) {
    return <div>Oh no... {result.error.message}</div>;
  }
 
  const { user } = data;
  const { name, bio } = user;
 
  return (
    <div>
      <h1>{name}</h1>
      <p>{bio}</p>
    </div>
  );
};
 
export default Home;

Conclusion

This is a very simple example of how you can fetch data client-side with Next.js using a third-party GraphQL endpoint and an api route. I hope this helps you get started with your own projects. If you have any questions or comments, feel free to reach out to me on Twitter @jennings_hunter.