Jak komunikować się z API? Wprowadzenie do GraphQL

Sylwia Zacharek
Sylwia Zacharek
August 4, 2026 | Software development
GraphQL

Wstęp

Ile razy zdarzyło Ci się zmagać z endpointem REST, który jednocześnie zwracał za mało i za dużo informacji?

Przykład:
Potrzebowałeś tytułu książki i nazwisk jej autorów, a otrzymywałeś liczbę stron i gatunek, po czym musiałeś wysłać drugie żądanie, aby pobrać nazwiska autorów.

A co, jeśli powiem Ci, że istnieje język, który rozwiązuje ten problem?

query GetBook { 
  book(id: "1") { 
    title 
    authors { 
      name 
    } 
  } 
} 

Za pomocą prostego zapytania otrzymujesz dokładnie to, czego potrzebujesz. Witaj w świecie GraphQL!

Przyjrzyjmy się, jak to działa.

Czym jest GraphQL?

Mówiąc najprościej, GraphQL to język zapytań służący do pobierania danych z API w formie określonej przez klienta. Został opracowany wewnętrznie przez Facebooka w 2012 roku, aby zmniejszyć liczbę wywołań API w aplikacjach mobilnych firmy. W 2015 roku został udostępniony jako rozwiązanie open source.

Kiedy warto go rozważyć?

Największą zaletą GraphQL jest to, że pozwala klientowi dokładnie określić, czego potrzebuje – czy ma to być jedynie lista tytułów dostępnych książek, czy pełny raport zawierający informacje o tym, kto, kiedy i gdzie napisał daną książkę oraz ile jej egzemplarzy jest obecnie dostępnych.

Pozwala również pobierać zagnieżdżone dane w ramach jednego żądania. Nie trzeba już osobno prosić o listę książek, a następnie o listę ich autorów. Wszystko może zmieścić się w jednym zapytaniu.

Kolejną zaletą jest to, że udostępnia tylko jeden endpoint, zwykle /graphql, zamiast wielu adresów REST – szczególnie gdy są one wersjonowane. Ułatwia to komunikację między frontendem a backendem, ponieważ frontend nie musi już wiedzieć, który adres wywołać.

Dzięki temu nasza aplikacja może również pełnić funkcję proxy dla wielu różnych backendów, takich jak interfejsy REST API, bazy danych czy usługi gRPC. Klient nie musi więc wiedzieć, co znajduje się pod jedynym endpointem, do którego wysyła zapytania.

Może to przyspieszyć proces tworzenia oprogramowania, ponieważ zespoły frontendowe mogą swobodnie wybierać pola już dostępne w schemacie, bez czekania, aż backend utworzy nowy endpoint. Elastyczność ta kończy się jednak tam, gdzie kończy się schemat: jeśli dany ekran wymaga pola, które jeszcze nie istnieje, ktoś nadal musi dodać je do schematu i zaimplementować po stronie backendu.

I wreszcie GraphQL ma wbudowaną obsługę komunikacji w czasie rzeczywistym, dzięki czemu nie trzeba tworzyć osobnego protokołu obok istniejącego API.

Kiedy tylko skomplikuje Ci życie?

Pojedynczy endpoint GraphQL może być również wadą, jeśli chodzi o cache’owanie. W REST cache’owanie na poziomie HTTP jest dość naturalne, ponieważ adres URL żądania GET może służyć przeglądarkom i sieciom CDN jako klucz cache’a. GraphQL traci tę zaletę, dlatego cache’owanie trzeba świadomie wdrażać na innych warstwach: po stronie klienta za pomocą znormalizowanych cache’y, po stronie serwera przy użyciu utrwalonych zapytań lub niestandardowego cache’owania odpowiedzi, wewnątrz resolverów albo na poziomie źródła danych.

Pobieranie danych z bazy wymaga nieco większej ostrożności, ponieważ nieprzemyślane resolvery mogą wywołać bardzo wiele zapytań do bazy – po jednym dla każdego zagnieżdżonego pola każdego elementu nadrzędnego.

Łatwiej jest również przeciążyć serwer, ponieważ kształt zapytania zależy wyłącznie od klienta. Aplikacja musi ograniczać głębokość i złożoność zapytań lub wdrożyć inne zabezpieczenia, które nie są konieczne w REST. Warto także rozważyć wyłączenie lub ograniczenie introspekcji schematu w środowisku produkcyjnym, ponieważ może ona ujawnić cały schemat każdemu, kto o niego zapyta. Wpływa to jednak wyłącznie na możliwość poznania schematu i nie zastępuje autoryzacji ani zabezpieczeń kontrolujących koszt zapytań.

GraphQL nie opiera się na kodach statusu HTTP, ponieważ wiele odpowiedzi zwraca 200 OK nawet wtedy, gdy operacja logicznie się nie powiodła – błędy mogą być częścią treści odpowiedzi.

Podstawowe pojęcia

Zestaw jasno określonych zasad zwykle ułatwia skuteczną komunikację, ale w przypadku aplikacji ma szczególne znaczenie. Trzeba dokładnie określić, czego mogą się spodziewać i czego się od nich oczekuje, ponieważ same nie są w stanie tego ustalić. Gdy dwie aplikacje chcą się ze sobą komunikować, zasady muszą być bardzo precyzyjne. W GraphQL taki zestaw zasad nazywamy schematem.

GraphQL: Schemat

Schemat określa, jakie dane można pobrać z aplikacji i w jaki sposób inna aplikacja może o nie zapytać. Może zostać zapisany w SDL (Schema Definition Language) albo utworzony zgodnie z podejściem code-first. Jak każdy szanujący się język, schemat potrzebuje słów i zdań. W SDL słowa definiuje się za pomocą typów. Kiedy mamy już ich definicje, możemy używać tych słów do tworzenia zdań nazywanych zapytaniami.

Gdy chcemy zdefiniować, czym jest książka, musimy zdecydować, jakie informacje będą ją opisywać w naszej aplikacji. Zwykle książka ma tytuł i co najmniej jednego autora posiadającego imię i nazwisko, prawda?

# --- SDL ---  
type Book { 
  id: ID! 
  title: String! 
  publicationYear: Int 
  authors: [Author!]! 
} 
 
type Author { 
  id: ID! 
  name: String! 
} 

Dzięki tej definicji schemat będzie wiedział, że Book ma:

  • pole id typu ID,
  • pole title typu String,
  • pole publicationYear typu Int,
  • listę authors zawierającą obiekty typu Author.

Na tym etapie autorzy nie potrzebują więcej niż:

  • pole id typu ID,
  • pole name typu String.

Przyjrzyjmy się przez chwilę składni, ponieważ warto zwrócić uwagę na kilka kwestii.

  • GraphQL udostępnia zestaw wbudowanych typów skalarnych reprezentujących wartości prymitywne. Oprócz dobrze znanych typów, takich jak Int, Float, Boolean czy String, na szczególną uwagę zasługuje ID. Jest serializowany tak samo jak String, ale wskazuje na intencję semantyczną, a nie na inny format danych.
  • Gdy typ ma modyfikator !, oznacza to, że jest wymagany i nie może mieć wartości null.
  • Gdy typ znajduje się w [nawiasach kwadratowych], oznacza to listę. Lista zapisana jako [list]! może być pusta, ale nie może mieć wartości null.

GraphQL: Zapytania

Niestety dwie definicje typów nie wystarczą. Nadal potrzebujemy jedynego typu wymaganego w schemacie GraphQL – typu Query. Jest on punktem wejścia do pobierania danych, a większość narzędzi po prostu odmówi zbudowania schematu i zwróci błąd walidacji, jeśli go zabraknie. Możemy zdefiniować Query w następujący sposób:

# --- SDL ---  
type Query { 
  book(id: ID!): Book 
  books: [Book!]! 
} 

Oznacza to, że nasza aplikacja będzie mogła:

  • zwracać informacje o pojedynczym obiekcie Book wskazanym przez id,
  • zwracać listę wszystkich books.

Query jest jednym z trzech głównych typów operacji dostępnych w SDL. Każdy z nich jest pojedynczy, co oznacza, że w schemacie może istnieć tylko jedna definicja danego typu.

Mamy już schemat na tyle kompletny, aby wysyłać do niego zapytania. Stwórzmy więc pierwsze zdanie w naszym nowo zdefiniowanym języku:

# --- request ---  
query GetBook { 
  book(id: "1") { 
    title 
    authors { 
      name 
    } 
  } 
} 

W języku naturalnym oznaczałoby to: „Podaj mi tytuł i listę nazwisk autorów książki o identyfikatorze id równym “1””. Mówiąc bardziej technicznie, odpytujemy o wybrany zestaw ({…}) pól: tytuł i nazwiska autorów książki, która jest identyfikowana poprzez argument id o wartości “1”.

To wystarczy, aby otrzymać odpowiedź z naszej aplikacji, która może wyglądać następująco:

# --- response ---  
{ 
  "data": { 
    "book": { 
      "title": "Good Omens", 
      "authors": [ 
        { 
          "name": "Terry Pratchett" 
        }, 
        { 
          "name": "Neil Gaiman" 
        } 
      ] 
    } 
  } 
} 

Odpowiedź jest zazwyczaj sformatowana jako JSON, z kilkoma bardzo konkretnymi odstępstwami.

GraphQL: Mutacje

Wiemy już, jak poprosić o książkę i jej autorów, więc pobawmy się nią i wykonajmy jakąś operację. Do tego właśnie służą mutacje w GraphQL – możemy tworzyć nowe obiekty, modyfikować je i usuwać, gdy nie są już potrzebne.

Bądźmy kreatywni i utwórzmy nową książkę, dobrze? Potrzebujemy tytułu i nazwiska autora.

# --- SDL ---  
input WriteBookInput { 
  title: String! 
  authorName: String! 
} 

GraphQL wyraźnie rozróżnia typy opuszczające serwer od tych, które do niego trafiają. Gdy chcemy przekazać serwerowi większy lub ustrukturyzowany argument, nie możemy użyć typu type – musimy zastosować typ input.

Za pomocą WriteBookInput przekażemy:

  • title typu String,
  • authorName, która również jest typu String.

Mamy już dane, ale nadal potrzebujemy działania.

# --- SDL ---  
type Mutation { 
  writeBook(writeInput: WriteBookInput!): Book! 
} 

W tym miejscu wprowadziliśmy kolejny z trzech wspomnianych wcześniej głównych typów operacji: Mutation. Ta definicja pozwala nam wykonać writeBook po przekazaniu argumentu writeInput, a następnie zwróci nowo utworzony obiekt Book. Podobnie jak w przypadku zapytań nadal możemy wybrać, które pola mają znaleźć się w odpowiedzi. Mutacja nie tylko wykonuje działanie – pozwala nam również dokładnie wskazać, co chcemy otrzymać w odpowiedzi.

Skoro mamy już wszystkie informacje potrzebne do dodania książki, pozostaje nam tylko to zrobić!

# --- request ---  
mutation WriteBook { 
  writeBook( 
    writeInput: { 
      title: "World of GraphQL", 
      authorName: "Jane Doe" 
    } 
  ) { 
    id 
    title 
    publicationYear 
  } 
} 

Ponieważ nie pytamy o dane, lecz nakazujemy API wykonanie określonego działania, prośbę tę można przetłumaczyć na język naturalny następująco: „Dodaj książkę o tytule “World of GraphQL” i autorze Jane Doe, a następnie zwróć jej pola id, title i publicationYear”.

Nasze API otrzyma to żądanie, zrobi swoją magię i zwróci oczekiwaną odpowiedź:

# --- response ---  
{ 
  "data": { 
    "writeBook": { 
      "id": "2", 
      "title": "World of GraphQL", 
      "publicationYear": 2026 
    } 
  } 
} 

Dla tej konkretnej książki otrzymaliśmy:

  • pole id, które w naszym przypadku jest po prostu automatycznie zwiększaną liczbą; ponieważ GraphQL nie określa jednak sposobu generowania ID, równie dobrze może to być UUID lub zupełnie inna wartość,
  • title przekazany przez nas w mutacji,
  • pole publicationYear, które jest po prostu bieżącym rokiem, ponieważ właśnie opublikowaliśmy książkę.

Możemy rozszerzyć Mutation o metody służące do edytowania danych obiektu Book lub całkowitego usunięcia Book. Sposób działania pozostaje taki sam. Pamiętajmy, że Mutation jest typem głównym, dlatego nie możemy zadeklarować kolejnego typu o tej nazwie – musimy rozszerzyć istniejący, używając słowa kluczowego extend.

# --- SDL ---  
input AddAuthorInput { 
  bookId: ID! 
  authorName: String! 
} 

extend type Mutation { 
  addAuthor(addAuthorInput: AddAuthorInput!): Book! 
  deleteBook(bookId: ID!): Boolean! 
} 

# --- request ---  
mutation AddAuthor { 
  addAuthor( 
    addAuthorInput: { 
      bookId: "2", 
      authorName: "Olivia Smith" 
    } 
  ) { 
    authors { 
      name 
    } 
  } 
  deleteBook(bookId: "1") 
} 
 
# --- response ---  
{ 
  "data": { 
    "addAuthor": { 
      "authors": [ 
        { 
          "name": "Jane Doe" 
        }, 
        { 
          "name": "Olivia Smith" 
        } 
      ] 
    }, 

    "deleteBook": true 
  } 
} 

GraphQL: Subskrypcje

A co, jeśli mamy ulubioną autorkę i nie możemy doczekać się jej najnowszych książek? Chcielibyśmy otrzymywać powiadomienie za każdym razem, gdy coś opublikuje. Albo co, jeśli jesteśmy molami książkowymi i chcemy czytać wszystko, co zostaje wydane, natychmiast po premierze? Do tego służy nasz ostatni typ główny – Subscription.

# --- SDL ---  
type Subscription { 
  bookPublished: Book! 
  bookPublishedByAuthor(authorName: String!): Book! 
} 

W ten sposób możemy poprosić serwer, aby powiadamiał nas o opublikowaniu dowolnej książki (bookPublished) albo książki konkretnego autora (bookPublishedByAuthor). Połączenie jest zwykle nawiązywane przez WebSockety, ale ponieważ GraphQL nie określa sposobu dostarczania tych powiadomień, wybór pozostaje po stronie implementacji serwera. Po utworzeniu subskrypcji będziemy otrzymywać powiadomienie za każdym razem, gdy zostanie napisana książka – tak długo, jak pozostaniemy zasubskrybowani.

Jeśli nasz znajomy naprawdę lubi naszą twórczość, może zasubskrybować powiadomienia o naszych książkach w następujący sposób:

# --- request ---  
subscription OnNewBookByAuthor { 
  bookPublishedByAuthor(authorName: "Jane Doe") { 
    title 
  } 
}

Takiego żądania nie da się właściwie przełożyć na jedno zdanie zwykłej rozmowy. Bardziej przypomina ono zaproszenie: „Od tej chwili informuj mnie za każdym razem, gdy zostanie opublikowana nowa książka autorstwa Jane Doe”.

Dzięki temu za każdym razem, gdy coś opublikujemy, nasz znajomy otrzyma powiadomienie:

# --- response ---  
{ 
  "data": { 
    "bookPublishedByAuthor": { 
      "title": "World of GraphQL" 
    } 
  } 
} 

GraphQL: Błędy

Nie wszystko zawsze idzie po naszej myśli. Czasami API zawodzi i musi poinformować nas o tym, co się wydarzyło. REST API opiera się w takich sytuacjach na kodach statusu HTTP, natomiast GraphQL stosuje inne podejście. Błędy takie jak nieprawidłowe lub nieautoryzowane żądania czy awarie serwera występują poza samym wykonywaniem GraphQL, dlatego w takich przypadkach otrzymujemy odpowiedź 4xx lub 5xx. Gdy jednak rozpocznie się wykonywanie zapytania, odpowiedź niemal zawsze ma status 200 OK, nawet jeśli wystąpił błąd. GraphQL sygnalizuje problem, umieszczając w odpowiedzi tablicę najwyższego poziomu o nazwie errors. Co ciekawe, errors może wystąpić obok obiektu data, jeśli nie powiodła się tylko część zapytania.

Podobny błąd możemy otrzymać, gdy zapytamy o książkę, której API nie może znaleźć:

# --- response ---  
{  
  "data": {  
    "book": null  
  }, 
  "errors": [ 
    { 
      "message": "Book could not be found", 
      "locations": [{ "line": 2, "column": 3 }], 
      "path": ["book"], 
      "extensions": { 
        "code": "NOT_FOUND" 
      } 
    } 
  ] 
} 

Struktura błędów jest określona w specyfikacji GraphQL:

  • message – jedyne wymagane pole, zwykle zawierające opis tego, co poszło nie tak,
  • locations – opcjonalna tablica przechowująca numer wiersza i kolumny w treści zapytania, w której wystąpił błąd,
  • path – opcjonalna tablica zawierająca informacje o tym, z którego pola w drzewie odpowiedzi pochodzi błąd,
  • extensions – opcjonalny obiekt przeznaczony na niestandardowe wartości, takie jak kody błędów, stack trace i inne przydatne metadane.

Podsumowanie

Poza omówionymi podstawami GraphQL oferuje znacznie więcej. Możemy wprowadzać zmienne do zapytań i wykorzystywać je ponownie, a także warunkowo pobierać pola. Możemy filtrować i stronicować dane, tworzyć skróty zapytań w celu poprawy wydajności lub bezpieczeństwa, a nawet łączyć wiele API w jedno – wszystko zależy od konfiguracji schematu i użytych narzędzi. Oczywiście dostępnych jest również wiele, wiele innych funkcji.

Jak w przypadku każdej koncepcji, to, czy GraphQL pasuje do danej aplikacji i architektury, zależy wyłącznie od okoliczności – liczby przewidywanych klientów, złożoności danych itd. Mam nadzieję, że znajomość podstaw pomoże Ci, gdy zdecydujesz się go wypróbować, oraz sprawi, że komunikacja pozostanie jasna i będzie przebiegać sprawnie 😉