Создание простого веб-приложения с Next.js и GraphQL

GraphQL — это язык запросов API, который позволяет клиентам получать необходимые им данные более эффективно и гибко, чем традиционные API REST. В последние годы GraphQL приобрел большую популярность и стал одной из самых популярных технологий для создания API.

В то же время Next.js — это высокоэффективная и масштабируемая среда разработки веб-приложений, которая стала очень популярной среди веб-разработчиков.

В этом руководстве будет показано, как создать сервер и клиент GraphQL с помощью Next.js, чтобы обеспечить более эффективную и гибкую разработку для наших пользователей.

Перед началом: в этом руководстве предполагается, что у вас есть базовые знания о GraphQL и Nextjs, чтобы лучше понять их.

Мы будем использовать dummyJSON для выполнения запросов и получения поддельных данных JSON, в частности, мы будем использовать https://dummyjson.com/docs/users

Настройка

Мы создаем новый проект Next.js с помощью следующей команды и следуем указанным шагам. На этот раз мы будем использовать pnpm, вы можете использовать менеджер пакетов по вашему выбору.

pnpm create-next-app 

Устанавливаем зависимости, которые нам понадобятся в проекте:

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

@apollo/server: — основная библиотека для самого сервера Apollo.

@as-integrations/next:интеграция Apollo Server для использования с Next.js.

graphql: для создания и использования API на языке Graphql.

apollo-server-core: для простого и гибкого создания серверов GraphQL и управления ими.

@apollo/client:для обработки запросов и статусов GraphQL на клиентах.

graphql-tag: для создания запросов GraphQL и управления ими в JavaScript.

@nextui-org/reactбиблиотека компонентов пользовательского интерфейса. Я буду использовать эту библиотеку только для быстрого оформления приложения, не стесняйтесь использовать ту, которую хотите.

После этого создадим следующую структуру проекта:

...
├── 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
...

Теперь мы создадим файл .env.local и создадим переменные среды, которые мы собираемся использовать:

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

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

Графический сервер

1 — Мы создадим сервер GraphQL внутри каталога /api, так как именно там живет сервер, созданный Nextjs.

/* /api/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)

Сервер GraphQL состоит из двух основных частей: схемы (typeDefs) и преобразователя. Схемы определяют структуру данных и запросы, которые можно сделать на сервере, а распознаватели определяют, как данные получаются и обрабатываются.

Также добавляем в первоначальную конфигурацию сервера определение ApolloServerPluginLandingPageGraphQLPlayground. Этот плагин предоставляет страницу игровой площадки GraphQL, которая позволяет нам тестировать запросы более удобным для пользователя способом.

Наконец, allowCors используется для включения поддержки совместного использования ресурсов между источниками (CORS) на сервере. Эта функция добавляет в ответ необходимые заголовки CORS.

2 —Создание схемы

/* graphql/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

Мы определяем схему GraphQL, используя gql. Функция gql используется для преобразования строки в действительный документ схемы GraphQL.

В схеме мы определяем тип запроса с именем Query, у которого есть свойство с именем users, представляющее собой список объектов типа User.

У нас также есть запрос с именем searchUser, который получает строковое значение, с помощью которого мы будем фильтровать пользователей, соответствующих этому значению.

В этом руководстве мы создадим тип User только с некоторыми свойствами, возвращаемыми API.

3 — Создание резольверов

/* graphql/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

Мы определяем распознаватели, соответствующие каждому запросу. Резолверы запрашивают внешний API, который возвращает JSON со списком пользователей. Мы делаем сопоставление данных, полученных от API, чтобы вернуть только те данные, которые мы будем использовать в приложении.

Если что-то пойдет не так во время запроса или обработки данных, будет выброшено исключение с сообщением «Что-то пошло не так».

Теперь запустим приложение, пройдем по пути /api/graphql и выполним созданные запросы:

GraphQL-клиент

1 — Создание экземпляра клиента Apollo.

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

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

export default client

Клиент создается с двумя основными свойствами: uriи cache. Свойство uri — это адрес GraphQL API, к которому будет подключаться клиент. В этом случае мы будем использовать созданную ранее переменную окружения NETX_PUBLIC_URI_SERVER_GRAPHQL, в которой хранится адрес API.

Свойство cache используется для хранения данных, загружаемых из API, в кэше. Таким образом, клиент может получить доступ к данным без создания нового запроса API.

2 — Этот компонент обеспечивает функциональность ApolloPrivider, которая позволяет экземпляру клиента Apollo получать доступ ко всем компонентам, визуализируемым в нем.

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— Создание запросов в .gql файлах.

# /graphql/queries/getUser.gql
query getUsers {
 users {
  id
  firstName
  lastName
  email
  username
  image
 }
}
# /graphql/queries/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
  }
  ...
}

Чтобы использовать файлы .graphqlили .gql в Next.js, необходимо добавить конфигурацию веб-пакета в конфигурацию Nextjs. Здесь вы можете прочитать об этом подробнее.

4 — Выполнение запросов на стороне сервера и на стороне клиента.

/* /pages/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>
    </>
  )
}

В приведенном выше коде мы будем использовать хук useQuery, чтобы получить пользователей с запросом GET_USERS, а затем сохранить его в состоянии.

Мы также используем хук Apollo useLazyQuery для выполнения поиска пользователей, когда пользователь нажимает кнопку «Найти пользователя». Когда запрос завершен, мы сохраняем результаты в состоянии.

Вот и все! теперь мы можем использовать graphql на стороне сервера и на стороне клиента. Вы можете добавить столько запросов и преобразователей, сколько вам нужно.

Посмотреть демо здесь

Репозиторий здесь

Приложение выглядит следующим образом:

Заключение

Таким образом, использование GraphQL в проекте Next.js может значительно повысить эффективность, гибкость, документацию, безопасность и масштабируемость приложения. Надеюсь, это послужит ориентиром для ваших будущих проектов.

Подробнее:





Хотите связаться с Автором?
Любите общаться с друзьями по всему миру через Twitter.

Дополнительные материалы на PlainEnglish.io.

Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord.

Повысьте узнаваемость и признание вашего технического стартапа с помощью Circuit.