Blog

GraphQL, .Net und React miteinander verbinden – Teil IV: React und GraphQL schnell zusammenbringen dank Apollo Client

Ulrike Kulzer
Ulrike Kulzer
10. August 2022

Hallo! 🙂 Ich bin Ulrike, Medieninformatik-Studentin im Master und momentan Praktikantin bei Centigrade. Während meines Praxissemesters durfte ich unter anderem gemeinsam mit Mikhail die VedaVersum-App fertigstellen. Wir wollten die Anwendung mit .NET, GraphQL und React als eine Art Wissensdatenbank bauen, in der sich Teammitglieder anmelden und Artikel verfassen, bearbeiten und löschen können (s. Blog-Beitrag). In diesem Teil der Beitragsserie möchte ich mein Vorgehen bei der Anbindung des Frontends mit euch teilen und kurz berichten, wie es mir als React- und GraphQL-Neuling dabei ergangen ist.

Rückblick: .NET, GraphQL und MongoDB

Im ersten Blog-Beitrag hat euch Mikhail gezeigt, wie man eine GraphQL-API mit .Net erstellt und zur Unterstützung Hot Chocolate von ChilliCream als GraphQL Server gewählt. Das von ChilliCream mitgelieferte Hilfsprogramm Banana Cake Pop zum Überprüfen von GraphQL-Anfragen wird uns auch später noch von Nutzen sein, wenn wir unsere Anfragen vom Frontend isoliert testen. Im Folgebeitrag haben wir uns ganz auf die Backend-Authentifizierung am Beispiel von GitLab konzentriert und im letzten Beitrag für die Gewährleistung der Datenpersistenz MongoDB zum Backend hinzugefügt.

In diesem Beitrag werden wir GraphQL-Anfragen in JavaScript schreiben und diese mittels der Apollo Client Library in unser React-Frontend integrieren.

Ein wichtiger Hinweis zum Code auf Github: Während wir an dieser Beitragsserie für unseren Blog schreiben, wurde und wird an VedaVersum weitergearbeitet. Dementsprechend gibt es – wie meistens beim Entwickeln von Software – immer wieder größere und kleinere Umbauten und Umbenennungen. Beispielsweise wurde inzwischen die VedaVersumCard zum VedaVersumArticle.

Frontend mit React, Tailwind CSS und Apollo Client

Für die Implementierung des Frontends verwenden wir folgende Technologien:

Logos React, tailwind, apollo

Wie schon im ersten Blog-Beitrag ausführlich beschrieben, haben wir uns für React entschieden, weil es leichtgewichtiger (User Interface Library) ist als Angular (User Interface Framework).

Tailwind CSS ist ein CSS-Framework, das den „Utility first“-Ansatz verfolgt. Den Anstoß, uns mit diesem Thema auseinander zu setzen, gab uns ein Blogartikel von frontstuff.io. Wir haben uns aus folgenden Gründen für TailwindCSS entschieden: Für unsere kleine Anwendung wollten wir nichts Schwergewichtiges wie z. B. Bootstrap. Zusätzlich werden die Anforderungen an unsere Anwendung und damit auch ihr Design und Layout kontinuierlich überarbeitet. Mit Tailwind CSS ist es möglich, diese Änderungen schnell direkt in HTML umzusetzen.
Da eine ausführliche Erörterung des Frameworks und „Utility first“ den Umfang des Blogbeitrags sprengen würde, gehe ich nur der Vollständigkeit halber kurz darauf ein.

Apollo Client ist eine open source JavaScript Library für die Datenverwaltung mit GraphQL. Wir haben für unser Projekt diese Library gewählt, weil Apollo Client häufig eingesetzt wird, gut dokumentiert ist und sehr viel Logik selbstständig „abarbeitet“. Beispielsweise müssen Fehler bei der Datenabfrage nicht manuell implementiert werden, sondern Apollo Client sendet sie direkt mit und wir müssen uns nur noch darum kümmern, was im Fehlerfall passieren soll.

VedaVersum – Eine React-Anwendung, die über Apollo Client mit dem GraphQL-Backend kommuniziert

Nachdem wir die Anforderungen an die VedaVersum-App für die Veröffentlichung des Blog-Beitrags noch einmal überarbeitet haben, enthält die fertige Anwendung im ersten „Minimum Viable Product“ folgende Features:

  • Auf der Startseite wird mir eine Liste mit allen existierenden Artikeln angezeigt. Diese Liste kann ich als Benutzer*in nach den von mir erstellten Artikeln filtern. Dafür benötigen wir GraphQL Queries.
  • Ich kann Artikel anlegen, von mir erstellte Artikel löschen und sowohl Artikel, die ich verfasst habe, als auch die Artikel von anderen Benutzer*innen bearbeiten. Dafür benötigen wir GraphQL Mutations.
  • Legen andere Benutzer*innen neue Artikel an oder bearbeiten bestehende Artikel, während ich eingeloggt bin, werde ich vom System benachrichtigt. Dafür benötigen wir eine GraphQL Subscription.

Apollo Client in React initialisieren

In den Apollo Client Docs ist sehr gut beschrieben, wie man Apollo Client in ein React-Projekt integriert. Da wir allerdings in den vorherigen Blogbeiträgen die Authentifizierung implementiert haben, benötigen wir etwas mehr Code für die Initialisierung als das „Get started“ von den Docs. Dementsprechend haben wir diesen Code in eine eigene Datei namens „ApolloSetup.ts“ ausgelagert. Den dort angelegten Apollo Client importieren wir in der „index.tsx“-Datei und legen ihn um unsere Anwendung herum. Das Gerüst sieht dann folgendermaßen aus:

ApolloSetup.ts

export const apolloClient = new ApolloClient({
  ...
});

 


index.tsx

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);

root.render(
  <ApolloProvider client={apolloClient}>
    ...
  </ApolloProvider>,
);


Die grundlegende Implementierung von GraphQL Queries, Mutations und Subscriptions ist sehr ähnlich zueinander. Daher werde ich im folgenden Abschnitt GraphQL Queries etwas ausführlicher beschreiben und die Erklärung in den weiteren Abschnitten kurz halten.

Wie man GraphQL-Queries schreibt und mithilfe von Apollo Client im React-Frontend verwendet

GraphQL Queries werden dazu verwendet, Daten aus der Datenbank auszulesen, weshalb wir sie oft in unserer Anwendung benutzen. Diese Queries können zum einen direkt aufgerufen werden, beispielsweise um alle in der Datenbank gespeicherten Artikel abzufragen. Zum anderen kann man ihnen Parameter übergeben, z. B. um alle gespeicherten Artikel nach den Artikeln des eingeloggten Users zu filtern.

GraphQL Queries in JavaScript schreiben

Um in JavaScript eine Query als GraphQL-Query zu definieren, muss man das Schlüsselwort gql mit Backticks ( ` ` ) verwenden. Zwischen die Backticks schreibt man die Query und speichert den gesamten Ausdruck in einer Konstante:

export const ALL_ARTICLES_QUERY = gql`
   ...
`;

Die GraphQL Query selbst sieht dann folgendermaßen aus: Eingeleitet durch das Schlüsselwort query und dem Namen der Query, packt man in geschweifte Klammern die Datenbank-Abfrage. Wichtig ist, dass die GraphQL Query genauso benannt wird wie die Variable, in der im Backend das Ergebnis der Anfrage gespeichert wird. Ansonsten kann das Ergebnis der Datenbank-Abfrage nicht zugeordnet werden und man bekommt eine Fehlermeldung. Wie genau man eine GraphQL Query schreibt, ist in den GraphQL Docs sehr gut beschrieben.

Unsere Query, die alle Informationen aller in der Datenbank gespeicherten Artikel ausliest, sieht als fertige Konstante dann so aus:

export const ALL_ARTICLES_QUERY = gql`
  query GetAllArticles {
    allArticles {
      id
      title
      content
      created
      userCreated
      relatedArticleIds
      userUpdated
      updatedAt
      accessCounter
    }
  }
`;

Um sicherzugehen, dass die GraphQL Query an sich ausführbar ist und das Backend das gewünschte Ergebnis zurückgibt, können wir die Anfrage auch ohne Frontend in Banana Cake Pop testen. Dazu kopieren wir einfach die reine GraphQL Query aus dem Code und fügen sie in Banana Cake Pop ein (s. auch erster Blog-Beitrag). Gibt es in der Query einen Syntaxfehler, weist uns das Programm glücklicherweise sofort darauf hin. Ist die Query korrekt, zeigt uns Banana Cake Pop das Ergebnis vom Backend:

Banana Cake Pop Ergebnis Backend

GraphQL Queries mit Apollo Client in React verwenden

Um jetzt die GraphQL Query in unserer React Anwendung auszuführen, müssen wir zunächst den React Hook useQuery von Apollo Client importieren. Dann legen wir eine Konstante an, in die das Ergebnis unserer GraphQL Query gespeichert wird. Wir wissen, dass Datenbankabfragen nicht immer erfolgreich aufgelöst werden können, z. B. wenn der Server, auf dem die Daten liegen, gerade nicht erreichbar ist. Genauso kann es sein, dass die Bearbeitung der Anfrage länger dauert. Um eine gute Usability zu ermöglichen, möchten wir beide Fälle abdecken, und wählen daher folgende Schreibweise:

const {
    error: errorAllArticles,
    data: allArticlesData,
    loading: loadingAllArticles,
  } = ...

Wird die Query erfolgreich aufgelöst, werden die Daten in die data-Variable (hier umbenannt in „allArticlesData“) gespeichert. Später können wir das Ergebnis im JavaScript Code weiterverarbeiten oder im HTML-Bereich abfragen und die entsprechende UI-Komponente anzeigen.

Aber zurück zur Query – um die Query aufzurufen, weisen wir unsere Konstante nun als „Wert“ folgenden Ausdruck zu:

const { ... } = useQuery(ALL_ARTICLES_QUERY);

useQuery übergeben wir in runden Klammern die Parameter, die die Query benötigt – in diesem Fall nur den Namen der GraphQL Query, die wir ausführen möchten. Die GraphQL Query muss dazu importiert werden.

Allgemein kann man useQuery außer dem Namen weitere Parameter übergeben. Diese werden in einem Objekt „gesammelt“ und mit einem Komma getrennt nach dem Namen der GraphQL Query mitgesendet. Ein Beispiel dafür ist fetchPolicy, dessen Wert angibt, ob und wie Daten gecached werden. Bei GraphQL Queries, die Übergabeparameter erwarten, werden die Variablen ebenfalls hier aufgelistet. Aber das schauen wir uns im Folgenden genauer an:

GraphQL Queries mit Parametern in JavaScript schreiben

GraphQL Queries können Parameter erwarten. Das können wir uns beispielsweise zu Nutze machen, wenn wir einen Artikel lesen wollen. Benutzer*innen gehen nicht immer den Umweg über die Startseite, sondern speichern sich einen Artikel z. B. als Lesezeichen im Browser ab. In diesem Fall möchten wir nicht alle Artikel von der Datenbank anfordern, sondern direkt nur die Informationen des ausgewählten Artikels. Dazu schreiben wir eine GraphQL Query, die die ID eines Artikels bekommt und dann alle Datenbankeinträge zu genau diesem Artikel zurückgibt – vorausgesetzt der Artikel existiert. Das Ganze sieht am Ende so aus:

export const ARTICLE_BY_ID_QUERY = gql`
  query GetArticle($articleId: String!) {
    article(articleId: $articleId) {
      # database table fields
    }
  }
`;

Die GraphQL Query erwartet eine Artikel-ID vom Typ String, die sie in der nächsten Zeile ans Backend weiterreicht. Wichtig dabei ist, dass die Namen der Parameter, die die GraphQL Query bekommt, mit einem Dollar-Zeichen beginnen und auch genauso weitergegeben werden müssen.

Zum Testen können wir auch diese GraphQL Query in Banana Cake Pop mit der Artikel-ID eines unserer gespeicherten Artikel ausführen. Dabei müssen wir die Artikel-ID nicht der GraphQL Query, sondern direkt der Backend-Funktion übergeben:

Query With Parameters

Um diese GraphQL Query erfolgreich im Frontend auszuführen, müssen wir bei den Parametern von useQuery ein Objekt mit dem Feld „variables“ hinzufügen. Dieses Feld bekommt ebenfalls ein Objekt, in dem wir bei mehreren Übergabeparametern alle auflisten würden. Damit GraphQL die übergebenen Werte verarbeiten kann, müssen die Felder des „variables“-Objekts genau gleich heißen wie die Parameter, die die Query erwartet. In diesem Fall haben wir als Parameter nur die Artikel-ID, die wir zuvor als „currentArticleId“ gespeichert haben. Von der useQuery-Struktur her bleibt der Rest gleich:

const { error, data, loading } = useQuery(ARTICLE_BY_ID_QUERY, {
    variables: { articleId: currentArticleId },
  });

Wie man GraphQL-Mutations schreibt und mithilfe von Apollo Client im React-Frontend verwendet

GraphQL Mutations verwendet man, um Daten in der Datenbank hinzuzufügen, zu ändern oder zu löschen. Da unsere Benutzer*innen genau das tun können, brauchen wir für jede Aktion eine Mutation. Die Mutations haben zwar unterschiedliche Parameter, sind aber ansonsten gleich aufgebaut. Wir schauen uns das Ganze anhand der „UpdateArticle“ Mutation an.

GraphQL Mutations in JavaScript schreiben

Die Grundstruktur eine GraphQL Mutation in JavaScript ist die Gleiche wie bei einer GraphQL Query:

export const UPDATE_ARTICLE_MUTATION = gql`
  mutation UpdateArticle($articleId: String!, $articleTitle: String!, $articleContent: String!) {
    articleAction(action: UPDATE, articleId: $articleId, title: $articleTitle, content: $articleContent) {
      # database table fields
    }
  }
`;

Zwei kleine Unterschiede gibt es: Da wir eine Mutation angelegt haben, verwenden wir als Schlüsselwort auch mutation statt query. Außerdem haben wir im Backend eine Funktion geschrieben haben, die alle Artikel-Mutations (anlegen – „CREATE“, bearbeiten – „UPDATE“, löschen – „DELETE“) abfertigt. Dementsprechend müssen wir beim Aufruf dieser Funktion die „action“ als zusätzlichen Parameter übergeben – bei „UpdateArticle“ natürlich „UPDATE“.

Mutations kann man wie GraphQL Queries mit Übergabeparametern in Banana Cake Pop testen:

Mutation Test Banana Cake Pop

GraphQL Mutations mit Apollo Client in React verwenden

GraphQL Mutations werden in React wieder über einen React Hook von Apollo Client eingebunden, und zwar mittels useMutation. Die Grundstruktur deckt sich mit der einer Query. Aber im Detail unterscheiden sie sich:

const [updateArticle] = useMutation(UPDATE_ARTICLE_MUTATION, {
    variables: { articleId: articleData?.id, articleTitle: title, articleContent: content },
    onError: error => {
      // error handling
    },
    onCompleted: data => {
      // actions when successful
    },
  });

Die verschiedenen Responses (Error, Data, Loading) werden nicht in der Konstante aufgeführt, sondern in der Mutation selbst. Sie werden nach dem Namen der Mutation und den Übergabeparametern als onError bzw. onCompleted mit den dazugehörigen Anweisungen aufgelistet.

Später im Code kann die Mutation wie eine normale Funktion aufgerufen werden:

if (editorSettings.type === 'create') {
   insertArticle();
} else if (editorSettings.type === 'edit') {
   updateArticle();
}

Wie man GraphQL-Subscriptions schreibt und mithilfe von Apollo Client im React-Frontend verwendet

GraphQL Subscriptions kommen zum Einsatz, wenn ein Client Nachrichten in Echtzeit vom Server erhalten soll. Wie Mikhail im ersten Blog-Beitrag schon erwähnt hat, sind Subscriptions eines DER Features von GraphQL. Was ich bisher zu GraphQL Queries und Mutations geschrieben habe, klingt im ersten Moment sehr nach Standard REST GET- und POST-Befehlen. GraphQL setzt diese mit normalen HTTP-Methoden um. Bei Subscriptions sieht das allerdings anders aus: Auch wenn man oberflächlich betrachtet nichts davon mitbekommt, arbeitet GraphQL bei Subscriptions mit Websockets. Deswegen müssen wir später für Apollo Client auch noch die entsprechende Library in unser Projekt integrieren. Der Anwendungsfall für eine Subscription bei VedaVersum sind die Benachrichtigungen, die serverseitig ausgelöst werden sollen: Legt jemand einen neuen Artikel an oder bearbeitet einen gespeicherten Artikel, bekommen alle eingeloggten Benutzer*innen eine Benachrichtigung und können sich die entsprechenden Artikel direkt anzeigen lassen.

GraphQL Subscriptions in JavaScript schreiben

Eine GraphQL Subscription in JavaScript ist genauso aufgebaut wie eine GraphQL Query ohne Parameter:

export const ARTICLE_CHANGED_SUBSCRIPTION = gql`
  subscription OnArticleChanged {
    articleChanged {
      action
      vedaVersumArticle {
        # database table fields
      }
    }
  }
`;

Als Schlüsselwort schreiben wir hier subscription statt query.

Eine Subscription in Banana Cake Pop zu testen ist etwas komplizierter als eine Query oder Mutation:

  1. Wir starten in einem Tab die Subscription. Ob die Subscription aktiv ist, sehen wir an dem Button, über den wir GraphQL Anweisungen ausführen. Wo vorher „Run“ mit einem Pfeil-Icon stand, sollte jetzt „Cancel“ und ein Ladekreis angezeigt werden.
  2. Wir wechseln in einen zweiten Tab, authentifizieren uns und bearbeiten einen Artikel.
  3. Wir gehen zurück zum ersten Tab. Dort sollte uns im rechten Feld der geänderte Artikel mit der „action“, die ausgeführt wurde, angezeigt werden.

GraphQL Subscriptions mit Apollo Client in React verwenden

GraphQL Subscriptions mit Apollo Client in React verwenden

Bevor wir unsere Subscription in React verwenden können, benötigen wir zunächst eine Subscription Library. Auch wenn es in den Apollo Docs nicht empfohlen wird, müssen wir die veraltete Library subscription-transport-ws nehmen. Grund dafür ist, dass Hot Chocolate, unser GraphQL Server, die Nachfolger-Library graphql-ws noch nicht unterstützt. Wie genau ihr die Library in eurem Projekt integriert, ist am Ende des Apollo Docs Eintrags ausführlich beschrieben.

Wenn ihr die Library erfolgreich eingerichtet habt, könnt ihr im Code den React Hook useSubscription genauso wie useQuery ohne Parameter verwenden:

const { data: subscriptionData } = useSubscription(ARTICLE_CHANGED_SUBSCRIPTION);

Jetzt bekommt der Client jedes Mal eine Benachrichtigung vom Server, wenn ein neuer Artikel angelegt oder ein bestehender Artikel bearbeitet oder gelöscht wurde. So weit, so gut. Nur nutzt uns das bisher noch nichts. Die Idee war, dass Benutzer*innen eine Benachrichtigung bekommen, wenn die Artikel-Tabelle in der Datenbank geändert wurde. Um das zu erreichen, müssen wir mithilfe des React Hooks useEffect die Daten, die die Subscription sendet (hier „subscriptionData“), überwachen. Sobald sie sich ändern, also der Client vom Server einen „neuen“ geänderten Artikel erhält, werden die Anweisungen innerhalb des useEffect Hooks ausgeführt. Im Code sieht das vereinfacht so aus:

useEffect(() => {
    if (subscriptionData) {
      // instructions what to do every time
      // a new change is sent by the server
    }
  }, [subscriptionData]);

In den Abhängigkeits-Array am Ende schreiben wir später alle Variablen oder Funktionen, von denen die Anweisungen innerhalb der geschweiften Klammern abhängen. Jetzt müssen wir nur noch in der if-Abfrage die User-Benachrichtigungen programmieren, dann ist auch dieses Feature fertig 🙂

Fazit und Ausblick

In diesem Blogbeitrag habe ich euch von meinem Vorgehen und meinen Erfahrungen, wie man ein React-Frontend mithilfe von Apollo Client an ein GraphQL-Backend anbindet, berichtet. Wir haben gelernt, wofür man GraphQL Queries, Mutations und Subscriptions verwendet und wie man sie in JavaScript im React-Frontend implementiert. Dazu gehörte auch, wie man diese GraphQL-Anfragen mittels Apollo Client in React aufrufen und deren Ergebnis im Code weiterverarbeiten kann.

Mir persönlich hat das Projekt viel Wissen vermittelt, weil React, GraphQL und „utility-first“ alles Bereiche sind, in denen ich mich bisher wenig bis gar nicht bewegt habe. Im folgenden Abschnitt ziehe ich für jede verwendete Technologie ein kurzes Fazit.

React habe ich als nicht besonders einsteigerfreundlich empfunden – unter anderem, weil ich ursprünglich von Vue.js komme, was sehr unterschiedlich zu React ist. Daher habe ich mir mit den Konzepten im Vergleich zum (parallelen) Einstieg in Angular eher schwergetan. Außerdem war es teilweise schwierig, Lösungen für Probleme oder Fehlermeldungen zu finden, weil viele Forenbeiträge etc. noch mit der älteren Klassensyntax statt der neuen Funktionssyntax verfasst wurden.

Mit Tailwind CSS konnte ich mich nur bedingt anfreunden. Ich verstehe den Gedanken, weniger CSS-Deklarationen zu schreiben, die sich dann in den Stylesheets auch weniger oft wiederholen. Löst man allerdings das komplette Styling über die Utility-Klassen, erhält man sehr schnell sehr viele Klassennamen, die meiner Meinung nach den Code unübersichtlich machen. Beispielsweise beschreibt im Code folgende Zeile das Styling für einen Button:

className="hover:cursor-pointer outline outline-4 outline-transparent text-white text-base text-center rounded-lg font-white bg-primary py-2 px-3 mr-4 hover:outline-primary-light active:bg-primary-dark disabled:bg-primary-dark disabled:outline-transparent disabled:cursor-auto"

Apollo Client zu verwenden war ziemlich entspannt, da die Library gefühlt alles (Ladezustand, Fehler, etc.) abfängt und aufbereitet, sodass wir mit den Responses der Abfragen direkt weiterarbeiten konnten. Außerdem kann man viele Einstellungen, z.B. wie gecached wird, so definieren, wie es am besten zum Projekt passt. Ein großer Pluspunkt ist zusätzlich, dass man Apollo Client auch als Einsteiger sehr schnell in Projekte integrieren und direkt damit arbeiten kann.

Bei GraphQL war es sehr interessant, mal etwas anderes als SQL-Abfragen zu verwenden. Vor allem, weil die GraphQL-Anfragen zwar unter Umständen viel Schreibarbeit sind, aber man dadurch auch genau sieht, was man als Ergebnis der Anfrage zurückbekommt. In diesem Projekt konnten wir leider nicht alle Features von GraphQL unterbringen, z. B. serverseitige Datenumwandlungen wie die Konvertierung von Einheiten, aber es war trotzdem spannend, sich damit zu beschäftigen.

So, jetzt haben wir eine lauffähige Web-App, mit minimalen, aber funktionierenden Features. Um die Anwendung intern zu nutzen, fehlt eigentlich nur noch ein Deployment. Wir haben uns dazu entschieden, VedaVersum vorerst auf GitLab-Pages zu deployen. Wie genau man dabei vorgeht, zeigen euch unsere DevOps Engineers im nächsten Artikel dieser Beitragsserie 🙂

Danke fürs Lesen und happy Coding!

Möchten Sie mehr zu unseren Leistungen, Produkten oder zu unserem UX-Prozess erfahren?
Wir sind gespannt auf Ihre Anfrage.

Client Relationship Manager
+49 681 959 3110

Bitte bestätigen Sie vor dem Versand Ihrer Anfrage über die obige Checkbox, dass wir Sie kontaktieren dürfen.