Case StudiesBlogO nas
Porozmawiajmy

Stack technologiczny 2020: GraphQL, Apollo Server i React.js

Wojciech Cichoradzki

12 maj 20207 min czytania

Back-end developmentProduct developmentGraphQL

Spis treści

  • GraphQL vs REST

  • GraphQL

  • Apollo GraphQL 

  • React.js

  • Nasza ocena 

Od 2000 roku zasady RESTful są branżowym standardem budowania webowych API. Choć REST rozwiązał wiele problemów, z którymi wcześniejsze protokoły sobie nie radziły, ma też wady. W nowoczesnych aplikacjach dane są powiązane złożonymi relacjami, co potrafi generować problemy wydajnościowe podczas tworzenia produktu. Właśnie to chce rozwiązać GraphQL.

GraphQL vs REST

REST, oparty na HTTP, to prosty sposób komunikacji między aplikacją kliencką a serwerem. Dane są dostępne i modyfikowane za pomocą metod HTTP na konkretnych endpointach. Aplikacja jest dzięki temu odseparowana od serwera. Nie są już potrzebne niestandardowe biblioteki, a integracja z różnymi platformami jest łatwiejsza. 

Na portalu informacyjnym każdy artykuł ma tytuł, treść, datę, autora oraz elementy wizualne, takie jak obrazy czy wideo. Może też mieć komentarze użytkowników pogrupowane w wątki i linki do powiązanych materiałów. Rosnąca złożoność grafu powiązań danych obnaża ograniczenia tradycyjnego podejścia REST. Każdy zasób trzeba pobierać osobno, często sekwencyjnie, ponieważ jeden zasób zależy od drugiego. GraphQL poprawia tu wydajność.

GraphQL

W 2015 roku inżynierowie Facebooka przedstawili GraphQL jako zupełnie nowe rozwiązanie do projektowania API. Kluczowy koncept GraphQL polega na tym, by dać deweloperom bardziej precyzyjną kontrolę nad potrzebnymi zasobami poprzez pojedynczy „inteligentny” endpoint zamiast wielu różnych endpointów dla każdego zasobu. 

Budowanie GraphQL API jest zorganizowane wokół typów i pól, a nie endpointów. Metody pobierania danych to queries, a wszelkie modyfikacje danych to mutations w terminologii GraphQL. Ponieważ w branży już wcześniej istniało zapotrzebowanie na takie rozwiązanie, GraphQL szybko zdobył popularność i wsparcie społeczności. 

GraphQL stał się popularny i ma implementacje we wszystkich najważniejszych językach programowania. Został już wdrożony przez duże firmy, takie jak Twitter, Yelp, The New York Times, Airbnb i wiele innych.

Deweloperzy cenią GraphQL za solidność i łatwość użycia. Według ankiety „State of JavaScript” z 2019 roku niemal 40% programistów JavaScript korzystało już z GraphQL i użyłoby go ponownie. Trend rośnie i w najbliższych latach zobaczymy dalszą adopcję GraphQL.

Apollo GraphQL 

Apollo to wiodąca implementacja GraphQL oferująca zestaw narzędzi i bibliotek ułatwiających budowę aplikacji GraphQL. Typowy serwer Apollo składa się z definicji schematu GraphQL i odpowiadających im resolverów. Aby pokazać możliwości GraphQL, zbudujemy prostą aplikację składającą się z serwera Apollo w Node.js i klienta w React.js.

Aplikacja będzie przechowywać recenzje przeczytanych książek użytkownika. Zacznijmy od stworzenia podstawowej struktury projektu i budowy serwera.

$ mkdir app app/server app/client && cd app/server
$ yarn init

Następnie utworzymy bazę danych z dwiema tabelami: Authors i Books.

CREATE TABLE `authors` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(256) NOT NULL UNIQUE,
  PRIMARY KEY (`id`)
);
CREATE TABLE `books` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `author` INT NOT NULL,
  `title` VARCHAR(512) NOT NULL,
  `image` VARCHAR(512) NOT NULL,
  `review` VARCHAR(2048) NOT NULL,
  PRIMARY KEY (`id`),
  FOREIGN KEY (`author`)
    REFERENCES `authors`(`id`)
    ON UPDATE NO ACTION ON DELETE CASCADE
);

Do komunikacji z bazą danych użyjemy biblioteki knex (query builder). Zainstalujmy wymagane zależności i skonfigurujmy podstawowy serwer.

$ yarn add dotenv knex mysql2 apollo-server
$ touch index.js

W pliku index.js stworzymy definicje schematu i resolvery. Użyjemy dwóch typów: Author i Book. Nasze API wystawi też po dwa zapytania dla każdego typu oraz jedną mutację dodającą nową książkę.

require('dotenv').config();
const { ApolloServer, gql } = require('apollo-server');


const knex = require('knex')({
  client: 'mysql2',
  connection: {
    host: process.env.DB_HOST || 'localhost',
    user: process.env.DB_USER || 'root',
    password: process.env.DB_PASSWORD || '1234',
    database: process.env.DB_NAME || 'books_database',
  },
});

// The GraphQL schema
const typeDefs = gql`
  type Author {
    id: Int!
    name: String!
  }

  type Book {
    id: Int!
    author: Author!
    title: String!
    image: String!
    review: String!
  }

  type Query {
    books: [Book!]
    authors: [Author!]
  }

  type Mutation {
    addBook(author: String!, title: String!, image: String!, review: String!): Book!
  }
`;

// A map of functions which return data for the schema.
const resolvers = {
  Query: {
    books: async (parent, args, context, resolveInfo) => {
      const result = await knex('books')
        .leftJoin('authors', 'books.author', 'authors.id')
        .select('*')
        .options({ nestTables: true });
        return result.map(({ books, authors }) => ({
        ...books,
        author: authors,
      }));
    },
    authors: async () => knex('authors').select('*'),
  },
  Mutation: {
    addBook: async (parent, {
      author: authorName, title, image, review,
    }) => {
      let author = await knex('authors').first().where({ name: authorName });
      if (!author) {
        const [authorId] = await knex('authors').insert({ name: authorName });
        author = {
          id: authorId,
          name: authorName,
        };
      }
      const newBook = {
        author: author.id,
        title,
        image,
        review,
      };
      const [bookId] = await knex('books').insert(newBook);
      return {
        ...newBook,
        id: bookId,
      };
    },
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
});

server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
}); 

Teraz, aby uruchomić serwer i przetestować nasze API z logami knex, wystarczy uruchomić Node.js na pliku index.js:

DEBUG=knex:query node index.js 🚀 Server ready at http://localhost:4000/

Następnie, po wejściu na http://localhost:4000/, powita nas tzw. GraphQL Playground. Tu możemy łatwo przetestować nasze API. 

Spróbujmy pobrać wszystkie książki za pomocą następującego polecenia w GraphQL Playground: 

query { books { title author } } 

Na tym etapie powinniśmy zobaczyć pustą tablicę w odpowiedzi, co oznacza, że obecnie nie ma żadnych książek.

Ponieważ na razie nie mamy zapisanych książek, odpowiedź to po prostu pusta tablica. 

Możemy dodać książki, wykonując mutację addBook. Przykładowo, użyj w GraphQL Playground takiego polecenia: 

mutation { addBook(title: "New Book", author: "John Doe") { title author } } 

Zwróci ono w odpowiedzi tytuł i autora dodanej książki.

Gdy ponownie wykonamy zapytanie o książki tym samym poleceniem co wcześniej: 

query { books { title author } }

 powinniśmy zobaczyć nowo utworzoną pozycję na liście wyników.

 Nasz resolver „books” wykonuje left join na tabeli „authors”, aby pobrać id i nazwę autora i dołączyć je do odpowiedzi. Gdy jednak pole author nie jest żądane, kosztowną operację left join można pominąć. Aby tak zoptymalizować zapytanie, możemy użyć pakietu graphql-parse-resolve-info i przerobić resolver tak, by sprawdzał, czy pole author jest obecne w żądaniu.

const {
  parseResolveInfo,
  simplifyParsedResolveInfoFragmentWithType,
} = require('graphql-parse-resolve-info');
…
    books: async (parent, args, context, resolveInfo) => {
      const query = knex('books')
        .select('*')
        .options({ nestTables: true });
      const simplifiedFragment = simplifyParsedResolveInfoFragmentWithType(
        parseResolveInfo(resolveInfo),
        resolveInfo.returnType,
      );
      if (simplifiedFragment.fields.author) {
        query.leftJoin('authors', 'books.author', 'authors.id');
      }
 
      const result = await query;
      return result.map(({ books, authors: author }) => ({
        ...books,
        author,
      }));
    },

Jeśli zmodyfikujemy zapytanie, usuwając pole author, w ten sposób: 

query { books { title } }

zauważymy zmianę w wykonywanych zapytaniach SQL. Dzieje się tak, ponieważ żądanie stało się bardziej precyzyjne i prosi wyłącznie o tytuły książek.

Nasze API jest już gotowe do użycia przez aplikację kliencką.

React.js

Inicjalizujemy projekt React za pomocą create-react-app i instalujemy wymagane zależności:

cd .. && npx create-react-app client && cd client yarn add apollo-boost @apollo/react-hooks graphql

Po uruchomieniu yarn start powinien otworzyć się podstawowy starter. Stwórzmy klienta GraphQL i udostępnijmy go podkomponentom przez context provider. Należy zmodyfikować index.js w następujący sposób:

import React from 'react';
import ReactDOM from 'react-dom';
import { ApolloProvider } from '@apollo/react-hooks';
import ApolloClient from 'apollo-boost';

import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

const client = new ApolloClient({
  uri: 'http://localhost:4000',
});

ReactDOM.render(
  <React.StrictMode>
    <ApolloProvider client={client}>
      <App />
    </ApolloProvider>
  </React.StrictMode>,
  document.getElementById('root')
);

W kolejnym kroku wykonamy query do naszego API i wyrenderujemy listę książek. Apollo udostępnia zestaw użytecznych hooków dla Reacta, które w tym pomagają. W naszym przypadku użyjemy hooka useQuery z wcześniejszym zapytaniem GraphQL o książki. Zmieńmy App.js tak, aby pobierał i renderował zapisane książki.

import React from 'react';
import { useQuery } from '@apollo/react-hooks';
import { gql } from 'apollo-boost';

const BOOK_QUERY = gql`
  {
    books{
      id
      author{
        name
      }
      title
      image
      review
    }
  }
`;

export default function App() {
  const { loading, error, data } = useQuery(BOOK_QUERY);

  if (loading) {
    return <p>Loading...</p>;
  }
  if (error) {
    return <p>Error :(</p>;
  } 

  return (
    <div className="App">
      <h2>My Books</h2>
      { data.books.map(({ id, author, title, image, review }) => (
      <div key={id}>
        <img src={image} alt={title} />
        <p>{ title }</p>
        <p>{ author.name }</p>
        <p>{ review }</p>
      </div>
     ))}
    </div>
  );
}

Po odświeżeniu aplikacja poprawnie renderuje listę książek. Wygląd jednak nie jest zbyt atrakcyjny. Dodajmy bibliotekę milligram.css, wydzielmy komponent Book do osobnego modułu i dołóżmy odrobinę CSS.

Index.html

<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/milligram/1.3.0/milligram.css">

Book.js

export default function Book({ author, title, image, review }: Props) {
  return (
    <div className="book container">
      <div className="row">
        <div className="column-10">
          <img
            src={image}
            alt="book cover"
            className="cover"
          />
        </div>
        <div className="column-90">
          <h4 className="title">{ title }</h4>
          <i>{ author }</i>
          <p>{ review }</p>
        </div>
      </div>
    </div>
  );
}

Index.css

.App {
  margin: 2rem 20%;
}

.book .cover {
  width: 100px;
  height: auto;
  margin-right: 2rem;
}

.book .title {
  margin-bottom: 0;
  font-weight: bold;
}

Po tych zmianach i usprawnieniach zapytań GraphQL pobieranie danych jest wydajniejsze, a nasza aplikacja działa responsywniej.

Nasza ocena 

Prostota i solidność GraphQL, zwłaszcza w połączeniu z Apollo i React, sprawiają, że to świetny wybór do uniwersalnego stacku technologicznego w 2020 roku. W nadchodzących miesiącach spodziewamy się szerszej adopcji i dalszych optymalizacji rozwijanych przez rosnącą społeczność. Chcesz wiedzieć więcej? Zajrzyj na naszego bloga po kolejne artykuły o narzędziach developerskich.

Masz pytania dotyczące rozwoju produktu? Wpadnij na naszą stronę kontaktową lub napisz do nas na

Opublikowany 12 maja 2020

Udostępnij


Wojciech Cichoradzki

JavaScript Developer

Digital Transformation Strategy for Siemens Finance

Cloud-based platform for Siemens Financial Services in Poland

See full Case Study
Ad image
Stack technologiczny 2020: GraphQL, Apollo Server i React.js
Nie przegap żadnego artykułu - zapisz się do naszego newslettera
Zgadzam się na otrzymywanie komunikacji marketingowej od Startup House. Kliknij, aby zobaczyć szczegóły

Może Ci się również spodobać...

igital transformation is reshaping healthcare with AI, data, and patient-centric innovation.
Project managementProduct development

Metodyka Lean Development: zasady, korzyści i wdrożenie

W dzisiejszym dynamicznie zmieniającym się środowisku rozwoju oprogramowania firmy nieustannie szukają sposobów na optymalizację procesów i efektywne dostarczanie produktów wysokiej jakości. Jednym z podejść, które zyskało dużą popularność, jest metodyka Lean Development. W tym artykule omówimy zasady, korzyści i wdrożenie Lean Development, przybliżając również metodykę Agile oraz to, jak może zrewolucjonizować praktyki tworzenia oprogramowania.

Marek Pałys

07 lut 20235 min czytania

Ruby on Rails - guide
Ruby on RailsBack-end developmentComputer programming

Jak zainstalować Ruby i Ruby on Rails oraz używać RubyGems

Poznaj przewodnik krok po kroku, który pokaże, jak zainstalować i wykorzystać Ruby on Rails do efektywnego tworzenia aplikacji internetowych. Dowiesz się, jak zainstalować i skonfigurować Ruby, zarządzać różnymi wersjami, korzystać z RubyGems i Bundlera oraz stworzyć nowy projekt w Ruby on Rails. Rozpocznij swoją przygodę z tworzeniem aplikacji w Ruby on Rails dzięki temu kompleksowemu przewodnikowi.

Jan Grela

20 mar 20206 min czytania

Business team analyzing smart locker monetization strategy
Digital productsProduct development

Co odzwierciedla test napisany w podejściu Test-Driven Development (TDD)?

Test-driven development (TDD), kluczowa praktyka w metodykach Agile tworzenia oprogramowania, to solidne i rygorystyczne podejście do pisania kodu. Jeśli kiedykolwiek pojawiło się pytanie: „Co właściwie przedstawia test napisany zgodnie z TDD?”, jesteś we właściwym miejscu. W tym artykule rozłożymy to podejście na czynniki pierwsze, wyjaśniając rolę testów jednostkowych, frameworków testowych, przypadków testowych i nie tylko.

Marek Majdak

24 sty 20237 min czytania

Gotowy, aby scentralizować swoje know-how z pomocą AI?

Rozpocznij nowy rozdział w zarządzaniu wiedzą — gdzie Asystent AI staje się centralnym filarem Twojego cyfrowego wsparcia.

Umów bezpłatną konsultację

Pracuj z zespołem, któremu ufają firmy z czołówki rynku.

Rainbow logo
Siemens logo
Toyota logo

Budujemy to, co będzie dalej.

Firma

Startup Development House sp. z o.o.

Aleje Jerozolimskie 81

Warszawa, 02-001

VAT-ID: PL5213739631

KRS: 0000624654

REGON: 364787848

Kontakt

hello@startup-house.com

Nasze biuro: +48 789 011 336

Nowy biznes: +48 798 874 852

Obserwuj nas

Award
logologologologo

Copyright © 2026 Startup Development House sp. z o.o.

UE ProjektyPolityka prywatności