Zapewne nieraz spotkałeś się z potrzebą stworzenia lub obsłużenia API. Często te API nie działały tak jak powinny, były nieintuicyjne, i żeby pobrać określone dane musiałeś wykonać kilka zapytań. Często nawet pisząc API zastanawiasz się co zwrócić, jakie endpoint-y stworzyć, jaki kod HTTP zwrócić, co zwrócić w body, co w nagłówkach. Istnieje, wiele poradników, jak robić API począwszy od API na poziomie 1 do HATEOAS(tutaj polecam przeczytać artykuł Richardsona na temat dojrzałości API.

Zakładając, że Twoje API jest napisane zgodnie z HATEOAS(Hypertext As The Engine Of Application State), samo się odkrywa, łatwo odgadnąć kolejne endpoint-y to nadal pozostaje problem, wielu zapytań i tak zwanego overfetchingu i underfetchingu.

Overfetching występuje wtedy, gdy dostajemy więcej danych w odpowiedzi niż potrzebujemy.
Underfetching natomiast to sytuacja, w której nie mamy wszystkich potrzebnych danych i musimy wykonać kolejne zapytanie by je dostać.

Nowoczesne aplikacje webowe, bardzo często wykorzystują architekturę API na *backend-zie i react.js/vue,js/angular na *frontend-zie i odpytują API od dane. I tutaj z pomocą przychodzi GraphQL, do którego mam zamiar Cię przekonać.

GraphQL

GraphQL to język zapytań do struktury, którą sami sobie tworzymy. Język ten zbudowany jest z pewnych składowych takich jak

  • zapytania
  • typy
  • pola wejściowe oraz wyjściowe
  • mutacje

oraz bardziej zaawansowane rzeczy:

  • interfejsy
  • unie
  • subskrypcje
  • aliasy
  • dyrektywy

Od REST-a do GraphQL-a

Zajmijmy się pobieraniem danych z GraphQL, do tego wykorzystam przykład API w architekturze REST i dokładnie wykonanie takich zapytań w GraphQL. Załóżmy, że mamy stronę główną na której wyświetlamy następujące dane: listę artykułów, nazwę zalogowanego użytkownika(id 12) oraz jego przyjaciół, aby zrobić to w architekturze REST należałoby wykonać mniej więcej takie zapytania

GET /articles
GET /users/12
GET /users/12/friends

i tutaj mamy już następujące problemy, założmy, że endpoint /users zawiera o wiele więcej danych niż potrzebujemy, np listę artykułów, które napisał, albo jego komentarze. Nigdy nie wiemy jakich dokładnie danych od użytkownika będziemy potrzebować, więc najczęściej dajemy wszystko co mamy, albo tworzymy widoki dla takiego endpoint-a

GET /users/12?view=extended

który może np nam zwrócić również przyjaciół użytkownika. Zaraz może się okazać, że tych widoków potrzebujemy jeszcze więcej, więc pewnie jakiś programista wpadnie na pomysł, żeby dokładnie sprecyzować czego potrzebujemy. Czyli zamiast view=extended zostanie użyte mniej więcej coś takiego

GET /users/12?fields={id,name,friends={id,name}}

przy takim zapytaniu dostaniemy dokładnie to co chcemy i odpadnie nam problem kiedy z jakiegoś endpoint-a musimy pobrać więcej informacji niż potrzebujemy. Jednakże nadal nie będziemy w stanie pobrać artykułów na stronę i będziemy musieli zrobić kolejne zapytanie. Poza tym API są zwykle bezstanowe więc nie będziemy mieli informacji na temat aktualnie zalogowanego użytkownika, tylko musimy explicit podać jego ID. GraphQL rozwiązuje te wszystkie problemy.

Tworzenie typów

typ w GraphQL jest mniej więcej tym samym, czym typ/klasa w programowaniu. Określa on czego możemy się spodziewać prosząc o dane pole. Typy mogą być proste jak i złożone, można też tworzyć własne typy na podstawie już istniejących. Przykładem prostego typu może być „name” !string. Pewnie zauważyłeś, że użyłem ! oznacza to, że dane pole jest wymagane. Przykładem złożonego typu może być „User”.

Stwórzmy więc typ użytkownik

type User {
    id ID,
    userId: Int!,
    name: String!,
    friends: [User]!
}

typ użytkownik posiada następujące pola

id typu ID jest to specjalny typ w GraphQL, który jest stringiem i jest wykorzystywany np przez Apollo(biblioteka frontowa do GraphQL), aby efektywnie zarządzać obiektami.

userId ->typ int reprezentuje ID użytkownika pole wymagane
name -> typ string nazwa użytkownika wymagane
friends -> tutaj ! oznacza, że zawsze powinniśmy dostać tablicę, a w niej mogą być inne obiekty typu User

stwórzmy typ Article:

type Article {
    id ID,
    author: User!,
    title: String!,
    summary: String!,
    content: String!,
}

Tworzenie pól

Mamy typ, ale potrzebujemy pola, pole jest tym czym obiekt w PHP. Każde pole musi mieć zaprogramowaną funkcję resolve(), która w momencie kiedy poprosimy o to pole, zwróci nam odpowiednie dane. Funkcja ta działa w kontekście oraz ma dostęp min do informacji o jakie pola użytkownik zapytał. Po co kontekst? Bo czym innym jest User wywołany bez kontekstu, a czym innym w kontekście przyjaciela(musimy robić inne zapytanie, aby go pobrać). Pole może również posiadać argumenty, np filtry.

Aby stworzyć pole należy dopisać go do „Query” w „Schema” i zaprogramować funkcję resolve.

Składanie całości

Dla potrzeb artykułu nie będę zagłębiał się w szczegóły implementacyjne, dajcie znać w komentarzach czy jest taka potrzeba, jeśli tak to zrobię projekt na github, który pokaże dokładną implementację tego co napisałem powyżej. Załóżmy, że mamy już stworzoną „scheme” z polami typu articles oraz users. Stwórzmy zapytanie graphQL.

Zapytania GraphQL to zwykłe zapytania HTTP typu GET lub POST. Do pobierania danych wykorzystywany jest GET, do mutacji(zmiany danych) wykorzystywany jest POST.

Chcemy pobrać listę wszystkich artykułów, ale tylko nazwę i id artykułu, zapytanie wygląda następująco

query {
  articles {
     id,
     name
  }
}

jeśli chcemy pobrać coś więcej możemy dodać dodatkowe pola. Główną zaletą jest to, że w jednym zapytaniu możemy sprecyzować dokładnie to czego potrzebujemy. Czyli załóżmy, że jesteśmy na stronie artykułu, potrzebujemy wszystkich informacji o tym artykule + zajawki i id innych artykułów oraz dane zalogowanego użytkownika. Zapytanie wygląda następująco

query {
  article(articleId: 12) {
     id,
     name,
     description,
     summary,
     author: {
         id,
         name
     }
  }
  loggedUser {
      id,
      name
  }
}

To zapytanie pokazuje sedno GraphQL, jedno query pobiera nam wszystko i dokładnie tyle informacji ile potrzebujemy na stronie, nie więcej nie mniej. May informacje o artykule, o jego autorze, który jest typu User, oraz o zalogowanym użytkowniku.

GraphQL jest bardzo ciekawą technologią, która rozwiązuje wiele problemów REST-a. Jako followup tego artykułu polecam następujące linki:

W następnym artykule postaram się napisać więcej o mutacjach oraz na podstawie biblioteki youshido graphql albo apiplatform pokazać Ci działający przykład 🙂 W międzyczasie zachęcam Was do zabawy z tą technologią. W PHP graphQL ma pewne ograniczenia, ale pomimo tego i tak warto go użyć zamiast REST-a.