DEV Community

Abiodun A. Sulaiman
Abiodun A. Sulaiman

Posted on

GraphQL Query in NextJS with useSWR and graphql-request

NextJS has made server-side rendering cool and easy. It's almost irresistible not using it, as it comes with so many things out of the box.

In this post, we will be fetching remote data in a new NextJS App using a GraphQL API. We'll be using useSWR and graphql-request. We'll use Chakra UI to beautify our app and https://github.com/lennertVanSever/graphcountries for data source.

*Step 1: * Create a new app with next-app

yarn create next-app
# or
npm init next-app

Follow the prompt and choose Default starter app

Step 2: Install useSWR and graphql-request for data fetching
cd into the directory and install the dependencies

yarn add graphql-request swr
# or
npm install graphql-request swr

*Step 3: * Install Chakra UI(This step is optional, but Chakra UI is awesome)

yarn add @chakra-ui/core @emotion/core @emotion/styled emotion-theming
# or
npm install @chakra-ui/core @emotion/core @emotion/styled emotion-theming

Then, open the folder in your favourite editor

Step 4: Create a theme file, extending the default theme from Chakra UI
components/theme.js

import { theme } from "@chakra-ui/core";

const customTheme = {
  ...theme,
  breakpoints: ["30em", "48em", "62em", "80em"],
  fonts: {
    heading: '"Avenir Next", sans-serif',
    body: "system-ui, sans-serif",
    mono: "Menlo, monospace",
  },
  fontSizes: {
    xs: "0.75rem",
    sm: "0.875rem",
    md: "1rem",
    lg: "1.125rem",
    xl: "1.25rem",
    "2xl": "1.5rem",
    "3xl": "1.875rem",
    "4xl": "2.25rem",
    "5xl": "3rem",
    "6xl": "4rem",
  },
  colors: {
    ...theme.colors,
    brand: {
      900: "#1a365d",
      800: "#153e75",
      700: "#2a69ac",
    },
  },
};
export default customTheme;

NextJS uses file-based routing. So, every file in pages directory become a route, by default.
So, if we had theme.js in the pages directory, we would end up with a /theme route.
Hence, non-route files are to be placed in other directories outside of pages

Step 5: Create a layout file to inject the theme at the root of our application
components/layout.js

import { ThemeProvider, CSSReset } from "@chakra-ui/core";
import theme from "./theme";

export default ({ children }) => (
  <ThemeProvider theme={theme}>
    <CSSReset />
    {children}
  </ThemeProvider>
);

Step 6: Create a barebones homepage with static content
pages/index.js

import Head from "next/head";
import { Heading } from "@chakra-ui/core";
import Layout from "..components/layout";

export default function Home() {
  return (
    <>
      <Head>
        <title>Countries</title>
      </Head>
      <Layout>
        <Heading size="2xl" as="center">
          Countries
        </Heading>
      </Layout>
    </>
  );
}

Step 7: Import graphql-request and swr for data fetching
The pages/index.js import section should, then, look like this:

import Head from "next/head";
import { Heading, Grid, Box, Badge, Image } from "@chakra-ui/core";
import { request } from "graphql-request";
import useSWR from "swr";
import Layout from "../components/layout";

Step 8: Initialise the query and API endpoint:
pages/index.js

const API_ENDPOINT = 'https://api.graph.cool/simple/v1/movies'
const countriesQuery = `{
  Country{
    _id
    name
    capital
    populationDensity
    currencies {
      _id
      code
    }
    timezones {
      _id
      name
    }
    officialLanguages {
      _id
      name
    }
    flag {
        _id
      svgFile
    }
  }
}`

You can put your API endpoint in a .env file and you, probably, should. Link here: https://nextjs.org/docs/basic-features/environment-variables

Step 9: Make a request to the endpoint using SWR
Within the Home function before the return block:

const { data: countries, error } = useSWR(countriesQuery, (query) =>
    request(API_ENDPOINT, query)
  );

useSWR returns 2 values: data and error, based on the status of the request.
If you console.log({ countries, error }); at this point, you should see your data or error

**Step 10: **Prepare the component that will display the result
components/country.js

import { Box, Badge, Image } from "@chakra-ui/core";

const Country = ({ country }) => (
  <Box maxW="sm" borderWidth="1px" rounded="lg" overflow="hidden">
    <Image src={country.flag?.svgFile} alt={country.nativeName} />
    <Box p="6">
      <Box d="flex" alignItems="baseline">
        {country.currencies &&
          country.currencies.map((currency) => (
            <Badge rounded="full" px="2" variantColor="teal" key={currency._id}>
              {currency.name}
            </Badge>
          ))}
        <Box
          color="gray.500"
          fontWeight="semibold"
          letterSpacing="wide"
          fontSize="xs"
          textTransform="uppercase"
          ml="2"
        >
          {country.capital} &bull;
          {country.timezones &&
            country.timezones.map((timezone) => (
              <span key={timezone._id}>{timezone.name}</span>
            ))}
        </Box>
      </Box>
      <Box mt="1" fontWeight="semibold" as="h4" lineHeight="tight" isTruncated>
        {country.name}
      </Box>

      <Box>
        {country.populationDensity}
        <Box as="span" color="gray.600" fontSize="sm">
          / sq.km
        </Box>
      </Box>
      {country.officialLanguages &&
        country.officialLanguages.map((language) => (
          <Box as="span" color="gray.600" fontSize="sm" key={language._id}>
            <span>{language.name}</span>
          </Box>
        ))}
    </Box>
  </Box>
);
export default Country;

All we did in the above was destructure country from the component props and used the Box component given us by Chakra UI to make the UI.

*Step 11: * Display the data and error component
Replace pages/index.js with the code below:

import Head from "next/head";
import { Heading, Grid, Alert, AlertIcon } from "@chakra-ui/core";
import { request } from "graphql-request";
import useSWR from "swr";
import Layout from "../components/layout";
import Country from "../components/country";

const API_ENDPOINT = "https://countries-274616.ew.r.appspot.com/";
const countriesQuery = `query getCountries{
  Country{
    _id
    name
    capital
    populationDensity
    currencies {
      _id
      code
    }
    timezones {
        _id
      name
    }
    officialLanguages {
      _id
      name
    }
    flag {
      _id
      svgFile
    }
  }
}`;

export default function Home() {
  const { data: countries, error } = useSWR(countriesQuery, (query) =>
    request(API_ENDPOINT, query)
  );
  return (
    <>
      <Head>
        <title>Countries</title>
      </Head>
      <Layout>
        <Heading size="2xl" as="center">
          Countries
        </Heading>
        {error && (
          <Alert status="error">
            <AlertIcon />
            There was an error processing your request
          </Alert>
        )}
        <Grid templateColumns="repeat(3, 1fr)" ml="10" mr="10" gap={6}>
          {countries && countries.Country &&
            countries.Country.map((country) => <Country country={country} key={country._id} />)}
        </Grid>
      </Layout>
    </>
  );
}

Run the app with yarn run dev or npm run dev.
You should see a list of countries, maps and their details listed.

In the next post, we will implement search functionality to demonstrate passing variables to GraphQL.

Full code available here on Github

Thanks for reading. Please, leave your comment in the feedback.

Top comments (3)

Collapse
 
eduardo305 profile image
eduardo305

Thanks, Abiodun! But your app is not rendering on the server-side, right? We can see clearly a blank page showing up before the country flags appear. I guess (might be wrong), but useSWR is designed to work on the client-side, although I saw some workarounds to make it work on the server-side by passing the 'initialData' attribute.

Collapse
 
canali83 profile image
canali83 • Edited

[Solved] after npm i graphql

I get the following error. I installed graphql
Failed to compile
./node_modules/graphql-request/dist/index.js
Module not found: Can't resolve 'graphql/language/printer' in ...

Collapse
 
shoptheworld profile image
shoptheWorld

next post URL Please ..