"A query language for your API"
Verwendung für ein einzelnes APIs, das wiederum mit folgenden Datenquellen kommunizieren kann:
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
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
REST: Für komplexere Fälle sind mehrere HTTP-Anfragen nötig
GraphQL: Daten werden mit einer einzigen HTTP-Anfrage abgefragt
REST: Für jeden Endpunkt werden immer die gleichen Felder zurückgegeben
GraphQL: Der Client kann genau die benötigten Objekte und Felder anfragen
Anfrage (GraphQL):
query {
random(min: 1, max: 100)
}
Antwort (JSON):
{
"random": 23
}
Anfrage:
query {
user(login: "john") {
login
friends {
login
}
}
}
Antwort:
{
"user": {
"login": "john",
"friends": [
{ "login": "mike" },
{ "login": "stephanie" }
]
}
}
Abfragen:
Definieren:
Beispiel zeigt Implementierung und Verwendung eines API für Zufallszahlen - mit Parametern für die Anzahl und den Höchstwert
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)
);
query random {
rand(max: 10, quantity: 3)
}
Abfrage:
query random($max: Int!, $quantity: Int!) {
rand(max: $max, quantity: $quantity)
}
Abfrageparameter (JSON):
{
"max": 10,
"quantity": 3
}
Vordefiniertes API mit Posts und Benutzern:
Szenario:
Social media App, in der wir eine Liste von Freunden anzeigen können. Ein Klick auf einen Freund zeigt dessen letzte Posts.
GET /users/$myuserid/friends
GET /users/$otheruserid/posts
{
users(id: "myuserid") {
friends {
userid
name
}
}
}
{
users(id: "otheruserid") {
posts {
date
title
body
}
}
}
Anzeigen neuer Posts aller Freunde
Umsetzung in REST:
/postsoffriends/$userid
Umsetzung in GraphQL:
{
users(id: "$myuserid") {
friends {
posts {
date
title
body
}
}
}
}
query {
user(login: "my-username") {
login
name
}
}
SELECT login, name
FROM user
WHERE login='my-username';
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
SQL: Beziehungen zwischen Tabellen (Joins) werden in der Query definiert
GraphQL: kennt die Beziehungen bereits → einfachere Queries
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
)
OpenCRUD: spezifischerer Standard, der auf GraphQL basiert - er kann anstelle von SQL verwendet werden
von https://github.com/APIs-guru/graphql-apis:
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: browserbasierter Explorer für GraphQL APIs
query getTitles {
allFilms {
films {
title
}
}
}
query getPlanetsWithPopulations {
allPlanets {
planets {
name
population
}
}
}
query getStarshipsByFilm {
allFilms {
films {
title
starshipConnection {
starships {
name
}
}
}
}
}
Beispiel unter https://api.spacex.land/graphql/:
{
launchesUpcoming(limit: 4) {
launch_date_utc
mission_name
}
}
{
rocket(id: "falcon9") {
cost_per_launch
wikipedia
}
}
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 Parameter sind (üblicherweise) mit !
gekennzeichnet - ebenso wie zurückgegebene Attribute, die immer vorhanden sind.
Abfrage:
query getLaunchesByYear($year: String!) {
launchesPast(find: { launch_year: $year }) {
launch_date_utc
launch_site {
site_name
site_name_long
}
}
}
Variablen:
{
"year": "2019"
}
https://todo-mongo-graphql-server.herokuapp.com/
(nur Definition einzelner Queries möglich)
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"
}
mutation toggleTodo($id: String!) {
toggle(id: $id) {
id
completed
}
}
mutation addOneAndClearCompleted($title: String!) {
add(title: $title) {
id
}
clearCompleted {
id
}
}
Aufgabe: Schreibe eine Query, die alle bisherigen Einträge löscht und zwei neue erstellt
mutation reset {
toggleAll(checked: true) {
id
}
clearCompleted {
id
}
add(title: "get some rest") {
id
}
}
query {
user (login: "marko-knoebl") {
followers (first: 10) {
nodes {
login,
followers (first: 10) {
nodes {
login
}
}
}
}
}
}
query {
user(login: "marko-knoebl") {
id
email
repositories(
first: 100
orderBy: { field: STARGAZERS, direction: DESC }
) {
nodes {
name
stargazers {
totalCount
}
}
}
}
}
Bei GraphQL sind die zurückgegebenen Datentypen immer bekannt.
verfügbare Typen:
Queries werden mittesl POST-Requests gesendet
Payload ist ein JSON-Objekt mit einer query
string property (auch bei Mutationen) und optional einer variables
property.
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);
const queryTemplate = `
{
reddit {
subreddit(name: "javascript") {
newListings(limit: 2) {
title
}
}
}
}`;
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));
query getPokemonByName($name: String = "Pikachu") {
pokemon(name: $name) {
number
image
}
}
Aufgabe: Nummer von Pikachu und Raichu (Pokémon API)
Die bekannte Art klappt nicht:
query getTwo {
pokemon(name: "Pikachu") {
number
}
pokemon(name: "Raichu") {
number
}
}
Die Antwort hätte die folgende Struktur:
{
"data": {
"pokemon": {
"number": "025"
},
"pokemon": {
"number": "026"
}
}
}
Der Key pokemon
wäre doppelt.
Ausweg aus dem Problem:
query getTwo {
pokemon1: pokemon(name: "Pikachu") {
number
}
pokemon2: pokemon(name: "Raichu") {
number
}
}
Antwort:
{
"data": {
"pokemon1": {
"number": "025"
},
"pokemon2": {
"number": "026"
}
}
}
Fragmente bieten "Vorlagen" für Queries - weniger Wiederholung
query getTwo {
pokemon1: pokemon(name: "Pikachu") {
...essentialData
}
pokemon2: pokemon(name: "Raichu") {
...essentialData
id
}
}
fragment essentialData on Pokemon {
number
maxHP
image
}