[практика] Пишем простой GraphQL сервер

от автора

Доброго времени суток, друзья!

Создание GraphQL сервера еще никогда не было таким простым, спасибо Express и Node.js.

Помните старый добрый протокол SOAP? Возможно, уже забыли, поскольку его заменил REST, и он намного лучше. Но что если я скажу вам, что скоро мы также забудем REST из-за GraphQL?

Необходимость получения данных

GraphQL — это язык запросов, созданный Facebook в 2012 и открытый (имеется ввиду ставший «опенсорным») в 2015. Он предназначен для получения и работы с определенными данными, предоставляя клиенту больше контроля за тем, какая информация отправляется на сервер. Одной из главных проблем REST является перегруженность данных: допустим, мы хотим получить имя и email пользователя, а вместо этого получаем весь профиль… зачем нам так много лишней информации? Теперь представьте, что нам нужен только список ID пользователей, однако API возвращает огромный массив объектов — профилей пользователей. Какое нерациональное расходование времени и ресурсов! GraphQL предоставляет API избирательного (выборочного) получения данных, который позволяет получать только то, что нужно.

Типобезопасность

GraphQL написан на SDL (Schema Definition Language), что обуславливает его строгую типизацию. Схемы GraphQL валидируются (проверяются) во время компиляции, т.е. компиляция завершится неудачно при обнаружении любой ошибки в типах данных.

Признание устаревшим и версионирование

Возвращаясь к REST, следует помнить, что со временем некоторые API могут быть признаны устаревшими. Может потребоваться переключиться с «someapi.dev/v1/myendpoint» на «someapi.dev/v2/myNewEndpoint» из-за изменения структуры ресурсов. GraphQL позволяет избежать этого, поскольку клиент запрашивает API по аналогии с базой данных. Структура сервера может измениться, но клиент по-прежнему будет иметь возможность получить содержимое в ожидаемом формате.

Создание сервера

Хорошо, давайте создадим сервер. Создаем проект и устанавливаем следующие пакеты:

$ yarn add graphql express express-graphql 

Отлично. Создаем файл "./main.js" и определяем новый Express Server:

const express = require('express') const app = express() const port = process.env.PORT || 3000  app.get('/' (req, res) => {   res.send('Hello World!') })  app.listen(port, () => {   console.log(`Server ready on port http://localhost:${port}`) }) 

Запустим сервер и посмотрим, как он работает: npm ./main.js

Страница выглядит не лучшим образом, но сервер работает! Добавляем конечную точку GraphQL, используя замечательный инструмент GraphiQL:

const express = require('express') const expressGraphQL = require('express-graphql') const {     buildSchema } = require('graphql') const app = express() const port = process.env.PORT || 3000  const schema = buildSchema(`     type Query {         message: String     } `)  const rootValue = {     message() {         return 'Hello World!'     }      app.use('/graphql', expressGraphQL({         schema,         rootValue,         graphiql: true     }))      app.get('/', (req, res) => {         res.send('Hello World!')     })      app.listen(port, () => {         console.log(`Server ready on port http://localhost:${port}`)     }) } 

Вот что мы здесь делаем:

  • Импортируем промежуточный слой Express GraphQL (express-graphql)
  • Импортируем функцию «buildSchema», позволяющую строить схему (Schema) GraphQL, которая описывает данные из источника
  • На 7 строчке строим схему, используя SDL. При этом необходимо определить тип каждого значения (в нашем случае, значения message, которое является строкой)
  • На 13 строчке определяем Resolver: он содержит ссылки операций на функции. Когда вы запрашиваете данные message, Resolver возвращает строку «Hello World!». Легко, не правда ли?
  • на 19 строчке создаем конечную точку "/graphql", которая также использует express-graphql для создания интерфейса GraphiQL

Перезапустим сервер и перейдем по адресу "http://localhost:3000/graphql":

Круто! Мы создали конечную точку GraphQL с помощью нескольких строчек кода! Протестируем ее, запросив message:

Превосходно. Мы только что создали наш первый GraphQL сервер! Создадим новый файл "./data.js":

module.exports = [{         id: 1,         name: "Java",         createdBy: "James Gosling",         year: 1995,         paradigm: "Object Oriented",         complexity: 7     },     {         id: 2,         name: "PHP",         createdBy: "Rasmus Lerdorf",         year: 1995,         paradigm: "Object Oriented",         complexity: 3     },     {         id: 3,         name: "C",         createdBy: "Dennis Ritchie",         year: 1972,         paradigm: "Imperative",         complexity: 10     },     {         id: 4,         name: "Erlang",         createdBy: "Joe Armstrong",         year: 1986,         paradigm: "Functional",         complexity: 8     },     {         id: 5,         name: "OCaml",         createdBy: "INRIA",         year: 1996,         paradigm: "Functional",         complexity: 8     },     {         id: 6,         name: "LISP",         createdBy: "John McCarthy",         year: 1958,         paradigm: "Functional",         complexity: 6     } ] 

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

Для улучшения нашего кода создадим третий файл "./graphqlData.js":

const { buildSchema } = require('graphql') const data = require('./data')  const schema = buildSchema(` type Query {     language(id: Int!): Language     languages(paradigm: String): [Language] }      type Language {         id: Int         name: String         createBy: String         year: Int         paradigm: String         complexity: Int     } `) 

Импортируем наши данные и buildSchema. Далее необходимо определить схему конечной точки GraphQL. Как видите, прежде всего мы определяем тип Query, что позволяет нам:

  • Получать определенный язык по ID (который является целым числом). Возвращается Language, о типе которого поговорим позже
  • Получать список (массив в JavaScript) языков, имеющих определенную парадигму. Типом парадигмы является строка (String), возвращается список (массив) языков (Language).

В конце мы определяем тип Language. При определении нового GraphQL типа необходимо описать запрашиваемые данные. У нас имеется ID с типом Int, название с типом String и т.д.

Двигаемся дальше. Создадим две функции, отвечающие за получение данных:

function getLanguage(args) {     return data.filter(language => language.id === args.id)[0] }  function getLanguages(args) {     return args.paradigm ?         data.filter(language => language.paradigm === args.paradigm) :         data }  const rootValue = {     language: getLanguage,     languages: getLanguages } 

Вот что делают эти функции и константа:

  • getLanguage принимает Object как аргумент и фильтрует данные, возвращая язык с определенным ID
  • getLanguages также принимает Object как аргумент и возвращает языки определенной парадигмы
  • rootValue готовит данные к использованию в express-graphql

Помните, что обе функции возвращают одинаковый тип, который мы определили в схеме.

Объединяем код:

const {     buildSchema } = require("graphql"); const data = require("./data");  const schema = buildSchema(`     type Query {         language(id: Int!): Language         languages(paradigm: String): [Language]     }      type Language {         id: Int         name: String         createdBy: String         year: Int         paradigm: String         complexity: Int     } `);  function getLanguage(args) {     return data.filter(language => language.id === args.id)[0] }  function getLanguages(args) {     return args.paradigm ?         data.filter(language => language.paradigm === args.paradigm) :         data }  const rootValue = {     language: getLanguage,     languages: getLanguages, }  module.exports = {     schema,     getLanguage,     getLanguages,     rootValue } 

Отлично. Немного изменим main.js:

const express = require('express') const expressGraphQL = require('express-graphql') const graphQLData = require('./graphQLData') const app = express() const port = process.env.PORT || 3000  app.use('/graphql', expressGraphQL({     schema: graphQLData.schema,     rootValue: graphQLData.rootValue,     graphiql: true }))  app.get('/', (req, res) => {     res.send('Hello World!') })  app.listen(port, () => {     console.log(`Server ready on port http://localhost:${port}`) }) 

Тестируем сервер:

Круто. Что если удалить некоторые значения из запроса?

Легко и просто. Что если мы хотим получить определенный язык?

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

Благодарю за внимание.

ссылка на оригинал статьи https://habr.com/ru/post/499084/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *