ОБНОВЛЕНИЕ: прокрутите до конца, чтобы увидеть сделанный мной небольшой видеоролик о том, как запускать graphQL из приложения nodejs.

Эта статья предназначена для людей, которые хотят познакомиться с GraphQL, языком запросов, разработанным Facebook. Я попытаюсь подчеркнуть, почему текущему статусу REST серьезно не хватает гибкости с примерами из реального мира, и почему graphQL предлагает гораздо большую детализацию.

Итак, вы слышали о graphQL и, возможно, уже провели с ним какие-то эксперименты - если у вас возникли проблемы или вы чувствуете, что что-то упустили, чтобы увидеть общую картину, эта статья может вам помочь. С этого момента я буду считать, что у вас есть некоторые навыки работы с nodejs, но если это не так, вы все равно можете найти кое-что интересное.

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

GraphQL не зависит от языка, его не волнует код вашего приложения или вашей базы данных. Это больше способ спросить

Привет ! Мне нужны эти данные, пришлите их мне в этой форме

Любой фрагмент кода может запрашивать некоторые данные, и любой сервер может обслуживать ответ, что действительно имеет значение, так это запрос (как ваш клиент запрашивает данные) и то, как ваш сервер поймет этот запрос.

Надеюсь, есть модуль npm, который справится с большей частью работы. GraphQL основан на таких концепциях, как поле, тип и схема, этот модуль поможет вам связать их все.

Возможно, вы уже знаете, что GraphQL используется для запроса данных из удаленного API, но у нас уже есть REST для той же цели, почему вам следует подумать об использовании чего-то другого для достижения той же работы?

Что ж, позвольте мне сделать быстрый обзор REST

ОТДЫХ

REST - это метод запроса данных из удаленного API, он использует преимущества протокола HTTP, используя как транспорт, так и URL. URL-адрес описывает ресурс, который вы хотите получить, а тело - это полученный вами ответ. Это очень похоже на просмотр веб-страницы в браузере.

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

GET http://moviedatabase.io/movie/123

На стороне сервера это тоже довольно просто, вам нужно выяснить

  • что это за запрос (getMovieFromId или что-то в этом роде)
  • получить конкретный идентификатор этого фильма (в нашем случае 123)
  • выполнить некоторую внутреннюю работу, чтобы получить реальные данные (возможно, подключиться к базе данных)
  • отправьте ответ в обычном формате, например JSON

Это также не зависит от языка, потому что и клиент, и сервер могут быть написаны на любом языке, который вы хотите. Единственное, что должен сделать клиент, - это сделать http-запрос, а единственное, что должен сделать сервер, - это прослушать запрос и отправить ответ с некоторыми данными. Эту работу обычно выполняет маршрутизатор.

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

Но REST не идеален

Здесь есть две потенциальные проблемы:

  • A: вы знаете, что получите данные о фильме (это то, что вы просите), но вы, вероятно, получите больше данных, чем вы действительно хотите, например временную метку последнего обновления или список связанных изображений. к этому фильму.
    Может быть, вам просто нужен основной заголовок и изображение плаката.
  • B: вам нужна более подробная информация об актерах, например, об их фотографиях, а не только об их имени.

Вы можете закончить, в то же время, слишком много данных и нехватку данных

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

Вторую проблему также легко решить, отправив больше запросов после первого, чтобы получить все фотографии актеров. Предполагая, что у вас есть 5 актеров, вам нужно будет запросить 6 раз ... но кого это волнует, у вас будет фотография, и вы будете счастливы.

Так что не так?

Сеть медленная, даже очень очень медленная

Всякий раз, когда делается удаленный запрос, в фоновом режиме происходит множество вещей, таких как разрешение DNS, шифрование SSL, обработка сокетов, задержка, управление тайм-аутом… многие вещи, которые вас действительно не волнуют. Это как черный ящик, вы не знаете, что происходит, а время может изменить каждый запрос.

Для каждого запроса, который вы делаете, вам придется повторять это (не вам, а системе, которая запускает ваш код) снова и снова, и вы хотите ускорить процесс как можно быстрее.
Вы можете оптимизировать свой время отклика сервера, и вы можете оптимизировать свою сеть, но суть в том, что если вам нужно 6 раз запросить ваш сервер, вам придется ждать 6 раз, даже если ваша сеть быстро работает, 6 всегда будет больше 1.

Можем ли мы просто передать некоторые дополнительные данные в URL-адрес, чтобы вернуть данные актеров вместе с данными фильма, чтобы избежать лишних запросов?

Хорошая идея ! Давай попробуем что-нибудь вроде…

http://moviedatabase.io/movie/123?withActor=true&actorField=picture

Тада! Если вы обрабатываете параметры withActor и актерField где-нибудь в своем приложении, вы удалите лишние запросы ... Но что, если вам нужно больше разных данных? вы, вероятно, закончите с очень уродливым, нечитаемым URL, очень специфичным для этого запроса ... и помните ли вы проблему A? теперь у вас есть полные данные о фильме со всеми данными об актерах ...

GraphQL приходит на помощь

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

query{
  myMovie: getMovieFromId(id:123){
    title
  }
}

Запрос - это простая строка, которая выглядит как «дерево». title - самый глубокий элемент, назовем его лифом. myMovie, с другой стороны, действует как ветка, это тоже поле, но больше похоже на сущность или объект. Все поля ниже описывают этот фильм

что сервер отправляет обратно?

{
  "data": {
    "myMovie": {
      "title": "Star Wars"
    }
  }
  "error": {
    // if your do correctly job job, you will never see me !
    // — the error message
  }
}

Структура ответа очень похожа на структуру запроса.

Поле корня данных всегда используется для обертывания реальных данных (потому что у вас может быть поле «ошибка» в корне, чтобы выявить некоторые ошибки). Ключ «myMovie» - это то, что я выбрал в запросе, это не обязательно, но может иметь значение в этом случае (myMovie1, myMovie2…)

Если вы делаете запрос REST, вы должны знать URL.
В GraphQL вы должны знать все имена полей и аргументы, которые нужно передать для некоторых из них.

В некотором смысле это похоже на использование SELECT * в REST и SELECT field1, field2… с GraphQL.

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

Привет со стороны Сервера

Пришло время посмотреть, как сервер со всем этим справляется. Я сосредоточусь на Javascript, потому что этот язык очень хорошо соответствует философии GraphQL.

Первое, что вам нужно определить, - это схема, она похожа на манифест. Он определяет список полей, которые вы можете запросить, и их соответствующий Тип.

Примечание. Есть кое-что, что я неправильно понял задолго до момента ха-ха, я был очень смущен: Каждая вещь - это поле. Я имею в виду буквально. Каждый уровень в вашем запросе - это поля. Самые глубокие поля (листья) должны быть примитивными (строка, число и т. Д.). Поля между ними и корнем служат для структурирования данных, придавая смысл и порядок

Тип относится к данным, самый простой тип - это примитив (строка, число, логическое значение и т. Д.). Когда вы определяете название фильма как строку, вы используете тип String. А как насчет самого фильма? это объект или что-то еще?

Фильм - это произвольный тип. Это похоже на определение «что такое фильм».
Что ж, фильм содержит строку заголовка и список лиц ... хорошо, но что такое человек? ... Хорошо, человек - это настраиваемый тип со строкой имени и плакатная строка ... вы понимаете, это как композиция

Movie и Person являются настраиваемыми типами и определяются именем и списком полей.

Больше никаких разговоров, покажи мне код !!!

Вернемся к нашему первому примеру и просто добавим тупое поле с именем дата

query{
  myMovie: getMovieFromId(id:123){
    title
    date
  }
}

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

Давайте пойдем глубже и создадим новый тип фильма

const Movie = new GraphQLObjectType({
  name: 'Movie',
  fields: {
    title: { type: GraphQLString }
  }
})

Фильм - это просто тип объекта, вы не можете его использовать, поэтому давайте создадим поле, которое будет использовать объект Фильм

const getMovieFromId = {
  type: Movie,
  args: {
    id: { type: GraphQLString }
  },
  resolve: (obj, args, root, ast) => {
    return {
     title: "Matrix"
    }
  }
}

Теперь у нас есть объект с именем «getMovieFromId», который имеет тип Movie. Опять же, это простое определение объекта, пока не очень полезное. Но мы также определили две важные вещи

  • «Args» - список аргументов, которые мы можем использовать для этого поля.
  • "Resolve" - ​​функция, возвращающая данные / значение для этого поля.

Наконец, давайте создадим нашу схему, которая ведет себя как ствол.

const schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'RootQueryType',
    fields: {
      getMovieFromId
    }
  })
})

Это корень каждого запроса, потому что вы не можете напрямую получить доступ к ветви 2-го уровня вниз, вам нужно начать с RootQuery.

Вернемся к нашему первоначальному запросу

query{
  myMovie: getMovieFromId(id:123){
    title
    date
  }
}

здесь мы просим пару вещей:

  • мы получаем доступ к полю «getMovieFromId» с параметром (id: 123)
  • мы перечисляем некоторые поля, которые нужно вернуть (заголовок и дата)
  • мы присваиваем значение поля getMovieFromId «myMovie»

На стороне сервера, когда graphQL проанализирует наш запрос, некоторые проверки будут выполнены в rootQuery.

  • Существует ли «getMovieFromId» как поле в rootQuery → да
  • Что это за тип getMovieFromId? → Фильм
  • Есть ли у getMovieFromId функция разрешения и проверки аргументов, совпадают ли они? → да
  • Выполните эту функцию с аргументами
  • Получите значение, возвращаемое функцией разрешения, и выполните те же проверки на один уровень ниже.
  • Являются ли заголовок и дата частью полей, определенных в фильме → нет
  • … Вы поняли идею

Каждый раз, когда graphQL встречает объект, тот же процесс начинается снова с этим новым объектом, чтобы, наконец, доставить остаток запроса.

Чтобы было ясно, если вы запросите это

query{
  myMovie: getMovieFromId(id:123){
    title
    date
    images{
      url
    }
  }
}

а функция разрешения поля «getMovieFromId» не возвращает никаких данных изображений, ваш ответ будет

{
  "data": {
    "myMovie": {
      "title": "Star Wars"
      "data": 2016
      "image" : null
    }
  }
}

Результат функции разрешения используется graphQL для продолжения процесса.

Сверху вниз каждая пара ключ / значение, возвращаемая этими функциями, будет рассматриваться как поля.

Вложенные поля и связанные данные

Теперь у вас есть простой запрос, чтобы узнать название фильма, но как насчет актеров? Актеры - это список лиц, нам нужно улучшить наш запрос, чтобы получить их имя и плакат.

query{
  myMovie: getMovieFromId(id:123){
    title
    actors{
      name
      poster
    }
  }
}

теперь у нас есть актеры {}, которые отражают список актеров, и для каждого из них нам нужно имя и изображение. Но тип фильма не определяет поле актеров, верно? так давай исправим это

const Movie = new GraphQLObjectType({
  name: 'Movie',
  fields: {
    title: { type: GraphQLString },
    actors: { type: new GraphQLList(Person) }
  }
})

акторы теперь являются полем, списком Person или массивом Person

но что такое человек? помните, это произвольный тип ... давайте создадим этот тип

const Person = new GraphQLObjectType({
  name: 'Person',
  fields: {
    name: { type: GraphQLString },
    poster: { type: GraphQLString }
  }
})

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

const Movie = new GraphQLObjectType({
  name: ‘Movie’,
    fields: () => {
    title: { type: GraphQLString },
    actors: { type: new GraphQLList(Person) },
    directors: { type: new GraphQLList(Person) },
    composers: { type: new GraphQLList(Person) },
    ...
  }
})`

Поскольку актеры, режиссеры и композиторы - это списки лиц, мы можем запросить их таким же образом.

query{
  myMovie: getMovieFromId(id:123){
    title
    actors{
      name
      poster
    }
    directors{
      name
    }
    composers{
      name
    }
  }
}

Здесь режиссер и композитор не получат поле постера ... даже если это разрешено

Запросить связанные данные

Пока мы получаем только название фильма, потому что поле getMovieFromId возвращает только название. Как получить другие данные?

Любое поле может иметь функцию разрешения. Если вы его не предоставите, graphQL просто вернет значение. Так обстоит дело с полями листьев, так как они являются глупыми значениями (примитивными), они просто возвращаются как это.

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

const Movie = new GraphQLObjectType({
  name: 'Movie',
  fields: {
    title: { type: GraphQLString },
    actors: {
      type: new GraphQLList(Person),
      resolve: (_, args, root, ast) => {
 
        // get the list of actors from the databse  or return a simple []
        // yes all my familly was in this movie ! 
       return [
         {name: “Benjamin”, poster: “benjamin.jpg”},
         {name: “Julie”, poster: “julie.jpg”},
         {name: “Anna”, poster: “anna.jpg”},
         {name: “Louisa”, poster: “louisa.jpg”}
       ]
    }
  }
})

Из функции разрешения вы можете получить доступ к «родительскому» полю из первого аргумента здесь с именем «_», _.id вернет, например, movie.id

Вы также можете получить доступ к текущему имени поля с помощью ast.fieldName
и соответствующему значению с помощью _ [ast.fieldName], это полезно
, когда вы действительно не знаете, в каком поле вы находитесь (don Не забывайте,
вы можете извлечь функцию разрешения и повторно использовать ее в разных полях)

Важно отметить пару вещей:

  • Первый запрос к базе данных возвращает только заголовок (и хорошо, может быть, и идентификатор), здесь нет полей «актеры».
  • поля актеров берутся из определения типа фильма, даже если список полей на самом деле не отражает поля базы данных - это нормально и не очень важно. Вы можете улучшить данные своего фильма с помощью некоторых вычисляемых полей, таких как актеры
  • если запрос к базе данных возвращает поле с именем «субъекты» с некоторыми данными, эти данные будут заменены значениями, возвращаемыми функцией разрешения. Вы можете воспользоваться этим или нет, решать вам.

Использование аргументов

Пока все хорошо, у нас есть фильм со списком актеров, но если нам нужно только 2 из них, а сервер дает нам всех, что мы можем сделать?

Функция разрешения поля участников возвращает данные, все данные. Если вам нужно меньше данных, вы можете ограничить объем возвращаемых данных. акторы - это массив Person, поэтому, как и любой массив, вы можете использовать Array.splice и возвращать только то, что вы хотите.

...
resolve: (_, args, root, ast) => {
  const list_of_actors = [{"name": "Benjamin", {}, {}…]
  return list_of_actors.splice(0, 2) 
}
...

но это не очень полезно, потому что количество возвращаемых Person жестко запрограммировано, давайте вместо этого будем использовать args

...
args: {
  limit: { type: GraphQLInt }
},
resolve: (_, args, root, ast) => {
  const list_of_actors = [{"name": "Benjamin", {}, {}…]
  if(!args.limit) return list_of_actors
  return list_of_actors.splice(0, args.limit) 
}
...

теперь вы можете использовать эту функцию без аргументов, чтобы получить весь список Person, или вы можете просто указать предел, чтобы получить меньше Peson.

Graphql строго типизирован, угадайте, что, здесь вы будете счастливы, что args.limit всегда будет целым числом (или нулем), но никогда строкой ... просто потому, что Array.splice () действительно хочет чего-то еще, кроме целого!

как это выглядит со стороны запроса?

query{
  myMovie: getMovieFromId(id:123){
    title
    actors(limit:2){
      name
      poster
    }
  }
}

поскольку ожидается, что limit будет целым числом, если вы попробуете что-то еще, grapqhQL будет кричать на вас - limit должен быть только целым числом или не существовать вообще.

Использование GraphQL в повседневной работе

Я впервые посмотрел на graphQL почти год назад. Я был очень впечатлен, но в то время не видел потенциала. Пару недель назад я беспокоился о том, сколько времени уходит на некоторые запросы на сайте yo-video.net и насколько они полезны.

Для домашней страницы, где вы можете найти фильм в кинотеатрах, ответ от сервера составлял около 1 Мб ... даже если запрос / ответ на самом деле происходит между двумя серверами (не от вашего браузера), этот большой объем данных требовал много памяти для анализа и извлечь.

GraphQL оказался довольно хорошим решением этой проблемы, при этом уменьшились объем используемой памяти и времени. Это был умный ход.

Я начал эту статью с жалобы на REST, но до сих пор часто использую его на этом веб-сайте. Например, на странице сведений о фильме нам нужны почти все поля, поэтому использовать REST для получения этих данных было несложно.
Фильм состоит примерно из 60 различных полей (расположенных на нескольких уровнях)… так что, если вам нужны все они , соответствующий запрос GraphQL будет нелегко написать. В этом конкретном случае и, на мой взгляд, использование REST позволит сэкономить время.

Я рекомендую использовать GraphQL IDE, которая намного лучше, чем graphiql, с множеством очень полезных опций, таких как сохраненные запросы по проектам, вкладки, заголовки… вы должны взглянуть

Поскольку я большой поклонник курсов egghead.io, Idefinitively рекомендую эту серию https://egghead.io/courses/build-a-graphql-server
Содержание предназначено только для профессиональных пользователей, но это стоит подписки

Спасибо, папа, за корректуру и советы

Бонус

Я сделал трехминутное видео, чтобы выполнить запрос GraphQL из приложения nodejs.