GraphQL vs. REST - Un tutorial GraphQL
Publicat: 2022-03-11S-ar putea să fi auzit despre noul copil din jurul blocului: GraphQL. Dacă nu, GraphQL este, într-un cuvânt, o nouă modalitate de a prelua API-uri, o alternativă la REST. A început ca un proiect intern la Facebook și, de când a fost open source, a câștigat multă tracțiune.
Scopul acestui articol este să vă ajute să faceți o tranziție ușoară de la REST la GraphQL, indiferent dacă v-ați hotărât deja pentru GraphQL sau sunteți doar dispus să încercați. Nu sunt necesare cunoștințe anterioare despre GraphQL, dar este necesară o anumită familiaritate cu API-urile REST pentru a înțelege articolul.
Prima parte a articolului va începe prin a oferi trei motive pentru care personal cred că GraphQL este superior REST. A doua parte este un tutorial despre cum să adăugați un punct final GraphQL pe back-end.
Graphql vs. REST: De ce să renunți la REST?
Dacă încă ezitați dacă GraphQL este sau nu potrivit pentru nevoile dvs., aici este oferită o prezentare generală destul de extinsă și obiectivă a „REST vs. GraphQL”. Cu toate acestea, pentru primele trei motive pentru a utiliza GraphQL, citiți mai departe.
Motivul 1: Performanța rețelei
Să presupunem că aveți o resursă de utilizator în back-end cu nume, prenume, e-mail și alte 10 câmpuri. Pe client, în general, aveți nevoie doar de câteva dintre acestea.
Efectuarea unui apel REST pe punctul final /users
vă oferă înapoi toate câmpurile utilizatorului, iar clientul le folosește doar pe cele de care are nevoie. În mod clar, există o risipă de transfer de date, care ar putea fi luată în considerare pentru clienții mobili.
GraphQL preia în mod implicit cele mai mici date posibile. Dacă aveți nevoie doar de numele și prenumele utilizatorilor dvs., specificați acest lucru în interogarea dvs.
Interfața de mai jos se numește GraphiQL, care este ca un explorator API pentru GraphQL. Am creat un mic proiect în scopul acestui articol. Codul este găzduit pe GitHub și ne vom scufunda în el în a doua parte.
În panoul din stânga al interfeței este interogarea. Aici, preluăm toți utilizatorii - am face GET /users
cu REST - și obținem doar numele și numele lor.
Interogare
query { users { firstname lastname } }
Rezultat
{ "data": { "users": [ { "firstname": "John", "lastname": "Doe" }, { "firstname": "Alicia", "lastname": "Smith" } ] } }
Dacă am dori să primim și e-mailurile, adăugarea unei linii de „e-mail” sub „nume” ar face truc.
Unele back-end-uri REST oferă opțiuni precum /users?fields=firstname,lastname
pentru a returna resurse parțiale. Pentru cât merită, Google îl recomandă. Cu toate acestea, nu este implementat în mod implicit și face cererea abia lizibilă, mai ales când introduceți alți parametri de interogare:
-
&status=active
pentru a filtra utilizatorii activi -
&sort=createdAat
pentru a sorta utilizatorii după data creării lor -
&sortDirection=desc
pentru că, evident, aveți nevoie de el -
&include=projects
pentru a include proiectele utilizatorilor
Acești parametri de interogare sunt patch-uri adăugate la API-ul REST pentru a imita un limbaj de interogare. GraphQL este mai presus de toate un limbaj de interogare, care face cererile concise și precise de la început.
Motivul 2: Alegerea de design „Include vs. Endpoint”.
Să ne imaginăm că vrem să construim un instrument simplu de management de proiect. Avem trei resurse: utilizatori, proiecte și sarcini. De asemenea, definim următoarele relații între resurse:
Iată câteva dintre punctele finale pe care le expunem lumii:
Punct final | Descriere |
---|---|
GET /users | Listați toți utilizatorii |
GET /users/:id | Obțineți un singur utilizator cu id :id |
GET /users/:id/projects | Obțineți toate proiectele unui utilizator |
Punctele finale sunt simple, ușor de citit și bine organizate.
Lucrurile devin mai complicate când solicitările noastre devin mai complexe. Să luăm punctul final GET /users/:id/projects
: Să spunem că vreau să arăt doar titlurile proiectelor pe pagina principală, dar proiecte+sarcini pe tabloul de bord, fără a face mai multe apeluri REST. as suna:
-
GET /users/:id/projects
pentru pagina de start. -
GET /users/:id/projects?include=tasks
(de exemplu) pe pagina tabloului de bord, astfel încât back-end-ul să adauge toate sarcinile conexe.
Este o practică obișnuită să adăugați parametrii de interogare ?include=...
pentru ca acest lucru să funcționeze și chiar este recomandat de specificația API-ului JSON. Parametrii de interogare precum ?include=tasks
sunt încă citibili, dar în scurt timp, vom ajunge cu ?include=tasks,tasks.owner,tasks.comments,tasks.comments.author
.
În acest caz, ar fi mai înțelept să creați un punct final /projects
pentru a face acest lucru? Ceva de genul /projects?userId=:id&include=tasks
, deoarece am avea un nivel de relație mai puțin de inclus? Sau, de fapt, ar putea funcționa și un punct final /tasks?userId=:id
. Aceasta poate fi o alegere de design dificilă, chiar mai complicată dacă avem o relație de la mulți la mulți.
GraphQL folosește abordarea include
peste tot. Acest lucru face ca sintaxa de preluare a relațiilor să fie puternică și consistentă.
Iată un exemplu de preluare a tuturor proiectelor și sarcinilor de la utilizator cu id 1.
Interogare
{ user(id: 1) { projects { name tasks { description } } } }
Rezultat
{ "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" } ] } ] } } }
După cum puteți vedea, sintaxa interogării este ușor de citit. Dacă am fi vrut să mergem mai profund și să includem sarcini, comentarii, imagini și autori, nu ne-am gândi de două ori cum să ne organizăm API-ul. GraphQL facilitează preluarea obiectelor complexe.
Motivul 3: Gestionarea diferitelor tipuri de clienți
Când construim un back-end, începem întotdeauna prin a încerca să facem API-ul cât mai ușor de utilizat de către toți clienții. Cu toate acestea, clienții vor întotdeauna să sune mai puțin și să ia mai mult. Cu includeri profunde, resurse parțiale și filtrare, solicitările făcute de clienții web și de telefonie mobilă pot diferi mult una de alta.
Cu REST, există câteva soluții. Putem crea un punct final personalizat (adică un punct final alias, de exemplu, /mobile_user
), o reprezentare personalizată ( Content-Type: application/vnd.rest-app-example.com+v1+mobile+json
) sau chiar un client -specific API (cum a făcut cândva Netflix). Toate trei necesită un efort suplimentar din partea echipei de dezvoltare back-end.
GraphQL oferă mai multă putere clientului. Dacă clientul are nevoie de solicitări complexe, va construi el însuși interogările corespunzătoare. Fiecare client poate consuma același API în mod diferit.
Cum să începeți cu GraphQL
În majoritatea dezbaterilor despre „GraphQL vs. REST” de astăzi, oamenii cred că trebuie să aleagă oricare dintre cele două. Acest lucru pur și simplu nu este adevărat.
Aplicațiile moderne folosesc în general mai multe servicii diferite, care expun mai multe API-uri. De fapt, ne-am putea gândi la GraphQL ca la o poartă sau un înveliș pentru toate aceste servicii. Toți clienții ar atinge punctul final GraphQL, iar acest punct final ar atinge nivelul bazei de date, un serviciu extern precum ElasticSearch sau Sendgrid sau alte puncte finale REST.
O a doua modalitate de a folosi ambele este să aveți un punct final /graphql
separat pe API-ul dvs. REST. Acest lucru este util mai ales dacă aveți deja numeroși clienți care accesează API-ul REST, dar doriți să încercați GraphQL fără a compromite infrastructura existentă. Și aceasta este soluția pe care o explorăm astăzi.
După cum am spus mai devreme, voi ilustra acest tutorial cu un mic exemplu de proiect, disponibil pe GitHub. Este un instrument simplificat de management al proiectelor, cu utilizatori, proiecte și sarcini.
Tehnologiile utilizate pentru acest proiect sunt Node.js și Express pentru serverul web, SQLite ca bază de date relațională și Sequelize ca ORM. Cele trei modele – utilizator, proiect și sarcină – sunt definite în folderul models
. Punctele finale REST /api/users
, /api/projects
și /api/tasks
sunt expuse lumii și sunt definite în folderul rest
.

Rețineți că GraphQL poate fi instalat pe orice tip de back-end și bază de date, folosind orice limbaj de programare. Tehnologiile folosite aici sunt alese din motive de simplitate și lizibilitate.
Scopul nostru este să creăm un punct final /graphql
fără a elimina punctele finale REST. Punctul final GraphQL va lovi direct baza de date ORM pentru a prelua date, astfel încât să fie total independent de logica REST.
Tipuri
Modelul de date este reprezentat în GraphQL prin tipuri , care sunt puternic tipizate. Ar trebui să existe o mapare 1-la-1 între modelele dvs. și tipurile GraphQL. Tipul nostru de User
ar fi:
type User { id: ID! # The "!" means required firstname: String lastname: String email: String projects: [Project] # Project is another GraphQL type }
Întrebări
Interogările definesc ce interogări puteți rula în API-ul GraphQL. Prin convenție, ar trebui să existe un RootQuery
, care conține toate interogările existente. De asemenea, am subliniat echivalentul REST al fiecărei interogări:
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 }
Mutații
Dacă interogările sunt cereri GET
, mutațiile pot fi văzute ca solicitări POST
/ PATCH
/ PUT
/ DELETE
(deși într-adevăr sunt versiuni sincronizate ale interogărilor).
Prin convenție, punem toate mutațiile noastre într-o 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 }
Rețineți că am introdus noi tipuri aici, numite UserInput
, ProjectInput
și TaskInput
. Aceasta este o practică comună și cu REST, pentru a crea un model de date de intrare pentru crearea și actualizarea resurselor. Aici, tipul nostru UserInput
este tipul nostru de User
fără câmpurile id
și projects
și observați input
cuvântului cheie în loc de type
:
input UserInput { firstname: String lastname: String email: String }
Schemă
Cu tipuri, interogări și mutații, definim schema GraphQL , care este ceea ce punctul final GraphQL expune lumii:
schema { query: RootQuery mutation: RootMutation }
Această schemă este puternic tipizată și este ceea ce ne-a permis să avem acele completări automate la îndemână în GraphiQL.
Rezolvatori
Acum că avem schema publică, este timpul să spunem GraphQL ce să facă atunci când fiecare dintre aceste interogări/mutații este solicitată. Rezolvatorii fac munca grea; pot, de exemplu:
- Atingeți un punct final REST intern
- Apelați la un microserviciu
- Atingeți stratul bazei de date pentru a efectua operațiuni CRUD
Alegem a treia opțiune în aplicația noastră exemplu. Să aruncăm o privire la fișierul nostru de rezolvare:
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 }
Aceasta înseamnă că, dacă interogarea user(id: ID!)
este solicitată pe GraphQL, atunci returnăm User.findById()
, care este o funcție ORM Sequelize, din baza de date.
Ce zici de alăturarea altor modele în cerere? Ei bine, trebuie să definim mai multe soluții:
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 } */
Deci, atunci când solicităm câmpul projects
într-un tip de User
în GraphQL, această îmbinare va fi atașată la interogarea bazei de date.
Și, în sfârșit, soluții pentru mutații:
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 }
Te poți juca cu asta aici. De dragul de a păstra datele de pe server curate, am dezactivat rezolutoarele pentru mutații, ceea ce înseamnă că mutațiile nu vor face operațiuni de creare, actualizare sau ștergere în baza de date (și astfel returnează null
pe interfață).
Interogare
query getUserWithProjects { user(id: 2) { firstname lastname projects { name tasks { description } } } } mutation createProject { createProject(input: {name: "New Project", UserId: 2}) { id name } }
Rezultat
{ "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" } ] } ] } } }
Poate dura ceva timp pentru a rescrie toate tipurile, interogările și soluțiile pentru aplicația dvs. existentă. Cu toate acestea, există o mulțime de instrumente pentru a vă ajuta. De exemplu, există instrumente care traduc o schemă SQL într-o schemă GraphQL, inclusiv soluții!
Punând totul laolaltă
Cu o schemă bine definită și soluții cu privire la ce să facem pentru fiecare interogare a schemei, putem monta un punct final /graphql
pe back-end-ul nostru:
// Mount GraphQL on /graphql const schema = makeExecutableSchema({ typeDefs, // Our RootQuery and RootMutation schema resolvers: resolvers() // Our resolvers }); app.use('/graphql', graphqlExpress({ schema }));
Și putem avea o interfață GraphiQL frumos pe back-end. Pentru a face o solicitare fără GraphiQL, pur și simplu copiați adresa URL a cererii și rulați-o cu cURL, AJAX sau direct în browser. Desigur, există câțiva clienți GraphQL care vă ajută să construiți aceste interogări. Vezi mai jos câteva exemple.
Ce urmeaza?
Scopul acestui articol este să vă ofere o idee despre cum arată GraphQL și să vă arate că este cu siguranță posibil să încercați GraphQL fără a vă arunca infrastructura REST. Cel mai bun mod de a ști dacă GraphQL se potrivește nevoilor dvs. este să îl încercați singur. Sper că acest articol vă va face să faceți scufundare.
Există o mulțime de caracteristici despre care nu am discutat în acest articol, cum ar fi actualizări în timp real, loturi pe server, autentificare, autorizare, cache pe partea client, încărcare de fișiere etc. O resursă excelentă pentru a afla despre aceste caracteristici este Cum să GraphQL.
Mai jos sunt câteva alte resurse utile:
Instrument pe partea serverului | Descriere |
---|---|
graphql-js | Implementarea de referință a GraphQL. Îl puteți folosi cu express-graphql pentru a crea un server. |
graphql-server | Un server GraphQL all-in-one creat de echipa Apollo. |
Implementări pentru alte platforme | Ruby, PHP etc. |
Instrument la nivelul clientului | Descriere |
---|---|
Releu | Un cadru pentru conectarea React cu GraphQL. |
apollo-client. | Un client GraphQL cu legături pentru React, Angular 2 și alte framework-uri front-end. |
În concluzie, cred că GraphQL este mai mult decât hype. Nu va înlocui REST mâine încă, dar oferă o soluție performantă pentru o problemă reală. Este relativ nou, iar cele mai bune practici sunt încă în curs de dezvoltare, dar este cu siguranță o tehnologie despre care vom auzi în următorii doi ani.