Setting up a GraphQL Server and Client in Next.js

Written by ljaviertovar | Published 2023/06/06
Tech Story Tags: react | react-tutorial | javascript | javascript-tutorial | graphql | webdevelopment | front-end-development | backend-development

TLDRGraphQL is an API query language that allows clients to get the data they need more efficiently and flexibly than traditional REST APIs. Next.js is a highly efficient and scalable web application development framework that has become very popular with web developers. This tutorial assumes that you have basic knowledge of GraphQL and Nextjs, to better understand it.via the TL;DR App

GraphQL is an API query language that allows clients to get the data they need more efficiently and flexibly than traditional REST APIs. In recent years, GraphQL has gained a lot of popularity and has become one of the most popular technologies for API creation.

At the same time, Next.js is a highly efficient and scalable web application development framework that has become very popular with web developers.

This tutorial will explore how to create a GraphQL server and client with Next.js to provide a more efficient and flexible development experience for our users.

Before starting: this tutorial assumes you have basic knowledge of GraphQL and Nextjs (pages directory).

We will use dummyJSON to make requests and retrieve fake JSON data, in particular, we will use https://dummyjson.com/docs/users

Setting Up

We create a new Next.js project with the following command and follow the steps indicated. This time we will use pnpm, you can use the package manager of your choice.

pnpm create-next-app

We install the dependencies that we will need in the project:

pnpm install @apollo/server graphql @as-integrations/next apollo-server-core @apollo/client graphql-tag @nextui-org/react

@apollo/server: is the main library for Apollo Server itself.

@as-integrations/next: An Apollo Server integration for use with Next.js.

graphql: to create and consume API’s in Graphql language.

apollo-server-core: to create and manage GraphQL servers simply and flexibly.

@apollo/client: to handle GraphQL requests and statuses on clients.

graphql-tag: to build and manipulate GraphQL queries in JavaScript.

@nextui-org/react library of user interface components. I will use this library only to quickly style the application, feel free to use the one you want.

After that, we will create the following structure for the project:

...
├── src/
│   ├── graphql/
│   │   ├── queries/
│   │   │   ├── getUseres.gql
│   │   │   └── searchUser.gql
│   │   ├── apollo-client.js
│   │   ├── resolvers.js
│   │   └── schemas.js
│   ├── pages/
│   │   ├── api/
│   │   │   └── graphql.js
│   │   ├── _app.js
│   │   ├── _document.js
│   │   └── index.js
│   │   ...
│   ├── utils/
│   │   └── cors.js
├── .env.local
...

Now we will create the .env.local file and create the environment variables that we are going to use:

/* we add the prefix NEXT_PUBLIC_ to the variable that will havethe graphql server url defined, this way we will be able toaccess to it client side */

NEXT_PUBLIC_URL_SERVER_GRAPHQL=http://localhost:3000/api/graphqlURL_API=https://dummyjson.com/users

Graphql server

1— We will create the GraphQL server inside the /api directory since it’s where the server created by Nextjs lives.

graphql.js:

import { ApolloServer } from "@apollo/server"
import { startServerAndCreateNextHandler } from '@as-integrations/next'
import { ApolloServerPluginLandingPageGraphQLPlayground } from "apollo-server-core"

import typeDefs from "@/graphql/schemas"
import resolvers from "@/graphql/resolvers"
import allowCors from "@/utils/cors"

const apolloServer = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [ApolloServerPluginLandingPageGraphQLPlayground()]
})

const handler = startServerAndCreateNextHandler(apolloServer, {
  context: async (req, res) => ({ req, res }),
})

export default allowCors(handler)

The GraphQL server has two main parts: schemas (typeDefs) and resolvers. The schemas define the structure of the data and the queries that can be made to the server, while the resolvers define how the data is obtained and processed.

We also add to the initial configuration of the server the definition of ApolloServerPluginLandingPageGraphQLPlayground. This plugin provides a GraphQL playground page that allows us to test queries in a more user-friendly way.

Finally, allowCors is used to enable Cross-Origin Resource Sharing (CORS) support on the server. This function adds the necessary CORS headers to the response.

utils/cors.js :

const allowCors = (fn) => async (req, res) => {
  res.setHeader('Access-Control-Allow-Credentials', true)
  res.setHeader('origin', 'https://nextjs-graphql-server-client.vercel.app')
  res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*')
  // another common pattern
  res.setHeader('Access-Control-Allow-Methods', 'GET,OPTIONS,PATCH,DELETE,POST,PUT')
  res.setHeader(
    'Access-Control-Allow-Headers',
    'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version',
  )
  if (req.method === 'OPTIONS') {
    res.status(200).end()
    return
  }
  await fn(req, res)
}

export default allowCors

In this file, we have defined the basic headers that the app needs to be able to make requests to the Graphql server.

This middleware is fully customizable to the requirements of your application, you can remove or add more headers, either known headers or custom headers that only your app uses.

You need to add the necessary headers so you can make requests to the server either using this middleware or directly in your requests.

2— Schema creation

schemas.js:

import { gql } from 'graphql-tag'

const typeDefs = gql`
  type Query {
    users: [User]
    searchUser(value:String):[User]
  }

  type User {
    id: ID
    firstName: String
    lastName: String
    email: String
    username: String
    image: String
  }
`
export default typeDefs

We define a GraphQL schema using gql. The gql function is used to convert a string into a valid GraphQL schema document.

In the Schema, we define a query type called Query, which has a property called users, which is a list of objects of type User.

We also have the query called searchUserwhich receives a string value with which we will filter the users that match the value. For this tutorial, we will create the type User with only some of the API returned properties.

3— Resolvers creation

resolvers.js:

const resolvers = {
  Query: {
    users: async () => {
      try {
        const response = await fetch(process.env.URL_API)
        const data = await response.json()

        return data.users.map(u => {
          return {
            id: u.id,
            firstName: u.firstName,
            lastName: u.lastName,
            email: u.email,
            username: u.username,
            image: u.image
          }
        })
      } catch (error) {
        throw new Error("Something went wrong")
      }
    },
    searchUser: async (_, { value }) => {
      try {
        const response = await fetch(`${process.env.URL_API}/search?q=${value}`)
        const data = await response.json()

        return data.users.map(u => {
          return {
            id: u.id,
            firstName: u.firstName,
            lastName: u.lastName,
            email: u.email,
            username: u.username,
            image: u.image
          }
        })
      } catch (error) {
        throw new Error("Something went wrong")
      }
    }
  }
}

export default resolvers
   

We define the resolvers that correspond to each query. The resolvers request the external API, which returns a JSON with a list of users. We do a mapping of the data received from the API to return only the data that we will use in the application.

In case something goes wrong during the request or the data processing, an exception is thrown with the message “Something went wrong”.

Now let’s run the application, go to the path /api/graphql and execute the queries created:

GraphQL client

1— Creation of an instance of an Apollo client.

import { ApolloClient, InMemoryCache } from "@apollo/client"

import { ApolloClient, InMemoryCache } from "@apollo/client"

const client = new ApolloClient({
  uri: process.env.NEXT_PUBLIC_URL_SERVER_GRAPHQL,
  cache: new InMemoryCache(),
})

export default client

The client is created with two main properties: uriand cache. The uri property is the GraphQL API address to which the client will connect. In this case, we will use the NETX_PUBLIC_URI_SERVER_GRAPHQL environment variable created earlier that stores the API address.

The cache property is used to store the data that is downloaded from the API in cache. This way, the client can access the data without making a new API query.

2— This component provides ApolloPrivider functionality, which allows an instance of an Apollo client to access all components rendered within it.

import { ApolloProvider } from '@apollo/client'

import client from "@/graphql/apollo-client"

export default function App({ Component, pageProps }) {
  return (
    <ApolloProvider client={client}>
        <Component {...pageProps} />
    </ApolloProvider>
  )
}

3— Creation of queries in .gql files.

getUser.gql:

query getUsers {
 users {
  id
  firstName
  lastName
  email
  username
  image
 }
}

searchUser.gql:

query getSearchUsers($value: String) {
 searchUser(value: $value) {
  id
  firstName
  lastName
  email
  username
  image
 }
}

next.config.js:


const nextConfig = {
 ...
  webpack: (config, options) => {
    config.module.rules.push({
      test: /\.(graphql|gql)/,
      exclude: /node_modules/,
      loader: "graphql-tag/loader"
    })

    return config
  }
  ...
}

In order to use .graphql or .gql files in Next.js, it’s necessary to add a webpack configuration in the Nextjs configuration. Here you can read more about it.

4— Making server-side and client-side requests.

index.js:

import { useEffect, useRef, useState } from 'react'
import { useLazyQuery, useQuery } from '@apollo/client'

import Head from 'next/head'
import { Button, Container, Grid, Input, Spacer, User, Row, Loading } from "@nextui-org/react"

import GET_USERS from '@/graphql/queries/getUsers.gql'
import SEARCH_USERS from '@/graphql/queries/searchUsers.gql'

export default function Home() {
  const [users, setUsers] = useState([])
  const [searchValue, setSearchValue] = useState('')

  const usersRef = useRef(null)

  const { data, loading, error } = useQuery(GET_USERS)

  const [getSearchedUsers] = useLazyQuery(SEARCH_USERS, {
    fetchPolicy: 'network-only',
    onCompleted(data) {
      setUsers(data.searchUser)
    }
  })

  useEffect(() => {
    if (data) {
      setUsers(data.users)
      usersRef.current = data.users
    }
  }, [data])


  const searchUser = () => {
    getSearchedUsers({
      variables: {
        value: searchValue
      }
    })
  }

  if (error) {
    console.error(error)
    return null
  }

  return (
    <>
      <Head>
        <title>Nextjs and Graphql Setup</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main >

        <Container css={{ display: 'flex', justifyContent: 'center' }}>
          <Spacer y={2.5} />
          <Row justify="center" align="center">

            <Input
              clearable
              labelPlaceholder="User"
              onClearClick={() => setUsers(usersRef.current)}
              initialValue={searchValue}
              onChange={(e) => setSearchValue(e.target.value)}
            />
            <Button color="gradient" auto onClick={() => searchUser()}>
              Search user
            </Button>
          </Row>

          <Spacer y={2.5} />
          <Row justify="center" align="center">

            {loading
              ?
              <Loading />
              :
              <Grid.Container gap={2} justify="center">
                {users.map(u => (
                  <Grid xs={3}
                    key={u.id}
                  >
                    <User
                      src={u.image}
                      name={`${u.firstName}${u.lastName}`}
                      description={u.email}
                      size="lg"
                      bordered
                      color="gradient"
                    />
                  </Grid>

                ))
                }
              </Grid.Container>
            }

          </Row>
        </Container>
      </main>
    </>
  )
}

In the above code, we will use the useQuery hook to get the users with the GET_USERS query and then save it in the state.

We also use Apollo’s useLazyQuery hook to perform a search on the users when the user clicks on the “Search user” button. When the query is finished we save the results in the state.

That’s it! now we can use graphql server-side and client-side. You can add as many queries and resolvers as you need.

The app looks as follows:

See the demo here

Repo here

Conclusion

In summary, using GraphQL in a Next.js project can significantly improve the application’s efficiency, flexibility, documentation, security, and scalability. Hopefully, this will serve as a guideline for your future projects.


Want to connect with the author?

Love connecting with friends all around the world on Twitter.

Read more:

Also published here.


Written by ljaviertovar | ☕ FrontEnd engineer 👨‍💻 Indie maker ✍️ Tech writer
Published by HackerNoon on 2023/06/06