GraphQL

GraphQL

"A query language for your API"

GraphQL

Verwendung für ein einzelnes APIs, das wiederum mit folgenden Datenquellen kommunizieren kann:

  • andere APIs
  • Datenbanken

GraphQL vs REST

Vorteile von REST

  • etabliert
  • einfacher

Vorteile von GraphQL

  • flexibler
  • effizienter

Vorteile von GraphQL

Flexibilität von GraphQL:

kann komplexe Abfragen beschreiben - serverseitiges API muss für neue Anwendungsfälle nicht angepasst werden

Effizienz von GraphQL:

der Client kann mittels eines Requests genau die benötigten Objekte und Felder anfordern

Video: GraphQL is the better REST

Unterschiede zwischen GraphQL und REST

REST: Endpunkt (z.B. /todos) und Methode (z.B. PUT) sind Teil des APIs; für jede Art von Anfrage muss ein eigener Endpunkt erstellt werden

GraphQL: nur ein Endpunkt (z.B. /api), nur POST-Requests

Unterschiede zwischen GraphQL und REST

REST: Für komplexere Fälle sind mehrere HTTP-Anfragen nötig

GraphQL: Daten werden mit einer einzigen HTTP-Anfrage abgefragt

Unterschiede zwischen GraphQL und REST

REST: Für jeden Endpunkt werden immer die gleichen Felder zurückgegeben

GraphQL: Der Client kann genau die benötigten Objekte und Felder anfragen

Anwendungsfälle

  • API services: z.B. Generieren einer Zufallszahl zwischen 1 und 100
  • Datenbankabfrage: z.B. Abfragen aller Login Namen von Freunden eines bestimmten Benutzers

Beispiel: Service für Zufallszahlen

Anfrage (GraphQL):

query {
  random(min: 1, max: 100)
}

Antwort (JSON):

{
  "random": 23
}

Beispiel: Freunde eines Benutzers

Anfrage:

query {
  user(login: "john") {
    login
    friends {
      login
    }
  }
}

Beispiel: Feunde eines Benutzers

Antwort:

{
  "user": {
    "login": "john",
    "friends": [
      { "login": "mike" },
      { "login": "stephanie" }
    ]
  }
}

Definieren und Abfragen eines GraphQL APIs

Abfragen:

  • Query (ausgedrückt in GraphQL)
  • evtl Abfrageparameter (in JSON)

Definieren:

  • GraphQL Schema
  • Resolver-Funktionen

Beispiel Zufallszahlen

Beispiel zeigt Implementierung und Verwendung eines API für Zufallszahlen - mit Parametern für die Anzahl und den Höchstwert

Beispiel Zufallszahlen: Definition

Schemadefinition:

type Query {
  rand(max: Int!, quantity: Int!): [Int]!
}

Resolver-Funktion (abhängig von der verwendeten Library):

(root, args, context) =>
  Array.from({ length: args.quantity }, () =>
    Math.floor(Math.random() * args.max)
  );

Beispiel Zufallszahlen: Feste Abfrage

query random {
  rand(max: 10, quantity: 3)
}

Beispiel Zufallszahlen: Parametrische Abfrage

Abfrage:

query random($max: Int!, $quantity: Int!) {
  rand(max: $max, quantity: $quantity)
}

Abfrageparameter (JSON):

{
  "max": 10,
  "quantity": 3
}

Ausprobieren

Vordefiniertes API mit Posts und Benutzern:

https://api.graph.cool/simple/v1/cjmj9v4mk1zs00182rnrzdrai

Resourcen

GraphQL vs REST: Beispiel

GraphQL vs REST

Szenario:

Social media App, in der wir eine Liste von Freunden anzeigen können. Ein Klick auf einen Freund zeigt dessen letzte Posts.

API in REST

GET /users/$myuserid/friends
GET /users/$otheruserid/posts

API in GraphQL

{
  users(id: "myuserid") {
    friends {
      userid
      name
    }
  }
}

API in GraphQL

{
  users(id: "otheruserid") {
    posts {
      date
      title
      body
    }
  }
}

Funktionalität hinzufügen: Feed neuer Posts

Anzeigen neuer Posts aller Freunde

Funktionalität hinzufügen: Feed neuer Posts

Umsetzung in REST:

  • Möglichkeit 1: mehrere Requests senden
  • Möglichkeit 2: neuer Endpunkt in der API, z.B. /postsoffriends/$userid

Funktionalität hinzufügen: Feed neuer Posts

Umsetzung in GraphQL:

{
  users(id: "$myuserid") {
    friends {
      posts {
        date
        title
        body
      }
    }
  }
}

GraphQL verglichen mit SQL

GraphQL verglichen mit SQL

query {
  user(login: "my-username") {
    login
    name
  }
}
SELECT login, name
  FROM user
  WHERE login='my-username';

GraphQL verglichen mit SQL

GraphQL: Parameter haben keine vordefinierte Bedeutung

In SQL: WHERE login='my-username' hat klare Bedeutung

GraphQL: Bedeutung von login: "my-username" ist der Implementierung am Server überlassen

GraphQL verglichen mit SQL

SQL: Beziehungen zwischen Tabellen (Joins) werden in der Query definiert

GraphQL: kennt die Beziehungen bereits → einfachere Queries

GraphQL verglichen mit SQL

query {
  user(login: "my-username") {
    posts {
      title
    }
  }
}
SELECT post.title
  FROM user
  LEFT JOIN post ON user.id = post.userId
  WHERE user.login = 'my-username';

(extra Code: LEFT JOIN post ON user.id = post.userId)

GraphQL verglichen mit SQL

OpenCRUD: spezifischerer Standard, der auf GraphQL basiert - er kann anstelle von SQL verwendet werden

Beispiel-APIs

Beispiele für GraphQL-APIs

von https://github.com/APIs-guru/graphql-apis:

  • GitHub (login benötigt)
  • Reddit (GraphQL Hub)
  • GraphQL Pokémon (zweiter Eintrag!)
  • Star Wars
  • SpaceX Land
  • Todo list
  • FakeQL: Selbstdefinierte Mock APIs

FakeQL

https://fakeql.com/

Template für einfache Todos bei FakeQL:

{
  "todos": [
    { "id": 1, "title": "Go shopping", "completed": true },
    { "id": 49, "title": "Do taxes", "completed": false },
    { "id": 50, "title": "Do dishes", "completed": false }
  ]
}

GraphiQL Explorer

GraphiQL Explorer

GraphiQL: browserbasierter Explorer für GraphQL APIs

  • Abfragestruktur / Datenstruktur ansehen (Docs oben rechts in der Ansicht)
  • Abfragen senden

Einfache Beispiele

Einfache Beispiele

Star Wars API:

  • Liste von Titeln aller Star Wars Filme
  • Liste von Planeten und Einwohnerzahl aus Star Wars
  • Liste von Schiffen, gruppiert nach Filmen, in denen sie vorkommen

Ãœbung: Liste von Titeln

query getTitles {
  allFilms {
    films {
      title
    }
  }
}

Ãœbung: Liste von Planeten und Einwohnerzahlen

query getPlanetsWithPopulations {
  allPlanets {
    planets {
      name
      population
    }
  }
}

Ãœbung: Liste von Schiffen gruppiert nach Filmen

query getStarshipsByFilm {
  allFilms {
    films {
      title
      starshipConnection {
        starships {
          name
        }
      }
    }
  }
}

Parametrische Abfragen

Abfrageparameter

Beispiel unter https://api.spacex.land/graphql/:

{
  launchesUpcoming(limit: 4) {
    launch_date_utc
    mission_name
  }
}
{
  rocket(id: "falcon9") {
    cost_per_launch
    wikipedia
  }
}

Abfrageparameter

Alle Falcon 9-Starts im Jahr 2019:

{
  launchesPast(
    find: { launch_year: "2019", rocket_name: "Falcon 9" }
  ) {
    launch_date_utc
    launch_site {
      site_name
      site_name_long
    }
  }
}

Anmerkung: Die server-seitige Implementierung entscheidet über die unterstützten Parameter (z.B. find, id, limit, ...)

Verpflichtende und optionale Parameter

Verpflichtende Parameter sind (üblicherweise) mit ! gekennzeichnet - ebenso wie zurückgegebene Attribute, die immer vorhanden sind.

Variablen

Abfrage:

query getLaunchesByYear($year: String!) {
  launchesPast(find: { launch_year: $year }) {
    launch_date_utc
    launch_site {
      site_name
      site_name_long
    }
  }
}

Variablen:

{
  "year": "2019"
}

Mutationen

Mutationen

https://todo-mongo-graphql-server.herokuapp.com/

(nur Definition einzelner Queries möglich)

Mutationen

Befehl, der die add-Aktion am Server triggert und die id des neuen Todos zurückliefert:

mutation addTodo($title: String!) {
  add(title: $title) {
    id
  }
}
{
  "title": "shopping"
}

Mutationen

mutation toggleTodo($id: String!) {
  toggle(id: $id) {
    id
    completed
  }
}

Mutationen

mutation addOneAndClearCompleted($title: String!) {
  add(title: $title) {
    id
  }
  clearCompleted {
    id
  }
}

Mutationen

Aufgabe: Schreibe eine Query, die alle bisherigen Einträge löscht und zwei neue erstellt

Mutationen

mutation reset {
  toggleAll(checked: true) {
    id
  }
  clearCompleted {
    id
  }
  add(title: "get some rest") {
    id
  }
}

Ãœbungen (GitHub)

Ãœbungen

  • Frage alle "follower von followern" für einen bestimmten GitHub-Account ab
  • Frage für einen bestimmten GitHub-Account alle Projekte mit Namen und Sternenanzahl ab

Übungen - Lösungen

query {
  user (login: "marko-knoebl") {
    followers (first: 10) {
      nodes {
        login,
        followers (first: 10) {
          nodes {
            login
          }
        }
      }
    }
  }
}

Übungen - Lösungen

query {
  user(login: "marko-knoebl") {
    id
    email
    repositories(
      first: 100
      orderBy: { field: STARGAZERS, direction: DESC }
    ) {
      nodes {
        name
        stargazers {
          totalCount
        }
      }
    }
  }
}

Datentypen

Datentypen

Bei GraphQL sind die zurückgegebenen Datentypen immer bekannt.

Datentypen

verfügbare Typen:

  • Boolean
  • Int: 32-bit int (signed)
  • Float: 64-bit Gleitkommazahl
  • String: UTF-8 Zeichenkette
  • ID: Eindeutige ID als String
  • Object: Objekt mit vordefinierten Einträgen
  • List: Liste, die bestimmte andere Typen beinhaltet

GraphQL Clients

GraphQL Clients

  • graphql.js: Referenzimplementierung
  • Apollo Client: einfache Integration mit React sowie mit Vue, Angular, ...
  • Relay: fortgeschrittene Integration mit React

GraphQL mit reinem JavaScript

Senden von Queries an den Server

Queries werden mittesl POST-Requests gesendet

Payload ist ein JSON-Objekt mit einer query string property (auch bei Mutationen) und optional einer variables property.

Senden von Queries an den Server

Testen aus der Browserkonsole (wir müssen uns auf der gleichen Seite befinden):

const requestBody = {
  query:
    'mutation addTodo($title: String!) { add(title: $title) { id } }',
  variables: '{"title": "test"}',
};

const requestBodyStr = JSON.stringify(requestBody);

fetch('https://todo-mongo-graphql-server.herokuapp.com', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: requestBodyStr,
}).then(console.log);

Beispiel: reddit API

const queryTemplate = `
{
  reddit {
    subreddit(name: "javascript") {
      newListings(limit: 2) {
        title
      }
    }
  }
}`;

Beispiel: reddit API

fetch('https://www.graphqlhub.com/graphql', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
  body: JSON.stringify({ query: queryTemplate }),
})
  .then(r => r.json())
  .then(data => console.log('data returned:', data));

Queries - Fortgeschritten

Standardwerte für Variablen

query getPokemonByName($name: String = "Pikachu") {
  pokemon(name: $name) {
    number
    image
  }
}

Aliases

Aufgabe: Nummer von Pikachu und Raichu (Pokémon API)

Aliases

Die bekannte Art klappt nicht:

query getTwo {
  pokemon(name: "Pikachu") {
    number
  }
  pokemon(name: "Raichu") {
    number
  }
}

Aliases

Die Antwort hätte die folgende Struktur:

{
  "data": {
    "pokemon": {
      "number": "025"
    },
    "pokemon": {
      "number": "026"
    }
  }
}

Der Key pokemon wäre doppelt.

Aliases

Ausweg aus dem Problem:

query getTwo {
  pokemon1: pokemon(name: "Pikachu") {
    number
  }
  pokemon2: pokemon(name: "Raichu") {
    number
  }
}

Aliases

Antwort:

{
  "data": {
    "pokemon1": {
      "number": "025"
    },
    "pokemon2": {
      "number": "026"
    }
  }
}

Fragmente

Fragmente bieten "Vorlagen" für Queries - weniger Wiederholung

Fragmente

query getTwo {
  pokemon1: pokemon(name: "Pikachu") {
    ...essentialData
  }
  pokemon2: pokemon(name: "Raichu") {
    ...essentialData
    id
  }
}

fragment essentialData on Pokemon {
  number
  maxHP
  image
}