© CreativeAngela/Shutterstock.com
RedwoodJS: Bringing Full Stack to the JAMstack – Teil 2

Das Full-Stack-Framework in Aktion


Im ersten Teil dieser Serie haben wir uns mit den Grundlagen von Redwood beschäftigt, einer Kombination aus etablierten Technologien zur Implementierung von Webapplikationen. Im zweiten Teil arbeiten wir mit Benutzereingaben, betrachten Authentifizierungsmöglichkeiten, Testing und vieles mehr.

Zu Beginn eine kleine Erinnerung: Im Frontend setzt RedwoodJS auf React, zur Kommunikation zwischen Front- und Backend wird GraphQL eingesetzt, im Backend verwendet Redwood Node.js und persistiert die Daten der Applikation in einer SQL-Datenbank. Die Besonderheit von Redwood gegenüber anderen Ansätzen ist, dass es relativ strikte Vorgaben macht und sowohl Client als auch Server aus einer Hand liefert. Ein weiterer Vorteil ist, dass bei Redwood das Deployment für bestimmte Plattformen, wie beispielsweise Netlify, Vercel oder AWS-Serverless, bereits vorbereitet ist und nach der initialen Konfiguration praktisch keinen weiteren Aufwand mehr bedeutet.

Bisher haben Sie einiges über die Architektur von Redwood erfahren und an einem kleinen Beispiel gesehen, wie die Daten vom Server zum Client fließen. Dieses Beispiel werden wir erweitern und um schreibende Aufrufe an den Server ergänzen; außerdem erfahren Sie, wie Sie innerhalb einer Redwood-Applikation navigieren können, wie Sie mit Eingaben Ihrer Benutzer umgehen und wie Sie die Applikation mit Hilfe von Authentifizierung vor dem Zugriff unberechtigter Dritter schützen können. Abgerundet wird das Ganze mit einem Blick auf das Thema Testing.

Schreibende Serverkommunikation

Die Applikation, die uns wie schon im ersten Teil begleiten wird, ist ein einfaches Adressbuch. Bisher können wir nur Daten anzeigen lassen und die Adressliste in unserer Applikation war lediglich in der Lage, die Adresseinträge darzustellen. Im nächsten Schritt erweitern wir die Liste um die Möglichkeit, Datensätze auch löschen zu können.

Löschen der Datensätze im Backend

Im ersten Teil dieser Artikelserie haben wir mit dem Kommando yarn rw g scaffold address dafür gesorgt, dass Redwood die Backend- und Frontend-Strukturen für die Adressverwaltung erzeugt. Auf dieser Basis haben wir anschließend unser eigenes Frontend aufgebaut. Das scaffold-Kommando legt im Backend eine Reihe von Strukturen an, die dafür sorgen, dass alle CRUD-Operationen auf den Adressdatensätzen möglich sind. Den Einstieg bildet die Schemadefinition in der addresses.sdl.js-Datei im graphql-Verzeichnis. Alternativ zum scaffold-Kommando können Sie auch nur die GraphQL-Schnittstelle erzeugen lassen. Dazu führen Sie auf der Kommandozeile den Befehl yarn rw d sdl addresses aus. Dieses Kommando erzeugt die Schemadefinition und den zugehörigen Service. Standardmäßig wird durch dieses Kommando lediglich der Query-Typ erzeugt. Mit der Option --crud weisen Sie Redwood an, auch Mutation-Typen zu erzeugen. Für das Löschen von Datensätzen wird hier ein Mutation-Typ benötigt. GraphQL unterscheidet zwischen den lesenden Query-Typen und den modifizierenden Mutation-Typen. In Listing 1 sehen Sie den Ausschnitt aus der addresses.sdl.js-Datei, die die GraphQL-Schemadefinition enthält.

Listing 1: GraphQL-Schemadefinition

export const schema = gql` type Address { id: Int! firstname: String! lastname: String! street: String! city: String! } ... type Mutation { ... deleteAddress(id: Int!): Address! } `

Für den Umgang mit Adressdatensätzen definieren wir zunächst den Address-Typ. Innerhalb des Mutation-Typs fassen Sie dann alle modifizierenden Operationen wie das Erzeugen, Aktualisieren und in unserem Fall Löschen von Datensätzen zusammen. Die deleteAddress-Mutation benötigt die ID des zu löschenden Datensatzes in Form einer Ganzzahl und gibt den gelöschten Datensatz zurück. Hinter jeder GraphQL-Schnittstelle steht eine Implementierung, die dafür sorgt, dass die angefragten Daten bereitgestellt werden beziehungsweise die gewünschte Operation ausgeführt wird. Diese Rolle erfüllen die Services in Redwood.

Haben Sie die SDL über die Kommandozeile definiert, hat Redwood automatisch einen solchen Service im services-Verzeichnis angelegt. Alle Dateien eines Service, also die Service-Datei addresses.js selbst und die zugehörigen Unit-Tests in der Datei addresses.test.js finden Sie in einem Unterverzeichnis mit dem Namen addresses. Der Service reicht die Anfrage im einfachsten Fall direkt an die Datenbank weiter, wie in Listing 2 sehen ist. Für den Datenbankzugriff verwendet Redwood die Bibliothek Prisma als ORM, damit kann über eine Objektnotation auf die Datenbank zugegriffen werden und SQL-Statements müssen nicht manuell geschrieben werden.

Listing 2: Löschen von Datensätzen mit Prisma

import { db } from 'src/lib/db' ... export const deleteAddress = ({ id }) => { return db.address.delete({ where: { id }, })

Die Kombination aus GraphQL, Prisma und Redwood sorgt dafür, dass die eigentlich asynchrone Operation von der Schnittstelle bis zur Datenbank und zurück ordnungsgemäß gehandhabt wird, sodass Sie sich nicht selbst um die asynchrone Flusssteuerung kümmern müssen. Mit diesen Backend-Strukturen kann mit einem GraphQL-Client auf die Schnittstelle zugegriffen, die deleteAddress-Mutation ausgelöst und Datensätze gelöscht werden. Damit die Benutzer Ihrer Applikation das neue Feature auch nutzen können, müssen Sie eine Möglichkeit zum Löschen der Datensätze ins Frontend integrieren. Hier bietet sich die Liste der Datensätze an und dort ein Button pro Datensatz, der die Löschoperation auslöst.

Löschen der Datensätze im Frontend

Die Darstellung der Adressdatensätze im Frontend gliedert sich in mehrere Teile: Die ListPage bildet den Rahmen und ist an die Standardroute / gebunden, sodass sie angezeigt wird, wenn die Benutzer die Applikation im Browser öffnen. Das AddressesLayout definiert den Rahmen der Ansicht mit einem Header und Footer. Die Page-Komponente integriert die Layout-Komponente und bindet den eigentlich anzuzeigenden Inhalt als Kindelemente in das Layout ein. Die ListItemCell-Komponente sorgt schließlich für die Anzeige der Datensätze. Eine solche Cell-Komponente deckt den gesamten Lebenszyklus einer asynchronen Komponente ab, die Daten vom Server lädt. Dazu definieren Sie verschiedene Komponenten mit den Namen Loading, Empty, Failure sowie Success. Diese decken den Ladezustand, ein leeres Ergebnis, einen Fehler beim Laden und das erfolgreiche Laden der Daten ab. Für unser Beispiel konzentrieren wir uns auf die Success-Komponente. Diese kümmert sich um die Listendarstellung und muss nun um eine Möglichkeit zum Löschen der Daten erweitert werden. Listing 3 enthält den Quellcode der ListItemCell.

Listing 3: Löschroutine in ListItemCell-Komponente

import { useMutation } from '@redwoodjs/web'; export const QUERY = gql` query ADDRESSES { addresses { id firstname lastname street city } } `; const DELETE_ADDRESS_MUTATION = gql` mutation DeleteAddressMutation($id: Int!) { deleteAddress(id: $id) { id } } `; ... export const Success = ({ addresses }) => { const [deleteAddress] = useMutation(DELETE_ADDRESS_MUTATION, { refetchQueries: [{ query: QUERY }], awaitRefetchQueries: true, }); const handleDelete = (address) => { if (confirm(`Are you sure to delete the address of ${address.firstname} ${address.lastname}?`)) { deleteAddress({ variables: { id: address.id } }); } }; return ( <tbody> {addresses.map((address) => ( <tr key={address.id}> <td>{address.firstname}</td> <td>{address.lastname}</td> <td>{address.street}</td> <td>{address.city}</td> <td> <button onClick={() => handleDelete(address)}>delete</button> </td> </tr> ))} </tbody> ); }

Die Komponente durchläuft alle Datensätze in einer Schleife und stellt sie dar. Die letzte Zelle einer jeden Zeile enthält ein button-Element zum Löschen des jeweiligen Datensatzes. Das Click-Event wird mit der handleDelete-Funktion verbunden, der Sie das zu löschende Address-Objekt übergeben. Die handleDelete-Funktion implementieren Sie innerhalb der Success-Komponente und fragen die Benutzer mit Hilfe der confirm-Funktion, ob der Datensatz wirklich gelöscht werden soll. Wird das bestätigt, führen Sie die deleteAddress-Funktion aus. Diese Funktion stößt die deleteAddress-Mutation an. Zur Erstellung dieser Funktion verwenden Sie den useMutation Hook. Dieser stammt von Apollo, dem GraphQL-Client, den Redwood verwendet. Redwood kapselt diese Hook-Funktion und stellt sie Ihnen zur Verfügung. Der useMutation-Funktion übergeben Sie die Mutation, die Sie mit Hilfe der gql-Funktion erzeugt haben. Als weitere Option übergeben Sie ein Objekt mit den Eigenschaften refetchQueries und awaitRefetchQueries. Diese sorgen dafür, dass die Daten der Listenabfrage nach einem erfolgreichen Löschvorgang erneut geladen und damit aktualisiert werden. Außerdem wartet Redwood darauf, dass die Daten geladen wurden, bis die Löschoperation als erledigt markiert wird.

Starten Sie Ihre Applikation im Entwicklungsmodus mit dem Kommando yarn rw dev, arbeitet im Hintergrund der webpack-Dev-Server und sorgt dafür, dass die Änderungen am Quellcode im Browser sofort wirksam werden. Betätigen Sie nun im Browser in Ihrer Applikation den Löschen-Button und bestätigen die angezeigte Meldung, sendet der Browser eine GraphQL-Anfrage an das Backend und führt die Delete-Mutation aus, die den Datensatz in der Datenbank löscht. Erhält der Browser die entsprechende Erfolgsmeldung, wird die Liste erneu...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang