Може би сте чували за новото дете около блока: GraphQL. Ако не, GraphQL е с една дума нов начин за извличане API , алтернатива на REST. Започна като вътрешен проект във Facebook и тъй като беше с отворен код, той спечели много сцепление .
Целта на тази статия е да ви помогне да направите лесен преход от REST към GraphQL, независимо дали вече сте се сетили за GraphQL или просто сте готови да опитате. Не са необходими предварителни познания за GraphQL, но е необходимо познаване на REST API, за да се разбере статията.
Първата част на статията ще започне с посочване на три причини, поради които аз лично смятам, че GraphQL превъзхожда REST. Втората част е урок за това как да добавите крайна точка GraphQL на вашия back-end.
Ако все още се колебаете дали GraphQL е подходящ за вашите нужди, е даден доста обширен и обективен преглед на „REST срещу GraphQL“ тук . Въпреки това, поради моите три основни причини да използвам GraphQL, прочетете нататък.
Да предположим, че имате потребителски ресурс в задната част с име, фамилия, имейл и 10 други полета. За клиента обикновено се нуждаете само от няколко такива.
Извършване на REST повикване на /users
endpoint ви връща всички полета на потребителя и клиентът използва само онези, от които се нуждае. Очевидно има някои отпадъци от прехвърляне на данни, което може да е съображение за мобилните клиенти.
GraphQL по подразбиране извлича възможно най-малките данни. Ако имате нужда само от собствено и фамилно име на вашите потребители, вие посочвате това в заявката си.
Интерфейсът по-долу се нарича GraphiQL, което е като API изследовател за GraphQL. Създадох малък проект за целта на тази статия. Кодът се хоства на GitHub , и ще се потопим във него във втората част.
В левия прозорец на интерфейса е заявката. Тук извличаме всички потребители - бихме направили GET /users
с REST - и само получаване на техните собствени и фамилни имена.
Заявка
query { users { firstname lastname } }
Резултат
{ 'data': { 'users': [ { 'firstname': 'John', 'lastname': 'Doe' }, { 'firstname': 'Alicia', 'lastname': 'Smith' } ] } }
Ако искахме да получим и имейлите, добавянето на ред „имейл“ под „фамилно име“ би свършило работа.
Някои REST back-end предлагат опции като /users?fields=firstname,lastname
за връщане на частични ресурси. За какво си струва, Google го препоръчва . Той обаче не е реализиран по подразбиране и прави заявката едва четима, особено когато хвърляте други параметри на заявката:
&status=active
за филтриране на активни потребители&sort=createdAat
за сортиране на потребителите по датата на тяхното създаване&sortDirection=desc
защото явно ти трябва&include=projects
да включва проектите на потребителитеТези параметри на заявката са кръпки, добавени към REST API за имитация на език на заявка. GraphQL е преди всичко език за заявки, който прави заявките кратки и точни от самото начало.
Нека си представим, че искаме да изградим прост инструмент за управление на проекти. Разполагаме с три ресурса: потребители, проекти и задачи. Ние също така дефинираме следните връзки между ресурсите:
Ето някои от крайните точки, които излагаме на света:
Крайна точка | Описание |
---|---|
GET /users | Избройте всички потребители |
GET /users/:id | Вземете единичния потребител с id: id |
GET /users/:id/projects | Вземете всички проекти на един потребител |
Крайните точки са прости, лесно четими и добре организирани.
Нещата стават по-сложни, когато исканията ни стават по-сложни. Да вземем GET /users/:id/projects
крайна точка: Кажете, че искам да показвам само заглавията на проектите на началната страница, но проекти + задачи на таблото за управление, без да извършвам множество REST повиквания. Бих се обадил:
GET /users/:id/projects
за началната страница.GET /users/:id/projects?include=tasks
(например) на страницата на таблото за управление, така че задният край да добавя всички свързани задачи.Честа практика е да се добавят параметри на заявката ?include=...
за да работи това и дори се препоръчва от JSON API спецификация. Параметри на заявката като ?include=tasks
са все още четими, но скоро ще стигнем до ?include=tasks,tasks.owner,tasks.comments,tasks.comments.author
.
В този случай би ли било по-разумно да създадете /projects
крайна точка за това? Нещо като /projects?userId=:id&include=tasks
, тъй като ще имаме едно ниво на връзка по-малко за включване? Или всъщност a /tasks?userId=:id
крайната точка също може да работи. Това може да е труден избор на дизайн, още по-сложно ако имаме връзка много към много.
GraphQL използва include
подход навсякъде. Това прави синтаксиса за извличане на връзки мощен и последователен.
Ето пример за извличане на всички проекти и задачи от потребителя с идентификатор 1.
Заявка
{ user(id: 1) { projects { name tasks { description } } } }
Резултат
{ 'data': { 'user': { 'projects': [ { 'name': 'Migrate from REST to GraphQL', 'tasks': [ { 'description': 'Read tutorial' }, { 'description': 'Start coding' } ] }, { 'name': 'Create a blog', 'tasks': [ { 'description': 'Write draft of article' }, { 'description': 'Set up blog platform' } ] } ] } } }
Както можете да видите, синтаксисът на заявката е лесно четим. Ако искахме да влезем по-дълбоко и да включим задачи, коментари, снимки и автори, нямаше да мислим два пъти как да организираме нашия API. GraphQL улеснява извличането на сложни обекти.
Когато изграждаме back-end, винаги започваме, като се опитваме да направим API възможно най-широко използваем от всички клиенти. И все пак клиентите винаги искат да звънят по-малко и да взимат повече. При дълбоко включване, частични ресурси и филтриране заявките, направени от уеб и мобилни клиенти, могат много да се различават една от друга.
С REST има няколко решения. Можем да създадем персонализирана крайна точка (т.е. псевдоним, напр. /mobile_user
), Персонализирано представяне (Content-Type: application/vnd.rest-app-example.com+v1+mobile+json
) или дори специфичен за клиента API (като Netflix веднъж го направи ). И трите изискват допълнителни усилия от екипа за разработка на заден план.
GraphQL дава повече мощност на клиента. Ако клиентът се нуждае от сложни заявки, той сам ще изгради съответните заявки. Всеки клиент може да използва един и същ API по различен начин.
В повечето дебати за „GraphQL срещу REST“ днес хората мислят, че трябва да изберат едно от двете. Това просто не е вярно.
Съвременните приложения обикновено използват няколко различни услуги, които разкриват няколко API. Всъщност бихме могли да мислим за GraphQL като шлюз или обвивка към всички тези услуги. Всички клиенти щяха да ударят крайната точка GraphQL и тази крайна точка би ударила слоя на базата данни, външна услуга като ElasticSearch или Sendgrid или други крайни точки REST.
Вторият начин за използване и на двете е да има отделен /graphql
крайна точка на вашия REST API. Това е особено полезно, ако вече имате много клиенти, които удрят вашия REST API, но искате да опитате GraphQL, без да компрометирате съществуващата инфраструктура. И това е решението, което изследваме днес.
Както беше казано по-рано, ще илюстрирам този урок с малък примерен проект, наличен на GitHub . Това е опростен инструмент за управление на проекти с потребители, проекти и задачи.
Технологиите, използвани за този проект, са Node.js и Express за уеб сървъра, SQLite като релационна база данни и Sequelize като ORM. Трите модела - потребител, проект и задача - са дефинирани в models
папка. Останалите крайни точки /api/users
, /api/projects
и /api/tasks
са изложени на света и са дефинирани в rest
папка.
Имайте предвид, че GraphQL може да бъде инсталиран на всякакъв тип back-end и база данни, използвайки всеки език за програмиране. Използваните тук технологии са избрани с оглед на простотата и четливостта.
Нашата цел е да създадем /graphql
крайна точка, без да премахвате REST крайните точки. Крайната точка на GraphQL ще удари ORM на базата данни директно, за да извлече данни, така че да е напълно независима от логиката REST.
Моделът на данни е представен в GraphQL от видове , които са силно написани. Трябва да има картографиране 1 към 1 между вашите модели и типове GraphQL. Нашите User
тип ще бъде:
type User { id: ID! # The '!' means required firstname: String lastname: String email: String projects: [Project] # Project is another GraphQL type }
Заявки дефинирайте какви заявки можете да изпълнявате на вашия API на GraphQL. По споразумение трябва да има RootQuery
, което съдържа всички съществуващи заявки. Също така посочих REST еквивалента на всяка заявка:
type RootQuery { user(id: ID): User # Corresponds to GET /api/users/:id users: [User] # Corresponds to GET /api/users project(id: ID!): Project # Corresponds to GET /api/projects/:id projects: [Project] # Corresponds to GET /api/projects task(id: ID!): Task # Corresponds to GET /api/tasks/:id tasks: [Task] # Corresponds to GET /api/tasks }
Ако заявките са GET
искания, мутации може да се разглежда като POST
/ PATCH
/ PUT
/ DELETE
заявки (въпреки че всъщност те са синхронизирани версии на заявките).
По споразумение поставяме всичките си мутации в RootMutation
:
type RootMutation { createUser(input: UserInput!): User # Corresponds to POST /api/users updateUser(id: ID!, input: UserInput!): User # Corresponds to PATCH /api/users removeUser(id: ID!): User # Corresponds to DELETE /api/users createProject(input: ProjectInput!): Project updateProject(id: ID!, input: ProjectInput!): Project removeProject(id: ID!): Project createTask(input: TaskInput!): Task updateTask(id: ID!, input: TaskInput!): Task removeTask(id: ID!): Task }
Имайте предвид, че тук въведохме нови типове, наречени UserInput
, ProjectInput
и TaskInput
. Това е често срещана практика и с REST, за да се създаде модел на входни данни за създаване и актуализиране на ресурси. Ето, нашите UserInput
type е нашият User
тип без id
и projects
полета и забележете ключовата дума input
вместо type
:
input UserInput { firstname: String lastname: String email: String }
С типове, заявки и мутации ние дефинираме Схема на GraphQL , което е това, което крайната точка GraphQL излага на света:
schema { query: RootQuery mutation: RootMutation }
Тази схема е силно написана и е това, което ни позволи да разполагаме с тези удобни автодовършвания GraphiQL .
Сега, когато разполагаме с публичната схема, е време да кажем на GraphQL какво да прави, когато се иска всяка от тези заявки / мутации. Решители вършете упоритата работа; те могат например:
Избираме третата опция в нашето примерно приложение. Нека да разгледаме нашите файл за разрешаване :
const models = sequelize.models; RootQuery: { user (root, { id }, context) { return models.User.findById(id, context); }, users (root, args, context) { return models.User.findAll({}, context); }, // Resolvers for Project and Task go here }, /* For reminder, our RootQuery type was: type RootQuery { user(id: ID): User users: [User] # Other queries }
Това означава, че ако user(id: ID!)
заявката се иска в GraphQL, след което връщаме User.findById()
, която е Sequelize ORM функция, от базата данни.
Какво ще кажете за присъединяването към други модели в заявката? Е, трябва да дефинираме повече решаващи устройства:
User: { projects (user) { return user.getProjects(); // getProjects is a function managed by Sequelize ORM } }, /* For reminder, our User type was: type User { projects: [Project] # We defined a resolver above for this field # ...other fields } */
Така че, когато поискаме projects
поле в User
тип в GraphQL, това присъединяване ще бъде добавено към заявката за база данни.
И накрая, разделители за мутации:
RootMutation: { createUser (root, { input }, context) { return models.User.create(input, context); }, updateUser (root, { id, input }, context) { return models.User.update(input, { ...context, where: { id } }); }, removeUser (root, { id }, context) { return models.User.destroy(input, { ...context, where: { id } }); }, // ... Resolvers for Project and Task go here }
Можете да си поиграете с това тук. За да поддържам данните на сървъра чисти, деактивирах резолверите за мутации, което означава, че мутациите няма да правят никакви операции по създаване, актуализиране или изтриване в базата данни (и по този начин връщат null
на интерфейса) .
Заявка
query getUserWithProjects { user(id: 2) { firstname lastname projects { name tasks { description } } } } mutation createProject { createProject(input: {name: 'New Project', UserId: 2}) { id name } }
Резултат
{ 'data': { 'user': { 'firstname': 'Alicia', 'lastname': 'Smith', 'projects': [ { 'name': 'Email Marketing Campaign', 'tasks': [ { 'description': 'Get list of users' }, { 'description': 'Write email template' } ] }, { 'name': 'Hire new developer', 'tasks': [ { 'description': 'Find candidates' }, { 'description': 'Prepare interview' } ] } ] } } }
Може да отнеме известно време, за да пренапишете всички типове, заявки и преобразуватели за вашето съществуващо приложение. Съществуват обаче много инструменти, които да ви помогнат. Например, там са инструменти които превеждат SQL схема в GraphQL схема, включително резолвери!
С добре дефинирана схема и разделители за това какво да правим при всяка заявка на схемата, можем да монтираме /graphql
крайна точка на нашия back-end:
// Mount GraphQL on /graphql const schema = makeExecutableSchema({ typeDefs, // Our RootQuery and RootMutation schema resolvers: resolvers() // Our resolvers }); app.use('/graphql', graphqlExpress({ schema }));
И ние можем да имаме красив интерфейс GraphiQL на нашия гръб. За да направите заявка без GraphiQL, просто копирайте URL адреса на заявката и го стартирайте с cURL, AJAX или директно в браузъра. Разбира се, има някои клиенти на GraphQL, които ще ви помогнат да изградите тези заявки. Вижте по-долу за някои примери.
Целта на тази статия е да ви даде представа за това как изглежда GraphQL и да ви покаже, че определено е възможно да опитате GraphQL, без да изхвърляте вашата REST инфраструктура. Най-добрият начин да разберете дали GraphQL отговаря на вашите нужди е да опитате сами. Надявам се, че тази статия ще ви накара да се потопите.
Има много функции, за които не сме обсъждали в тази статия, като актуализации в реално време, пакетиране от страна на сървъра, удостоверяване, упълномощаване, кеширане от страна на клиента, качване на файлове и др. Отличен ресурс за научаване на тези функции е Как да GraphQL .
По-долу има някои други полезни ресурси:
Инструмент от страна на сървъра | Описание |
---|---|
graphql-js | Референтната реализация на GraphQL. Можете да го използвате с express-graphql за да създадете сървър. |
graphql-server | Всичко в едно GraphQL сървър, създаден от Екип на Аполон . |
Приложения за други платформи | Ruby, PHP и др. |
Инструмент от страна на клиента | Описание |
---|---|
Реле | Рамка за свързване на React с GraphQL. |
аполон-клиент . | Клиент на GraphQL с обвързвания за Реагирайте , Ъглова 2 , и други фронтови рамки . |
В заключение вярвам, че GraphQL е нещо повече от хайп. Все още няма да замени REST утре, но предлага ефективно решение на истински проблем. Той е сравнително нов и най-добрите практики все още се развиват, но определено е технология, за която ще чуем през следващите няколко години.
GraphQL е език за заявки и алтернатива на REST. Започна като вътрешен проект във Facebook.
GraphQL обединява всичките ви RESTful крайни точки в една и използва по-малко мрежов трафик.