GraphQL to język zapytań do API, który pozwala nam na bardziej wydajne i elastyczne pobieranie danych.
Został stworzony przez developerów Facebooka jako alternatywa dla REST API. Początkowo myślano, że koncepcja ta jest nierozerwalna z Reactem, ponieważ na konferencjach narzędzia te były prezentowane razem. Ale nie jest to prawdą. GraphQL można używać z każdą technologią i frontendowym frameworkiem.
Głównymi powodami, dla których Facebook zaczął tworzyć tę koncepcję, są coraz częstsze użycia urządzeń mobilnych, wykorzystywanie urządzeń o małej mocy IoT czy słabe sieci. GraphQL rozwiązuje te problemy, pozwalając na ograniczanie ilości przesyłanych danych do niezbędnego minimum.
Głównym założeniem GraphQL jest deklaratywne pobieranie danych, gdzie to klient określa, co dokładnie potrzebuje otrzymać w odpowiedzi z API. Zamiast kilku enpointów, z których każdy zwraca nam inne dane, wystarczy jeden endpoint, który w odpowiedzi zwraca dokładnie to, o co został odpytany.
Z dalszej części artykułu dowiesz się, kiedy wykorzystywać GraphQL, a kiedy REST API oraz w czym GraphQL ma przewagę.
GraphQL vs REST API
REST API jest od wielu lat najpopularniejszym sposobem pobierania danych z serwera, ale ma kilka wad, z którymi próbowali sobie poradzić twórcy języka GraphQL. Jedną z nich jest niska elastyczność narzędzia w momencie, kiedy zmieniają się wymagania klienta. Trzeba wówczas zmieniać API, dopisywać nowe lub wersjonować, jeśli zachodzą jakieś znaczące zmiany.
GraphQL powstał po to, aby poradzić sobie z potrzebą bardziej efektywnej współpracy między klientem i serwerem.
Żeby zobrazować różnicę między REST API a GraphQL, spójrzmy na krótki przykład. Niech to będzie strona użytkownika na Facebooku, gdzie potrzebujemy wyświetlić:
- imię i nazwisko użytkownika,
- napisane przez niego posty
- jego trzech followersów.
Jakby to wyglądało z użyciem REST API?
Żeby otrzymać wyżej opisany zestaw danych, serwer musiałby udostępniać 3 różne endpointy:
- Pierwszy to
/users/id
, skąd możemy otrzymać dane użytkownika. Zwykle takie API zwróci nam wszystkie dane dotyczące danej osoby, czyli dużo więcej informacji niż jest wymagane. - W dalszej kolejności należałoby wyciągnąć dane na temat postów opublikowanych przez użytkownika –
/users/id/posts
. Jeśli takie API nie oferuje limitowania danych, zwrócona zostanie lista wszystkich postów, czyli znów za dużo danych względem tego, co przewiduje projekt. - Kolejny endpoint to
/users/id/followers
– po raz kolejny zderzamy się z tym, że dostajemy masę danych, które są zbędne, czyli listę wszystkich followersów danego użytkownika.
Oczywiście, można zaprojektować API w taki sposób, aby odpowiadało wymaganiom tej konkretnej podstrony, ale zwykle się tego nie praktykuje. Jeśli API miałoby być dostosowywane za każdym razem, kiedy zmieni się design aplikacji lub wymagania klienta, trwałoby to długo i ograniczało szybkość dokonywania zmian w aplikacji.
Jak zrobić to samo z użyciem GraphQL?
Aby uzyskać ten sam efekt z użyciem GraphQL, wystarczy odwołać się tylko do jednego endpointa, który można odpytać o wszystko, co serwer może zwrócić.
W praktyce wygląda to tak, że klient wysyła request za pomocą metody POST, w którego body znajduje się zapytanie odzwierciedlające zestaw danych, jaki ma być zwrócony z serwera. Pytając o dane usera o konkretnym ID, można ograniczyć odbierane dane do imienia i nazwiska, jednocześnie w tym samym zapytaniu wyciągając posty i 3 ostatnio dodanych followersów.
Kiedy serwer otrzymuje takie zapytanie, procesuje je i wyciąga z bazy tylko te dane, które zostały sprecyzowane w zapytaniu, a następnie zwraca je do klienta w postaci JSON-a.
Przewaga GraphQL nad REST API
Problemy, z jakimi rozprawia się GraphQL, to:
- z jednej strony overfetching, czyli zwracanie przez serwer zbyt dużej ilości danych, które aktualnie nie są do niczego potrzebne,
- jak również underfetching, czyli fakt, że serwer zwraca zbyt mało danych i trzeba zwracać się do kilku endpointów, żeby uzyskać wystarczające informacje.
I znów – w REST API można oczywiście stworzyć endpoint, który zwróci dokładnie taki zestaw danych, jaki jest nam potrzebny i często się tak robi. Wadą takiego rozwiązania jest niemożność dokonywania szybkich zmian na frontendzie. Zawsze potrzebna jest praca osoby z backendu, która poprawi, zmieni API, jeśli pojawi się potrzeba nowego zestawu danych np.: do nowego widoku.
W GraphQL ten problem nie istnieje. Zmiany na frontendzie mogą być zrobione bez konieczności jakiegokolwiek ingerowania w backend.
Kolejną zaletą GraphQL jest to, że każdy request dokładnie określa, jakich danych potrzebuje. Możemy te requesty monitorować i np.: stwierdzić, które dane nie są używane przez żadnego klienta lub są używane sporadycznie i można je określić jako deprecated.
Następna przewaga nad REST API to użycie silnego typowania danych. Wszystkie typy, które są widoczne w API, są zapisane w schemacie danych przy użyciu tzw. schema definition language. Po ustaleniu schematu developerzy frontendu mogą już mockować dane i nie ma potrzeby dalszej komunikacji między nimi a osobami tworzącymi backend.
Serwer GraphQL
Tworzone po stronie serwera GraphQL Schema to najważniejsza część pracy z GraphQL API, często nazywana kontraktem między klientem i serwerem. Opisuje typy danych oraz możliwości API – to „jak i co” klient może uzyskać od serwera.
Schema
GraphQL ma swój własny syntax do definiowania schematu API i jest to Schema Definition Language.
type Post {
id: ID!
title: String!
author: User!
}
type User {
id: ID!
name: String!
age: Int!
posts: [Post!]!
followers: [User]
photoUrl: String!
}
W powyższym przykładzie widać definicję dwóch prostych typów: User i Post
oraz relacji jeden do wielu między typami. Typ User
ma odwołanie do postów – każda osoba może mieć do siebie przypisaną listę postów. W drugą stronę – Post
może mieć jednego autora. Wykrzyknik umieszczony za typem pola oznacza, że wartość jest wymagana i nie może być nullem.
Oprócz typów danych, osoba tworząca serwer GraphQL musi zawsze dodać i opisać typy root
czyli Query, Mutation i Subscription
.
Query
Pracując z REST API, dane pobierane są z konkretnych endpointów, z których każdy ma z góry zdefiniowaną strukturę danych, jaką zwraca. Czyli już na poziomie URL do API określone jest, co zostanie zwrócone.
W GraphQL podejście jest zupełnie inne. Serwer GraphQL wystawia nam tylko jeden endpoint, w którym nie ma z góry określonej struktury zwracanych danych.
type Query {
allUsers(first: Int): [User!]
user(id: ID!): User
allPosts: [Post!]
post(id: ID!): Post
}
Mutation
Do tworzenia, edycji lub usuwania danych GraphQL wykorzystuje tzw. mutacje. Struktura mutacji jest bardzo podobna do query.
type Mutation {
createUser(name: String!, age: Int!): User!
updateUser(id: ID!, name: String!, age: String!): User!
deleteUser(id: ID!): User!
createPost(title: String!): Post!
updatePost(id: ID!, title: String!): Post!
deletePost(id: ID!): Post!
}
Znowu należy użyć pola root – tutaj createUser
. Pole createUser
otrzymuje 2 argumenty: name
oraz age
i zwraca obiekt nowo utworzonej wartości typu User
. Równie dobrze createUser
mogłoby zwrócić tylko identyfikator nowo utworzonego rekordu lub jakiś status i dane.
Subscription
Trzeci typ zapytań to subscription, czyli nasłuchiwanie na zmiany. Subskrypcja używana jest do połączenia z serwerem w czasie rzeczywistym, tak, żeby aplikacja była od razu informowana np. o utworzeniu nowego rekordu.
type Subscription {
newUser: User!
updatedUser: User!
deletedUser: User!
newPost: Post!
updatedPost: Post!
deletedPost: Post!
}
Kiedy klient subskrybuje się na event, wtedy inicjalizuje i utrzymuje stałe połączenie z serwerem. Zatem, kiedy dany event się wydarzy, serwer wypycha dane do klienta.
Resolvers
Kiedy do serwera GraphQL trafia zapytanie, jest ono przechwytywane przez resolvery, czyli funkcje dostarczające dane.
W implementacji serwera każde z pól odwołuje się do dedykowanego resolvera. Oznacza to, że każdy z typów zawartych w schema ma zdefiniowane pola, które należy obsłużyć za pomocą resolverów. Resolver zawiera typy odzwierciedlające schema i pod każdy klucz z danego typu tworzy funkcję, która będzie zwracała dane dla tego typu.
Serwery GraphQL mają pewne resolvery domyślne. Potrafią obsługiwać różne typy danych takie jak stringi, liczby, obiekty i tablice.
const resolvers = {
Query: {
allUsers: () => usersList,
user: (parent, args, context, info) => usersList.find((user) => user.id == args.id),
allPosts: () => postsList,
post: (parent, args, context, info) => usersList.find((post) => post.id == args.id),
},
Mutation: {
createUser: (parent, args, context, info) => {
const newUser = {
id: usersList.length + 1,
name: args.name,
age: args.age,
};
usersList.push(newUser);
return newUser;
},
…
},
Post: {
title: (parent, args, context, info) => parent.title,
author: (parent, args, context, info) => usersList.find((user) => user.id == parent.author_id),
},
User: {
…,
posts: (parent, args, context, info) => postsList.filter((post) => post.author_id == parent.id),
},
};
Funkcja resolvera przyjmuje 4 argumenty (parent, args, context i info
), o których więcej poniżej:
parent
– to obiekt rodzica, czyli obiekt, który był dostarczony poziom wyżej np.: dla resolveraauthor
w poluparent
przekazany będziePost
,args
– to argumenty przekazane do resolvera, np.: szukane ID,- context – to obiekt współdzielony między wszystkimi resolverami. Zawiera min..: informacje o autentykacji,
- info – trzyma informacje dotyczące zapytania.
Kiedy GraphQL, a kiedy REST API?
Okazuje się, że te dwie metody nie muszą działać osobno i mogą z powodzeniem ze sobą współistnieć. Wiele systemów działa tak, że po stronie klienta np.: na urządzeniu IoT, aplikacji mobilnej czy webowej, działa GraphQL i tu konstruowane są zapytania GraphQL. Dane te są przesyłane do resolvera na serwerze GraphQL, a resolver odpytuje np.: mikroserwisy już z użyciem REST API.
GraphQL lepiej sprawdzi się w aplikacji, która potrzebuje różnych danych na każdej ze stron. Natomiast, jeśli są to duże ilości danych, lepiej użyć REST API, ponieważ zbyt skomplikowane zapytania do GraphQL mogą się okazać wolniejsze niż zapytania do kilku REST-owych endpointów. Nie podlega też dyskusji fakt, że REST API buduje się szybciej i łatwiej, dlatego dla małych aplikacji nie ma sensu budować serwera GraphQL.
Zdarzają się również konkretne przypadki, w których konieczne będzie zrezygnowanie z GraphQL. Ograniczeniem jest fakt, że GraphQL nie poradzi sobie z zagnieżdżeniem zapytań, których głębokości nie można określić np.: menu i podmenu, gdzie każdy element podmenu może mieć kolejne podmenu. Klient nie jest w stanie stwierdzić, jaki jest poziom zagnieżdżeń, dlatego nie może jednoznacznie określić budowy zapytania.
Ciężko jednoznacznie określić, czy GraphQL jest lepszy i czy może wyprzeć REST API, jednak pewne jest, że obie technologie mogą z powodzeniem współgrać i współpracować ze sobą, a każda z nich może sprawdzić się lepiej w konkretnych przypadkach użycia.
***
Jeśli interesuje Was temat REST API, zachęcamy do zerknięcia również do innych artykułów naszych ekspertów.
Zostaw komentarz