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 searchUser
which 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: uri
and 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:
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:
- How to Create a Modern Skeleton Loader in React to Improve User Experience
- How to Simulate a Backend REST API with json-server for CRUD Development in React
Also published here.