В тази статия ще научите концепции като schema и resolver и накрая ще научите как да създадете GraphQL сървър и да правите заявки към същия. Това покрива необходимите основи. Ако вече знаете основите, погледнете „Разработване на GraphQL сървър с nodejs + express“

Има различни начини за дефиниране на схема. Използвайте този, който работи за вас

  • Необработен подход, това използва примитивите, изложени от библиотеката graphql
  • Дефиниция на езика за заявки на GraphQL, тук ще използваме метода buildSchema(), това е много по-лесно за работа

Суровият подход има за цел да ви покаже какво се случва под капака. Ако искате да знаете само как да изградите вашите схеми с дефиниция на езика за заявки, тогава преминете към разделаbuildSchema в тази статия

Съдържание на схемата

Може да се каже, че схемата се състои от две части:

  • Дефиниране на схема, като дефинираме схема, ние дефинираме съществуващите обекти, но също така и различните заявки и мутации, които е възможно да се направят
  • Резолвери, резолверите са функции, които се извикват, когато потребителят направи заявка или мутация. Резолвърите общуват с API на трета страна или база данни, за да извършат операция и в крайна сметка връщат отговор на извикващия потребител

Определение на схемата

Една схема до голяма степен съдържа следните понятия:

  • Заявка, това са данни, подлежащи на заявка, те са или без параметри, или приемат параметър
  • Персонализирани типове, можете да създавате свои собствени типове, ако не сте доволни от основните типове, които се предоставят
  • Мутации, това са „методи“, които трябва да променят данните, като CREATE, UPDATE или DELETE
  • Резолвери, това са функции, които връщат данни обратно на потребителя
  • Фрагмент, това е многократно използвана част от схемата, която можете да използвате на много места във вашата дефиниция
  • Псевдоним, това е механизъм за преименуване, който ни позволява да преименуваме колона
  • Директива, мислете за това като за if/else и ви позволява да решите кои колони да включите/изключите в заявката си

Резолвъри

Резолверите са част от вашия екземпляр на схема, но не и част от дефиницията на схемата. Резолверите са просто функции, които отговарят, когато се опитате да извикате заявка или мутация. Резолвер функцията отговаря или директно с данни, или с обещание.

Суровият подход

В суровия подход, който ще опишем по-долу, дефиницията на схемата и резолверите са включени заедно. Ще дефинираме ресурс, който можем да извикаме, и съответния му резолвер в една дефиниция.

За да създадете схема, трябва да създадете екземпляр от типа GraphQLSchema. Има различни начини за създаване на този екземпляр, но нека първо да разгледаме най-суровия подход. При този подход имаме следния тип GraphQLSchema:

new GraphQLSchema(options)

options е от тип GraphQLSchemaConfig и изглежда по-долу:

export interface GraphQLSchemaConfig extends GraphQLSchemaValidationOptions {
  query: Maybe<GraphQLObjectType>;
  mutation?: Maybe<GraphQLObjectType>;
  subscription?: Maybe<GraphQLObjectType>;
  types?: Maybe<GraphQLNamedType[]>;
  directives?: Maybe<GraphQLDirective[]>;
  astNode?: Maybe<SchemaDefinitionNode>;
  extensionASTNodes?: Maybe<ReadonlyArray<SchemaExtensionNode>>;
}

Както виждаме по-горе, единственото задължително поле, което трябва да дадем за сега, е query. Това са прости заявки/въпроси и можем да видим, че те са от тип GraphQLObjectType. Така че нека започнем да конструираме такъв екземпляр:

import {
  graphql,
  GraphQLSchema,
  GraphQLObjectType,
  GraphQLString,
} from “graphql”;
const schema = new GraphQLObjectType({
  name: “RootQueryType”,
  fields: {
    hello: {
      type: GraphQLString,
      resolve() {
        return “world”;
      }
    }
  }
});

По-горе виждаме, че сме създали заявката hello. hello е присвоен обект, така че нека разделим този обект:

  • тип, това е типът данни, примитивен или потребителски тип
  • resolve, това е функция за разрешаване, която просто казва, ако някой ме извика, с какво трябва да отговоря

Има още полета, които бихме могли да декларираме като description, но тези две са най-важните.

Запитване за нашата схема

Следващата стъпка е да кажем на graphql за тази схема, за да можем да я използваме:

let query = `{ hello }`;
graphql(schema, query).then(result => {
  res.json(result);
});

По-горе можете да видите, че извикваме graphql с два различни параметъра:

  • схема, схемата, която току-що дефинирахме
  • заявка, това е променлива, която току-що инициализирахме и съдържа езика за заявки GraphQL

Нека увеличим мащаба на query и да видим какво прави:

{
  hello
}

По-горе просто правим заявка за известна заявка hello и това ще отговори с world, тъй като запитването за нея ще извика метода resolve(). Добре, чудесно, току-що създадохме hello world в GraphQL. Пауза за ефект :)

Въвеждане на потребителски тип

Сега има много примитиви, които можем да използваме в GraphQL и току-що разгледахме тип String. Идва момент, когато това е наистина ограничаващо и ние искаме да конструираме наши собствени типове. Така че нека направим това по-нататък:

let humanType = new GraphQLObjectType({
  name: "Human",
  fields: () => ({
    id: { type: GraphQLString },
    description: { type: GraphQLString },
    name: { type: GraphQLString }
  })
});

По-горе използваме GraphQLObjectType, за да създадем нашия персонализиран тип. Свойството fields ни позволява да изброим всички различни свойства, които нашият тип трябва да има като id, description и name.

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

let schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: “RootQueryType”,
    fields: {
      hello: {
        type: GraphQLString,
        resolve() {
          return “world”;
        }
      },
      person: {
        type: humanType,
        resolve() {
          return people[0];
        }
      }
    }
  })
});

По-горе добавихме person, който има тип humanType и по този начин използва нашия персонализиран тип. Тъй като добавихме person като queryable, сега можем да разширим нашата заявка с този тип, така:

let query = `{ hello, person { name, description } }`;
graphql(schema, query).then(result => {
  res.json(result);
});

Както можете да видите по-горе, сега добавяме следното:

{
  person {
    name, description
  }
}

Извършваме заявка за person и знаем, че лицето е от тип humanType и humanType има полета id, name и description и избираме да изберем подмножество от това, а именно name и description.

Пълният код досега изглежда така:

const people = [
  {
    id: 1,
    name: “chris”,
    description: “viking”
  },
  {
    id: 2,
    name: “maxim”,
    description: “viking”
  },
  {
    id: 3,
    name: “sherry”,
    description: “viking”
  },
  {
    id: 4,
    name: “ana”,
    description: “viking”
  }
];
let humanType = new GraphQLObjectType({
  name: "Human",
  fields: () => ({
    id: { type: GraphQLString },
    description: { type: GraphQLString },
    name: { type: GraphQLString }
  })
});
let schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: "RootQueryType",
    fields: {
      hello: {
        type: GraphQLString,
        resolve() {
          return "world";
        }
      },
      person: {
        type: humanType,
        resolve() {
          return people[0];
        }
      }
    }
})
});
let query = `{ hello, person { name, description } }`;
graphql(schema, query).then(result => {
  res.json(result);
});

Представяне на тип списък

Добре, досега описахме как можем да използваме примитив като String, но също и как можем да създадем персонализиран тип humanKind. Нека да разгледаме как можем да използваме типа списък. За да използваме тип списък, трябва да създадем нещо от тип GraphQLList. Нека добавим това към нашата съществуваща схема, така:

let schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: "RootQueryType",
    fields: {
      hello: {
        type: GraphQLString,
        resolve() {
          return "world";
        }
      },
      person: {
        type: humanType,
        resolve() {
          return people[0];
        }
      },
      people: {
        type: new GraphQLList(humanType),
        resolve() {
          return people;
        }
      }
    }
})
});

По-горе добавихме people, който използва GraphQLList и можете да видите, че го извикваме като метод и му предаваме нашия персонализиран тип humanType, за да кажем какъв тип списък е това. Това наистина е всичко.

buildSchema — използване на дефиниция на език за заявки

Досега дефинирахме схема по възможно най-болезнения начин, работейки с типове като GraphQLObjectType, GraphQLList и т.н., типове от ниско ниво, които ни принуждават да пишем много, създават нашата схема.

Използвайки този подход, ще отделим дефиницията на схемата от резолверите, така че трябва да ги създадем отделно

Разбира се, донякъде се чете, но има по-добър начин. Този по-добър начин е използването на метод, наречен buildSchema(), който ще произведе екземпляр на GraphQLSchema и ни позволява да дефинираме схемата по много по-четлив начин. Нека ти покажа:

import { buildSchema } from ‘graphql’;
var schema = buildSchema(`
  type Query {
    hello: String,
  }
`);
var root = {
  hello: () => { return 'world' }
}
let query = `{ hello, person { name }, people { name, description } }`;
graphql({
  schema,
  source: query,
  rootValue: root
}).then(result => {
  console.log(result);
});

тип по избор

За да създадем персонализиран тип, просто трябва да използваме ключовата дума type и да изберем име за нашия тип, така:

var schema = buildSchema(`
  type Person {
    id: Int,
    name: String
  },
  type Query {
    hello: String,
    person: Person
  }
`);

Това, което можете да видите по-горе е, че ние правим две неща:

  • създайте нашия тип Person
  • разширете Query с person, който е от тип Person

Това означава, че сега трябва да добавим функция за преобразуване за person, така че ако някой се опита да направи запитване за нея, да не получи грешка, така че нека направим това по-нататък:

const people = [{
  id: 1, name: ‘chris’
},
{
  id: 2, name: ‘maxim’
}]
var root = {
  hello: () => { return ‘world’ },
  person: () => people[0]
}

По-горе можете да видите, че създадохме масива people и също така разширихме нашия root, нашия резолвер със свойството person. Крайният резултат тук е, че сега някой може да направи заявка за person и това ще работи

тип списък

Нека добавим друг тип списък към нашата дефиниция на схема:

var schema = buildSchema(`
  type Person {
    id: Int,
    name: String
  },
  type Query {
    hello: String,
    person: Person,
    people: [Person]
  }
`);

По-горе вече добавихме заявката people и можем да видим, че тя е от тип списък, защото използва квадратни скоби [Person]. След това трябва да добавим функция за преобразуване за person, с която сме свикнали досега:

var root = {
  hello: () => { return 'world' },
  person: (id) => people.find(p => p.id === id),
  people: () => people
}

параметризирана заявка

Досега разглеждахме прости заявки без параметри, но най-вероятно ще трябва да изпратим някои параметри към нашата заявка, за да филтрираме нашия отговор. Можем лесно да направим това, като първо дефинираме нашата схема правилно. Имаме съществуващо свойство person, така че нека го актуализираме, за да вземе параметър, така:

var schema = buildSchema(`
  type Person {
    id: Int,
    name: String
  },
  type Query {
    hello: String,
    person( id: Int!): Person,
    people: [Person]
  }
`);

person сега изглежда по следния начин:

person( id: Int!): Person

Изглежда като подпис на извикване на метод.

Мутация

Мутацията е начинът, по който променяме данните в нашето приложение, така че да можем да извършваме операции като CREATE, UPDATE или DELETE върху ресурс. За да дефинираме мутация, просто трябва да създадем запазения тип Mutation, така:

type Mutation {
  // mutation
  // another mutation
}

След като имаме горното, можем да започнем да мислим какви неща искаме да правим. Предвид горния разказ със списък с хора, има смисъл да можете да добавите човек към този списък, така че нека създадем такава мутация, така че актуализираме нашата схема до следното:

input PersonInput {
  name: String!
},
type Mutation {
  addPerson(person: PersonInput!): String
}

Горното ни казва, че можем да извикаме метод/мутация, наречена addPerson(), която приема person като входен параметър и в крайна сметка отговаря с низ. Тук добавяме нов тип конструкция от тип input. Когато се занимаваме с мутации, трябва да конструираме неща от тип input, ако искаме да изпратим нещо по-сложно от примитив (String, Boolean и т.н.). Да, нашият PersonInput изглежда малко прост и примитивен може да е достатъчен в този случай, но реших да ви покажа как бихте могли да изпратите по-сложен вход вместо това, ако желаете.

Следващата стъпка е да дефинираме резолвер за това, така че ако някой извика addPerson() да знаем какво да правим:

const people = [{
  id: 1,
  name: 'chris'
}]
const addPerson = (person) => {
const nextId = people.length === 0 ? 1 : people[people.length -1].id + 1
people = […people, {…person, { id : nextId } }]
}
var root = {
  hello: () => { return 'world' },
  person: (id) => people.find(p => p.id === id),
  people: () => people,
  addPerson: (args) => {
    const { person } = args;
    addPerson(person);
    return 'success'
  }
}

Добавете тази точка, създадохме специфична мутация addPerson() и дефинирахме резолвер.

Сега знаем основите на създаването на GraphQL сървър.

Резюме

Научихме, че има два основни начина за деклариране на схема:

  • суровият подход, повече за писане
  • Подходът на дефиницията на схемата на GraphQL използва buildSchema()

Също така разгледахме как можем да отговорим на заявка, като извикаме resolvers, които са функции, които трябва да извлекат данните от някъде.

Накрая се научихме как да създаваме мутации.

След това ще разгледаме как да създадете схема, да напишете някои резолвери и да стартирате и стартирате GraphQL сървър

GraphQL + Node.js Express