Разрешение вложенных запросов в GraphQL

Первоначально опубликовано на https://www.wisdomgeek.com 9 декабря 2020 г.

При создании сервера GraphQL с реляционными данными мы хотим вернуть данные в иерархическом формате с этими отношениями в одном запросе. В конце концов, именно здесь GraphQL пригодится, верно? Давайте посмотрим, как мы можем это сделать, используя вложенные запросы в GraphQL.

В этом посте предполагается, что вы знакомы с запросами GraphQL и как создать сервер GraphQL с помощью сервера Apollo. Вы можете ссылаться на эти сообщения, если вы новичок в GraphQL.

Предположим, у нас есть приложение для ведения блога, в котором есть пользователи и сообщения. Типы для них определены как:

type Post {
    id: ID!
    title: String!
    authorId: ID!
  }

  type Author {
    id: ID!
    name: String!
    posts: [Post]
  }

Добавляем несколько примеров жестко закодированных данных для нашего приложения,

const posts = [
  {
    id: 1,
    title: 'Why GraphQL?',
    authorId: 1,
  },
  {
    id: 2,
    title: 'Creating a GraphQL API with Apollo Server',
    authorId: 1,
  },
  {
    id: 3,
    title: 'This should not be returned',
    authorId: 2,
  },
];

const authors = [{ id: 1, name: 'Saransh Kataria' }];

Теперь, когда мы сделали нашу начальную настройку. Давайте посмотрим, что мы ожидаем от нашего запроса:

Ожидаемый результат

Для указанных выше жестко заданных значений мы хотим запросить всех пользователей и получить их соответствующие сообщения. Таким образом, наш запрос будет выглядеть так:

query {
  authors {
    id,
    name,
    posts {
      title
    }
  }
}

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

И мы ожидаем, что результат для нашего варианта использования будет следующим:

{
  "data": {
    "authors": [
      {
        "id": "1",
        "name": "Saransh Kataria",
        "posts": [
          {
            "title": "Why GraphQL?"
          },
          {
            "title": "Creating a GraphQL API with Apollo Server"
          }
        ]
      }
    ]
  }
}

Создание запроса и распознавателя для авторов

Не беспокоясь о поле сообщений, мы можем настроить распознаватель для запроса авторов. Это будет:

const resolvers = {
  Query: {
    authors: () => {
      return authors;
    },
  },
}

Мы возвращаем массив авторов, когда получаем запрос на авторов. Это довольно просто. У объекта уже есть все соответствующие свойства, которые нужны типу, они разрешаются, и мы получаем ожидаемый ответ.

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

Разрешение вложенных запросов в GraphQL

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

Наряду с запросом мы добавим новое свойство, которое сообщит GraphQL, как разрешить автора. Свойство author будет объектом, и мы создадим метод для каждого из полей, которые должны быть разрешены.

Сервер Apollo может автоматически разрешать скалярные свойства. Нам нужно только создать преобразователь для вложенных свойств. Чтобы разрешить вложенные запросы в GraphQL, мы создаем метод только для свойств, которые ссылаются на другие пользовательские типы. В нашем случае у нас есть только поле сообщений, которое мы ищем. Итак, мы определим это как:

const resolvers = {
  Query: {... },
  Author: {
    posts: () => {...},
  },
}

Поскольку posts — это метод распознавателя точно так же, как и другие методы на сервере Apollo, мы получаем доступ ко всем 4 параметрам, которые мы получаем в других методах. Цель этого метода — вернуть правильные посты, соответствующие автору. Для этого нам нужна некоторая информация от объекта автора, например идентификатор автора.

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

С помощью автора мы можем легко выяснить, какие посты нужно вернуть.

const resolvers = {
  Query: {
    authors: () => {
      return authors;
    },
  },
  Author: {
    posts: (author) => {
      return posts.filter((post) => post.authorId === author.id);
    },
  },
};

Окончательный код

const { gql, ApolloServer } = require('apollo-server');

const posts = [
  {
    id: 1,
    title: 'Why GraphQL?',
    authorId: 1,
  },
  {
    id: 2,
    title: 'Creating a GraphQL API with Apollo Server',
    authorId: 1,
  },
  {
    id: 3,
    title: 'This should not be returned',
    authorId: 2,
  },
];

const authors = [{ id: 1, name: 'Saransh Kataria' }];

const typeDefs = gql`
  type Post {
    id: ID!
    title: String!
    authorId: ID!
  }

  type Author {
    id: ID!
    name: String!
    posts: [Post]
  }

  type Query {
    authors: [Author]
  }
`;

const resolvers = {
  Query: {
    authors: () => {
      return authors;
    },
  },
  Author: {
    posts: (author) => {
      return posts.filter((post) => post.authorId === author.id);
    },
  },
};

const server = new ApolloServer({ typeDefs, resolvers });
server.listen(4000).then(({ url }) => {
  console.log(`Server started at ${url}`);
});

Вывод

Когда мы сейчас запустим запрос, мы получим желаемый результат:

Первой вызываемой функцией является функция разрешения авторов, поскольку именно ее запрашивает запрос. Он возвращает идентификатор, имя и заголовок для всех авторов. В нашем случае у нас есть только один, и он возвращается.

Далее GraphQL проверяет, какие данные были запрошены. Если бы были запрошены только имя и идентификатор, выполнение функции закончилось бы на этом, поскольку это скалярные типы.

Но мы попросили посты. И посты не существуют на объекте авторов. Таким образом, GraphQL будет вызывать функцию сообщений для каждого отдельного автора. И именно здесь на сцену выходит наше разрешение вложенных запросов в GraphQL. Наша функция распознавателя сообщений вызывается с родительским набором в качестве автора ({id: 1, name: ‘Saransh Kataria’ }).

Если бы в нашем жестко запрограммированном примере присутствовало несколько авторов, функция сообщений вызывалась бы индивидуально для каждого из них. Мы используем идентификатор переданного родительского автора, чтобы получить сообщения автора и вернуть их.

Мы можем определить отношения по-другому, определив сообщения в типе автора, а затем создав таким образом вложенный запрос GraphQL. Определение схемы, объявление и ее выполнение — все зависит от нас. И мы можем разрешать вложенные запросы в GraphQL, как захотим, как только узнаем, как это делать.

Если у вас есть какие-либо вопросы, не стесняйтесь оставлять комментарии ниже.