Skip to content

Graphql ist eine API Anfrage Sprache, die für eine effiziente Kommunikation zwischen Client und Servern designt ist. Es ist dem Nutzer möglich genau zu spezifizieren, was in der Antwort enthalten sein soll. Bei Graphql wird ein Vertrag mit dem Server definiert, über den ein Client mit dem Server kommunizieren kann. Der Client muss dabei nicht wissen, wo sich die Daten befinden, statdessen werden die Abfragen an den Server geschickt, welcher wiederum alle Informationen von den entsprechenden Stellen abruft. GraphQL ist Platformunabhängig und lässt sich so zur Kommunikation mit praktisch jedem Datenspeicher nutzen.

Aufbau

Graphql hat dabei drei grundlegende Operationen: - Queries holen sich Daten - Mutations ändern, entfernen oder fügen Daten hinzu - Subscriptions ähnlich wie Queries, haben aber eine permanente Verbindung, mit welcher der Server Daten zum Client im spezifizierten Format schicken kann.

Die Daten werden als eine reihe von Typen definiert. Dabei können sie von einem Service implementiert werden. Die meisten dieser Typen sind Objekt Typen. Diese definieren die verfügbaren Objekte und die Felder und Argumente, welche sie haben. Dabei hat jedes Feld seinen eigenen Typ, welcher entweder ein anderes Objekt, ein skalar, enum, union, interface oder ein benutzerdefinierter Typ sein kann. Das Beispiel zeigt eine simple Schema Definition für einen Produkt Typen. Der ! Operator sagt, dass das Feld nicht null sein darf, wenn es aufgerufen wird.

#Example schema definition

type Product {
    id: ID!
    name: string!
    description: String!
    price: Int
}
Ein Schema muss dabei mindestens eine verfügbare query haben. Zudem haben sie für gewöhnlich Informationen über Verfügbare Mutationen.

Graphql Queries

Graphql Queries emfangen Daten von den Datenspeichern. Sie sind dabei ähnlich zu den GET Anfragen in REST API's. Die Abfragen haben dabei für gewöhnlich die folgenden Komponenten: - Einen query Operations Typ. Dieser ist optional, aber es empfiehlt sich diesen zu nutzen, um dem Server zu sagen, dass es sich um eine query handelt. - Einen query Namen. Dieser ist optional da dieser lediglich für's debuggen hilfreich sein kann. - Die Datenstruktur, welche die query zurückgeben soll. - Optionall ein oder mehrere Argumente, um Informationen zu einem speziellen Objekt zurückzugeben. Das Beispiel zeigt eine query, welche ein Produkt abfragt und zu diesem Produkt spezielle Informationen haben will:

query myGetProcuctQuery {
    getProduct(id: 123) {
        name
        description
    }
}
Hier wurde lediglich der Name und die Beschreibung abgefragt, allerdings ist es möglich, dass es noch mehr Informationen gibt, welche hier nicht angezeigt werden, dass ist ein Vorteil von Graphql.

Graphql Mutationen

Mutationen ändern die Daten in irgendeiner Weise. Dabei sind sie mehr oder weniger äquivalent zu den REST API Operationen POST, PUT und DELETE. Wie auch die querys haben mutations einen Operationstypen, namen und eine Datenstruktur für die zurückgegebenen Daten. Sie nehmen aber immer eine Eingabe irgendeines Typs. Dies kann ein inline Wert sein, aber in der Praxis wird meist ein Variable genutzt.

mutation {
    createProduct(name: "Flamin' Cocktail Glasses", listed: "yes"){
        id
        name
        listed
    }
}
Response:
{
    "data": {
        "createdProduct": {
            "id": 123,
            "name": "Flamin' Cocktail Glasses", 
            "listed": "yes"
        }
    }
}

Felder

Die folgende Abfrage fragt die Felder id, name.firstname und name.lastname ab.

query myGetEmployeeQuery {
    getEmployees {
        id
        name {
            firstname
            lastname
        }
    }
}
Response:
 {
        "data": {
            "getEmployees": [
                {
                    "id": 1,
                    "name" {
                        "firstname": "Carlos",
                        "lastname": "Montoya"
                    }
                },
                {
                    "id": 2,
                    "name" {
                        "firstname": "Peter",
                        "lastname": "Wiener"
                    }
                }
            ]
        }
    }

Argumente

Argumente sind Werte, die zur Verfügung gestellt werden für spezifische Felder. Die Argumente, welche genutzt werden können sind in dem Schema definiert.

Variablen

Mit ihnen ist es möglich der Anfrage dynamische Daten mitzugeben. Variablen basierte queries sind änlich zu den inline queries. Allerding werden einige Aspekte aus einem JSON basierten Wörterbuch genommen. Sie ermöglichen es mehrere Anfragen zu schicken, wobei nur die Variablen geändert werden. Für eine query mit Variablen wird das folgende benötigt: - Deklaration der Variablen und des Typs - Hinzufügen der Variable an der richtigen Stelle in der query - Übergeben des Variablen Schlüssels, und des Wertes der Variablen

query getEmployeeWithVariable($id: ID!) { 
        getEmployees(id:$id) { 
            name { 
                firstname 
                lastname 
            } 
        } 
    } 
    Variables: 
    { 
    "id": 1 
    }

Aliase

Aliase

In Graphql ist es nicht möglich mehrere Objekte des selben Typs abzufragen. Hierfür müssen Aliase genutzt werden:

query getProductDetails { 
    product1: getProduct(id: "1") { 
        id 
        name 
    } 
    product2: getProduct(id: "2") { 
    id 
    name 
    } 
}

Fragmente

Fragmente sind wiederverwendbare Teile von queries oder mutationen.

fragment productInfo on Product {
    id
    name
    listed
}
In dem obigen Codeausschnitt wird ein Fragment namens productInfo erstellt. Dieses basiert dabei auf dem Product Typ.
query {
    getProduct(id: 1) {
        ...productInfo
        stock
    }
}
Der zweite Codeausschnitt zeigt eine query, die ein Produkt aufrufen, dabei verwenden sie die Argumente des Fragments und fügt noch das stock Argument hinzu.

Subscriptions

Subscriptions sind ein spezieller query Typ. Sie erstellen eine lang lebige Verbindung, um Push benachrichtigen zu schicken. Dies eignet sich besonders für kleine Updates. Diese werden dabei meistens mit WebSockets realisiert.

Bypassing rate-limiting mit Aliasen

Dank Aliases ist es möglich mehrere Abfragen des gleichen Objektes in einer Anfrage zu schicken. Dadurch ist es möglich das rate-limiting zu umgehen.

Finden eines Graphql Endpunktes

Mit der query query{__typename} kann geprüft werden, ob ein graphql Endpunkt erreichbar ist und funktioniert. Dies ist möglich, da jede graphql das reservierte Wort __typename hat, welches den Typ der Anfrage zurückgibt also in diesem Fall query. Graphql nutzt dabei zumeist einen der folgenden Suffixe: - graphql - /api - /api/graphql - /graphql/api - /graphql/graphql Falls dies nicht erfolgreich ist kann noch etwas wie /v1 angehängt werden. Zum senden der Anfragen sollte dabei der Content-Type application/json benutzt werden, da dieser vor CSRF schützt, während dies beim Content-Type x-form-urlencoded nicht zutrifft.

Introspektion

Mit der Introspektion kann das Schema von Graphql abgefragt werden. Es kann mit der folgenden Anfrage überprüft werden, ob Introspektion erlaubt ist:

{ "query": ".   
    {__schema{queryType{name}}}" 
}
Der folgende request liefert eine volle Introspektion query:
query IntrospectionQuery { 
    __schema { 
        queryType { 
            name 
        } 
        mutationType { 
            name 
        } 
        subscriptionType { 
            name 
        } 
        types { 
            ...FullType 
        } 
        directives { 
            name 
            description 
            args { 
                ...InputValue 
            } 
            onOperation #Often needs to be deleted to run query 
            onFragment #Often needs to be deleted to run query 
            onField #Often needs to be deleted to run query 
        } 
    } 
} 

fragment FullType on __Type { 
    kind 
    name 
    description 
    fields(includeDeprecated: true) { 
        name 
        description 
        args { 
            ...InputValue 
        } 
        type { 
            ...TypeRef 
        } 
        isDeprecated 
        deprecationReason 
    } 
    inputFields { 
        ...InputValue 
    } 
    interfaces { 
        ...TypeRef 
    } 
    enumValues(includeDeprecated: true) { 
        name 
        description 
        isDeprecated 
        deprecationReason 
    } 
    possibleTypes { 
        ...TypeRef 
    } 
} 

fragment InputValue on __InputValue { 
    name 
    description 
    type { 
        ...TypeRef 
    } 
    defaultValue
} 
Bypassing rate-limiting mit Aliasen
Dank Aliases ist es möglich mehrere Abfragen des gleichen Objektes in einer Anfrage zu schicken. Dadurch ist es möglich das rate-limiting zu umgehen.
fragment TypeRef on __Type { 
    kind 
    name 
    ofType { 
        kind 
        name 
        ofType { 
            kind 
            name 
            ofType { 
                kind 
                name 
            } 
        } 
    }
}
Funktioniert die query nicht kann versucht werden die Direktiven onOperation, onFragment und onFieldzu entfernen. Da manche diese nicht akzeptieren in einer Introspektion.

Umgehen der Introspektion Verteidigung

Um die Introspektion zu unterdrücken nutzen manche Entwickler Regex. Ist dieses Regex aber nicht richtig erstellt, kann es sein, dass durch kleine Anpassungen diese umgangen werden können. Dazu können zu dem Schlüsselwort __schema Zeichen wie Kommas, Leerzeichen oder newlines angehängt werden. Auch kann es mit einem GET Request klappen oder einem POST mit dem Content-TYPE x-www-form-urlencoded.

Suggestions

Falls die Introspektion nicht klappt oder deaktiviert ist gibt es noch die Vorschläge. Beispielsweise benutzt Apollo graphql dies, wenn ein kleiner Fehler gemacht wurde liefert es Vorschläge:

There is no entry for 'productInfo'. Did you mean 'productInformation' instead?
Für diesen Zweck gibt es auch ein Tool, welches das Schema herausfinden soll, wenn Introspektion ausgeschaltet ist clairvoyance.