15%

Zaoszczędź 15% na wszystkich usługach hostingowych

Sprawdź swoje umiejętności i zdobądź Rabat na dowolny plan hostingowy

Użyj kodu:

Skills
Rozpocznij
01.11.2024
1 +1

Kompletny przewodnik GraphQL: Twórz szybsze, inteligentniejsze API na hostingu o wysokiej wydajności

GraphQL fundamentalnie zmienił sposób, w jaki deweloperzy projektują i konsumują API. Powstały wewnątrz zespołów inżynierskich Facebooka w 2012 roku i udostępniony społeczności open-source w 2015 roku, GraphQL stał się jednym z najszerzej adoptowanych języków zapytań API w nowoczesnym tworzeniu aplikacji internetowych. Niezależnie od tego, czy budujesz aplikację czatu w czasie rzeczywistym, pulpit nawigacyjny obciążony danymi, czy produkt mobilny z ścisłymi ograniczeniami przepustowości, GraphQL daje Ci chirurgiczną kontrolę nad dokładnie tym, jakie dane przesyłają się przez sieć.

W tym kompleksowym przewodniku dowiesz się, czym jest GraphQL, dlaczego przewyższa tradycyjne API REST w wielu scenariuszach, jak skonfigurować swój pierwszy serwer GraphQL i jak wdrożyć go na infrastrukturze, która może obsługiwać wymagania obciążeń produkcyjnych.

Spis treści

  1. Czym jest GraphQL?
  2. GraphQL vs. REST: Dlaczego to ważne
  3. Kluczowe cechy GraphQL
  4. Konfiguracja serwera GraphQL
  5. Definiowanie schematu
  6. Implementacja resolverów
  7. Zapytania i mutacje danych
  8. Aktualizacje w czasie rzeczywistym za pomocą subskrypcji
  9. Introspekcja GraphQL i narzędzia dla deweloperów
  10. Wdrażanie GraphQL w produkcji
  11. Najlepsze praktyki bezpieczeństwa
  12. Podsumowanie

1. Czym jest GraphQL? {#what-is-graphql}

GraphQL to otwarty język zapytań dla API i środowisko uruchomieniowe do wykonywania tych zapytań na Twoich danych. W przeciwieństwie do REST, który udostępnia stały zestaw punktów końcowych, z których każdy zwraca z góry określoną strukturę danych, GraphQL udostępnia pojedynczy punkt końcowy, przez który klienci mogą żądać dokładnie pól i relacji, których potrzebują — nic więcej, nic mniej.

Takie podejście eliminuje dwa z najbardziej utrwalonych problemów w projektowaniu API REST:

  • Over-fetching — otrzymywanie znacznie więcej danych niż klient faktycznie potrzebuje, marnowanie przepustowości i czasu przetwarzania.
  • Under-fetching — otrzymywanie zbyt mało danych w jednym żądaniu, zmuszające klienta do wykonania wielu kolejnych wywołań w celu zebrania pełnego obrazu.

W GraphQL klient określa kształt odpowiedzi. Serwer spełnia umowę zdefiniowaną przez schemat, a klient prosi tylko o to, co zamierza użyć.

2. GraphQL vs. REST: Dlaczego to ważne {#graphql-vs-rest}

Zrozumienie, kiedy wybrać GraphQL zamiast REST — i odwrotnie — jest krytyczne dla podejmowania rozsądnych decyzji architektonicznych.

WymiarRESTGraphQL
Punkty końcoweWiele (jeden na zasób)Pojedynczy ujednolicony punkt końcowy
Pobieranie danychStała struktura odpowiedziPola określone przez klienta
Over-fetchingPowszechneWyeliminowane z projektu
Under-fetchingPowszechne (problem N+1)Rozwiązane za pomocą zagnieżdżonych zapytań
WersjonowanieWymagane wersjonowanie URL lub nagłówkaEwolucja schematu bez wersjonowania
Obsługa czasu rzeczywistegoWymaga WebSockets lub pollinguNatywne subskrypcje
System typówOpcjonalny (OpenAPI/Swagger)Wbudowany, obowiązkowy schemat
BuforowanieBuforowanie na poziomie HTTP jest prosteWymaga buforowania świadomego zapytań

GraphQL jest szczególnie potężny dla:

  • Złożonych, wzajemnie powiązanych modeli danych, gdzie pojedynczy widok wymaga danych z wielu zasobów.
  • Aplikacji mobilnych, gdzie minimalizacja rozmiaru ładunku bezpośrednio poprawia doświadczenie użytkownika i zmniejsza koszty danych.
  • Szybkiej iteracji produktu, gdzie zespoły frontendowe muszą rozwijać swoje wymagania dotyczące danych bez czekania na zmiany API backendu.
  • Agregacji mikrousług, gdzie brama GraphQL łączy wiele usług downstream w ujednoliconą powierzchnię API.

REST pozostaje solidnym wyborem dla prostych API CRUD, publicznych API konsumowanych przez strony trzecie, które korzystają z przewidywalnej semantyki HTTP, oraz scenariuszy, w których buforowanie na poziomie HTTP jest twardym wymogiem.

3. Kluczowe cechy GraphQL {#key-features}

3.1 Precyzyjne pobieranie danych

Definiującą cechą GraphQL jest to, że klienci deklarują swoje wymagania dotyczące danych w samym zapytaniu. Pojedyncze żądanie do pojedynczego punktu końcowego może pobrać głęboko zagnieżdżony graf powiązanych obiektów, z tylko polami określonymi przez klienta wypełnionymi w odpowiedzi.

To jest transformacyjne dla zespołów budujących produkty na wielu platformach — web, iOS, Android, smart TV — gdzie każda powierzchnia ma inne potrzeby dotyczące danych. Zamiast utrzymywania oddzielnych punktów końcowych REST lub akceptowania puchniętych ładunków, każdy klient wysyła dostosowane zapytanie i otrzymuje dostosowaną odpowiedź.

3.2 Silnie typowany schemat

Każde API GraphQL jest wspierane przez schemat napisany w GraphQL Schema Definition Language (SDL). Schemat jest autorytatywną umową między serwerem a każdym klientem, który go konsumuje. Definiuje:

  • Typy — kształt każdego obiektu w Twoim modelu danych.
  • Zapytania — operacje odczytu, które mogą wykonywać klienci.
  • Mutacje — operacje zapisu (tworzenie, aktualizacja, usuwanie).
  • Subskrypcje — strumienie zdarzeń w czasie rzeczywistym.
  • Relacje — jak typy odwołują się do siebie nawzajem.

Ponieważ schemat jest silnie typowany, całe kategorie błędów są wychwytywane w czasie rozwoju, a nie w produkcji. Narzędzia mogą sprawdzać zapytania względem schematu, zanim zostaną kiedykolwiek wykonane, a IDE mogą zapewniać dokładne autouzupełnianie i wbudowaną dokumentację.

3.3 Aktualizacje w czasie rzeczywistym za pomocą subskrypcji

Mechanizm subskrypcji GraphQL umożliwia trwałe, sterowane zdarzeniami połączenia między klientem a serwerem — zwykle implementowane za pośrednictwem WebSockets. Gdy zdarzenie subskrybowane występuje na serwerze (nowa wiadomość jest wysyłana, cena akcji się zmienia, status zamówienia się aktualizuje), serwer natychmiast wysyła odpowiednie dane do wszystkich subskrybowanych klientów.

To sprawia, że GraphQL jest naturalnym rozwiązaniem dla:

  • Aplikacji czatu na żywo i wiadomości
  • Narzędzi do wspólnej edycji
  • Pulpitów nawigacyjnych finansowych z danymi rynkowymi na żywo
  • Powiadomień w czasie rzeczywistym i kanałów aktywności
  • Synchronizacji stanu gry wieloosobowej

3.4 Introspekcja

API GraphQL są samodokumentujące się z projektu. System introspekcji pozwala klientom na zapytanie samego schematu — odkrywanie dostępnych typów, pól, zapytań, mutacji i ich opisów w czasie wykonywania. Ta możliwość napędza narzędzia dla deweloperów, takie jak GraphiQL i Apollo Studio, które zapewniają interaktywne eksploratory API, konstruktory zapytań i automatyczne generowanie dokumentacji bez żadnego dodatkowego wysiłku ze strony autora API.

3.5 Ewolucja schematu bez wersjonowania

Jednym z najbardziej praktycznie wartościowych aspektów GraphQL jest to, jak elegancko obsługuje zmiany. Ponieważ klienci żądają tylko pól, których potrzebują, możesz dodawać nowe pola i typy do schematu bez łamania istniejących klientów. Wycofywanie starych pól jest obsługiwane za pośrednictwem adnotacji schematu, a nie wersjonowania URL, utrzymując czystą powierzchnię API i stabilnych klientów.

4. Konfiguracja serwera GraphQL {#setting-up}

GraphQL jest niezależny od języka. Dojrzałe biblioteki serwerowe istnieją w całym stosie technologicznym. Oto najszerzej używane opcje:

Język / Środowisko uruchomienioweBiblioteka / Framework
Node.jsApollo Server, GraphQL Yoga, Express-GraphQL
PythonStrawberry, Graphene
JavaSpring for GraphQL, graphql-java
Gogqlgen, graphql-go
Rubygraphql-ruby
PHPLighthouse (Laravel), webonyx/graphql-php
Rustasync-graphql
.NET / C#Hot Chocolate, GraphQL.NET

Krok po kroku: Node.js z Apollo Server

Apollo Server to najszerzej wdrażany serwer GraphQL w ekosystemie Node.js. Poniższy przewodnik przenosi Cię od zera do uruchomionego serwera.

Wymagania wstępne:

  • Node.js 18 lub nowszy zainstalowany
  • Menedżer pakietów npm lub yarn

Krok 1: Zainicjuj swój projekt

mkdir graphql-api && cd graphql-api
npm init -y
npm install @apollo/server graphql

Krok 2: Utwórz plik serwera

Utwórz plik o nazwie index.js (lub index.mjs dla modułów ES):

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

// Step 1: Define your type definitions (schema)
const typeDefs = `#graphql
  type Book {
    id: ID!
    title: String!
    author: String!
    publishedYear: Int
    genre: String
  }

  type Query {
    books: [Book!]!
    book(id: ID!): Book
  }
`;

// Step 2: Define your data source (in-memory for this example)
const books = [
  {
    id: '1',
    title: 'The Pragmatic Programmer',
    author: 'David Thomas & Andrew Hunt',
    publishedYear: 1999,
    genre: 'Technology',
  },
  {
    id: '2',
    title: 'Clean Code',
    author: 'Robert C. Martin',
    publishedYear: 2008,
    genre: 'Technology',
  },
];

// Step 3: Define your resolvers
const resolvers = {
  Query: {
    books: () => books,
    book: (_, { id }) => books.find((b) => b.id === id),
  },
};

// Step 4: Create and start the server
const server = new ApolloServer({ typeDefs, resolvers });

const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 },
});

console.log(`🚀 GraphQL server ready at: ${url}`);

Krok 3: Uruchom serwer

node index.js
# Output: 🚀 GraphQL server ready at: http://localhost:4000/

Przejdź do http://localhost:4000/ w przeglądarce, aby otworzyć Apollo Sandbox — interaktywny eksplorator zapytań, w którym możesz natychmiast przetestować swoje API.

5. Definiowanie schematu {#defining-schema}

Schemat jest kręgosłupem każdego API GraphQL. Inwestowanie czasu w dobrze zaprojektowany schemat przynosi dywidendy przez całe życie Twojej aplikacji.

Typy skalarne

GraphQL zawiera pięć wbudowanych typów skalarnych:

    Int — 32-bitowa liczba całkowita ze znakiem
    Float — zmiennoprzecinkowa podwójna precyzja
    String — sekwencja znaków UTF-8
    Boolean — true lub false
  • ID — unikalny identyfikator, serializowany jako ciąg
  • Możesz również zdefiniować skalary niestandardowe dla typów takich jak Date, DateTime, Email, URL lub JSON.

    Typy obiektów

    type Author {
      id: ID!
      name: String!
      biography: String
      books: [Book!]!
    }
    
    type Book {
      id: ID!
      title: String!
      author: Author!
      publishedYear: Int
      genre: String
      tags: [String!]
    }

    Modyfikator ! oznacza pole, które nie może być zerowe. [Book!]! oznacza listę, która nie może być zerowa, zawierającą obiekty Book, które nie mogą być zerowe.

    Zapytania

    type Query {
      books: [Book!]!
      book(id: ID!): Book
      authors: [Author!]!
      author(id: ID!): Author
      booksByGenre(genre: String!): [Book!]!
    }

    Mutacje

    type Mutation {
      createBook(title: String!, authorId: ID!, genre: String): Book!
      updateBook(id: ID!, title: String, genre: String): Book
      deleteBook(id: ID!): Boolean!
    }

    Typy wejściowe

    Dla mutacji z wieloma argumentami typy wejściowe utrzymują schemat czystym i wielokrotnie użytkowym:

    input CreateBookInput {
      title: String!
      authorId: ID!
      publishedYear: Int
      genre: String
      tags: [String!]
    }
    
    type Mutation {
      createBook(input: CreateBookInput!): Book!
    }

    6. Implementacja resolverów {#implementing-resolvers}

    Resolvery to funkcje, które spełniają każde pole w Twoim schemacie. Każde pole w schemacie GraphQL może mieć resolver. Jeśli żaden resolver nie jest zdefiniowany dla pola, GraphQL wraca do resolvera domyślnego, który po prostu zwraca właściwość o tej samej nazwie z obiektu nadrzędnego.

    Podpis resolvera

    fieldName: (parent, args, context, info) => value
    • parent — rozwiązana wartość typu nadrzędnego (przydatna dla zagnieżdżonych resolverów).
    • args — argumenty przekazane do pola w zapytaniu.
    • context — obiekt współdzielony przesyłany przez cały łańcuch resolvera, zwykle zawierający uwierzytelnionego użytkownika, połączenie z bazą danych lub ładowarki danych.
    • info — metadane dotyczące wykonania zapytania, w tym nazwę pola i schemat.

    Przykład: Resolvery z bazą danych

    const resolvers = {
      Query: {
        books: async (_, __, { db }) => {
          return db.collection('books').find().toArray();
        },
        book: async (_, { id }, { db }) => {
          return db.collection('books').findOne({ _id: id });
        },
      },
    
      Mutation: {
        createBook: async (_, { input }, { db, user }) => {
          if (!user) throw new Error('Authentication required');
          const result = await db.collection('books').insertOne(input);
          return { id: result.insertedId, ...input };
        },
      },
    
      Book: {
        // Nested resolver: fetch the author for each book
        author: async (book, _, { db }) => {
          return db.collection('authors').findOne({ _id: book.authorId });
        },
      },
    };

    Unikanie problemu N+1 za pomocą DataLoader

    Zagnieżdżone resolvery mogą wyzwolić problem zapytania N+1 — pobieranie listy 100 książek, a następnie wykonywanie 100 oddzielnych wywołań bazy danych w celu rozwiązania autora każdej książki. Rozwiązaniem jest DataLoader, narzędzie do grupowania i buforowania:

    import DataLoader from 'dataloader';
    
    // In your context factory:
    const authorLoader = new DataLoader(async (authorIds) => {
      const authors = await db.collection('authors')
        .find({ _id: { $in: authorIds } })
        .toArray();
      return authorIds.map((id) => authors.find((a) => a._id === id));
    });
    
    // In your resolver:
    Book: {
      author: (book, _, { authorLoader }) => authorLoader.load(book.authorId),
    }

    DataLoader grupuje wszystkie wyszukiwania author w ramach jednego taktu pętli zdarzeń w jedno zapytanie do bazy danych, zmniejszając 100 zapytań do 1.

    7. Zapytania i mutacje danych {#querying-data}

    Podstawowe zapytanie

    query GetAllBooks {
      books {
        id
        title
        author {
          name
        }
        genre
      }
    }

    Zapytanie z argumentami

    query GetBook {
      book(id: "1") {
        title
        author {
          name
          biography
        }
        publishedYear
        tags
      }
    }

    Zapytanie ze zmiennymi

    Zmienne utrzymują Twoje zapytania dynamiczne i zapobiegają lukom w bezpieczeństwie:

    query GetBook($bookId: ID!) {
      book(id: $bookId) {
        title
        author {
          name
        }
      }
    }
    {
      "bookId": "1"
    }

    Przykład mutacji

    mutation AddBook($input: CreateBookInput!) {
      createBook(input: $input) {
        id
        title
        author {
          name
        }
      }
    }
    {
      "input": {
        "title": "Designing Data-Intensive Applications",
        "authorId": "42",
        "publishedYear": 2017,
        "genre": "Technology"
      }
    }

    Fragmenty

    Fragmenty pozwalają na ponowne użycie selekcji pól w wielu zapytaniach:

    fragment BookDetails on Book {
      id
      title
      genre
      publishedYear
    }
    
    query {
      books {
        ...BookDetails
        author {
          name
        }
      }
    }

    8. Aktualizacje w czasie rzeczywistym za pomocą subskrypcji {#subscriptions}

    Subskrypcje GraphQL utrzymują trwałe połączenie — zwykle za pośrednictwem WebSockets — i wysyłają dane do klientów, gdy na serwerze występują określone zdarzenia.

    Definicja schematu

    type Subscription {
      bookAdded: Book!
      bookUpdated(id: ID!): Book!
    }

    Implementacja serwera (Apollo Server z WebSockets)

    npm install graphql-ws ws @graphql-tools/schema
    import { createServer } from 'http';
    import { WebSocketServer } from 'ws';
    import { useServer } from 'graphql-ws/lib/use/ws';
    import { makeExecutableSchema } from '@graphql-tools/schema';
    import { PubSub } from 'graphql-subscriptions';
    
    const pubsub = new PubSub();
    
    const resolvers = {
      Mutation: {
        createBook: async (_, { input }, { db }) => {
          const book = await db.collection('books').insertOne(input);
          pubsub.publish('BOOK_ADDED', { bookAdded: book });
          return book;
        },
      },
      Subscription: {
        bookAdded: {
          subscribe: () => pubsub.asyncIterator(['BOOK_ADDED']),
        },
      },
    };
    
    const schema = makeExecutableSchema({ typeDefs, resolvers });
    const httpServer = createServer();
    const wsServer = new WebSocketServer({ server: httpServer, path: '/graphql' });
    useServer({ schema }, wsServer);

    Subskrypcja klienta

    subscription OnBookAdded {
      bookAdded {
        id
        title
        author {
          name
        }
      }
    }

    9. Introspekcja GraphQL i narzędzia dla deweloperów {#introspection}

    System introspekcji GraphQL jest jedną z jego najbardziej przyjaznych dla deweloperów funkcji. Poprzez zapytanie pól meta __schema i __type, klienci i narzędzia mogą odkrywać kompletną strukturę Twojego API w czasie wykonywania.

    Przykład zapytania introspekcji

    {
      __schema {
        types {
          name
          kind
          description
        }
      }
    }

    Niezbędne narzędzia dla deweloperów

    NarzędzieCel
    GraphiQLIDE w przeglądarce do pisania i testowania zapytań
    Apollo StudioPełne zarządzanie API, monitorowanie wydajności, rejestr schematu
    PostmanObsługa zapytań GraphQL z zarządzaniem kolekcjami
    InsomniaLekki klient API z obsługą GraphQL
    GraphQL Code GeneratorAutomatycznie generuje typy TypeScript z Twojego schematu
    Apollo Client DevToolsRozszerzenie przeglądarki do debugowania pamięci podręcznej Apollo Client

    > Uwaga bezpieczeństwa: Wyłącz introspekcję w środowiskach produkcyjnych, aby uniknąć ujawnienia schematu API potencjalnym atakującym. Apollo Server czyni to proste:

    >

    > “`javascript

    > new ApolloServer({ typeDefs, resolvers, introspection: false });

    > “`

    10. Wdrażanie GraphQL w produkcji {#deploying}

    Przeniesienie API GraphQL z rozwoju do produkcji wymaga ostrożnej uwagi na infrastrukturę, wydajność i niezawodność.

    Wybór odpowiedniej infrastruktury hostingowej

    Infrastruktura, na której uruchamiasz swoje API GraphQL, bezpośrednio wpływa na jego wydajność, niezawodność i skalowalność. W przypadku obciążeń produkcyjnych masz kilka silnych opcji:

    Hosting VPS to doskonały punkt wyjścia dla większości API GraphQL. Plan Hosting VPS daje Ci dedykowane zasoby, dostęp root i swobodę konfigurowania środowiska uruchomieniowego Node.js, odwrotnego proxy i menedżera procesów dokładnie tak, jak potrzebujesz. Plany VPS AlexHost są zbudowane dla obciążeń wrażliwych na wydajność i zawierają pamięć masową SSD i łączność o wysokiej przepustowości.

    Serwery dedykowane to właściwy wybór, gdy Twoje API GraphQL obsługuje duże ilości zapytań, złożone obciążenia subskrypcji lub służy jako brama agregująca wiele mikrousług. Z Serwerem dedykowanym uzyskujesz wyłączny dostęp do wszystkich zasobów CPU, RAM i I/O — brak hałaśliwych sąsiadów, brak rywalizacji o zasoby i surowa moc do obsługi tysięcy równoczesnych połączeń WebSocket dla subskrypcji.

    Hosting GPU warto rozważyć, jeśli Twoje API GraphQL służy jako warstwa interfejsu dla wnioskowania uczenia maszynowego, potoków przetwarzania danych w czasie rzeczywistym lub funkcji zasilanych sztuczną inteligencją. Hosting GPU od AlexHost udostępnia zasoby GPU NVIDIA, umożliwiając Twojemu API dostarczanie wyników wymagających dużych mocy obliczeniowych przy niskim opóźnieniu.

    Stos wdrażania produkcyjnego

    Solidne wdrażanie produkcyjne dla API GraphQL zwykle wygląda następująco:

    Client → CDN / Load Balancer → Nginx (Reverse Proxy) → Node.js (PM2) → Database
                                                          ↘ Redis (Caching / PubSub)

    Krok 1: Zainstaluj i skonfiguruj Nginx jako odwrotny proxy

    server {
        listen 80;
        server_name api.yourdomain.com;
    
        location /graphql {
            proxy_pass http://localhost:4000;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_cache_bypass $http_upgrade;
        }
    }

    Nagłówki Upgrade i Connection są krytyczne dla obsługi WebSocket, która napędza subskrypcje GraphQL.

    Krok 2: Zarządzaj procesem Node.js za pomocą PM2

    npm install -g pm2
    pm2 start index.js --name graphql-api --instances max
    pm2 save
    pm2 startup

    --instances max włącza tryb klastra, tworząc jeden proces roboczy na rdzeń CPU w celu maksymalizacji przepustowości.

    Krok 3: Zabezpiecz za pomocą SSL

    Każde produkcyjne API musi być obsługiwane przez HTTPS. Certyfikat SSL od AlexHost zapewnia, że wszystkie dane przesyłane między klientami a punktem końcowym GraphQL są szyfrowane. Jest to szczególnie ważne dla API, które obsługują tokeny uwierzytelniania, dane osobowe lub informacje finansowe.

    # Install Certbot and obtain a certificate
    sudo apt install certbot python3-certbot-nginx
    sudo certbot --nginx -d api.yourdomain.com

    Krok 4: Zarejestruj swoją domenę

    Twoje API potrzebuje pamiętnej, profesjonalnej domeny. Rejestracja domeny za pośrednictwem AlexHost daje Ci dostęp do wszystkich głównych

    15%

    Zaoszczędź 15% na wszystkich usługach hostingowych

    Sprawdź swoje umiejętności i zdobądź Rabat na dowolny plan hostingowy

    Użyj kodu:

    Skills
    Rozpocznij