Stack technologiczny 2020: GraphQL, Apollo Server i React.js
Wojciech Cichoradzki
12 maj 2020・7 min czytania
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 initNastę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.jsW 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 hello@start-up.house
Digital Transformation Strategy for Siemens Finance
Cloud-based platform for Siemens Financial Services in Poland


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

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 2023・5 min czytania

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 2020・6 min czytania

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 2023・7 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.




