Automatisierung der GraphQL-Schemavalidierung mit @graphql-tools und @graphql-inspector

July 31, 2024

In der modernen Webentwicklung hat sich GraphQL zu einem leistungsstarken Werkzeug für das API-Design entwickelt, das Flexibilität und Effizienz bietet. Es ist jedoch entscheidend, dass die Abfragen in deiner Frontend-Anwendung aktuell und nicht veraltet sind, während sich deine API weiterentwickelt.

graphql schema image

Im obigen Beispiel sehen wir, dass das GraphQL-Schema eine veraltete Abfrage und ein veraltetes Feld enthält. Folglich könnte unsere Frontend-Anwendung beeinträchtigt werden und unsere Apps könnten nicht mehr aktuell sein.

Dieser Blogbeitrag zeigt dir, wie du die Validierung deiner GraphQL-Abfragen mit @graphql-tools und GraphQL Inspector automatisierst. Durch die Implementierung dieser Validierung kannst du die Integrität deiner Anwendung aufrechterhalten und sicherstellen, dass deine Abfragen stets mit den neuesten Schemaänderungen kompatibel sind.

 

Du kannst das offizielle Repository mit dem vollständigen Code hier einsehen.

 

Überlegungen

Bevor du mit der Implementierung beginnst, solltest du die folgenden Schritte zum Einrichten deines Projekts berücksichtigen:

Projekteinrichtung:

  • Unabhängig davon, ob du React, Flutter oder ein anderes Framework für deine Frontend-Anwendung verwendest, erstelle einen separaten Ordner für das Validierungsskript.

  • Erstelle ein neues Verzeichnis namens script innerhalb deines Projekts.

Initialisieren eines Node.js-Projekts:

  • Navigiere zum Verzeichnis script und initialisiere ein neues Node.js-Projekt:

cd script; npm init -y;
  • Erstelle eine Datei namens validaton-script.js

Abhängigkeiten installieren:

  • Installiere die erforderlichen Abhängigkeiten:

npm install @graphql-inspector/core @graphql-tools/load @graphql-tools/url-loader fs graphql-tag path

Schema und Abfragen:

  • Stelle sicher, dass dein GraphQL-Schema von einem Endpunkt aus zugänglich ist und dass du deine GraphQL-Abfragen in einer Datei definiert hast.

  • React-Dateibeispiel:

const GET_USER = gql` query getUser($id: Int!) { getUser(id: $id) { id name } } `;
  • Flutter-Dateibeispiel:

class GraphQLAPIDefinitions { static const getUser = r''' query getUser($id: Int!){ getUser(id: $id) { id name } } '''; }

Nachdem du diese Schritte abgeschlossen hast, hast du deine Umgebung eingerichtet und kannst das Validierungsskript erstellen und ausführen.

Validierungsskript

Nun lass uns in den Prozess des Schreibens des Skripts eintauchen, um die Validierung unserer GraphQL-Abfragen zu automatisieren.

Werkzeuge sammeln:

  • Zuerst müssen wir die wesentlichen Node.js-Module und -Bibliotheken für unser Skript sammeln. Wir verwenden @graphql-tools/load und @graphql-tools/url-loader, um das GraphQL-Schema zu laden, graphql-inspector, um es zu validieren, fs.promises und path, um mit dem Dateisystem zu arbeiten und graphql-tag, um die GraphQL-Abfragen zu parsen.

const { loadSchema } = require("@graphql-tools/load"); const { UrlLoader } = require("@graphql-tools/url-loader"); const { validate } = require("@graphql-inspector/core"); const fs = require("fs").promises; const path = require("path"); const gql = require("graphql-tag");

Szenario einrichten:

  • Als nächstes definieren wir die für unsere Aufgabe benötigten Konstanten. GRAPHQL_ENDPOINT gibt die URL an unter der sich unser GraphQL-Schema befindet. DEFINITIONS_FILE_PATH gibt den Pfad zu unserer Datei mit den GraphQL-Abfragedefinitionen an.

const GRAPHQL_ENDPOINT = "https://your-url/graphql"; const DEFINITIONS_FILE_PATH = path.join( __dirname, "your-graphql-file-definitions-path" );

Schema laden:

  • Die Funktion loadSchemaFromEndpoint ist dafür verantwortlich, das Schema vom angegebenen Endpunkt mithilfe von UrlLoader zu laden.

async function loadSchemaFromEndpoint(endpoint) { return loadSchema(endpoint, { loaders: [new UrlLoader()], }); }

Definitionen lesen:

  • Die Funktion readDefinitionsFromFile liest die Abfragedefinitionen aus einer Datei. Dies ist entscheidend, da sie die in der Frontend-Anwendung verwendeten GraphQL-Abfragen enthält.

async function readDefinitionsFromFile(filePath) { return fs.readFile(filePath, "utf-8"); }

Abfragen extrahieren:

  • extractQueriesFromDefinitions verwendet einen regulären Ausdruck, um Abfragen aus der Definitionsdatei zu extrahieren. Jede Abfrage wird geparst und zur Validierung vorbereitet.

function extractQueriesFromDefinitions(data) { const regex = /(query|mutation)\s+[a-zA-Z0-9_]+\s*\([^)]*\)\s*\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/gm; const sources = []; for (const match of data.matchAll(regex)) { const query = match[0]; const parsedQuery = gql`${query}`; const queryName = parsedQuery.definitions[0].name?.value; sources.push({ ...parsedQuery.loc.source, name: queryName }); } return sources; }
  • Falls du die komplexeren Abfragen verarbeiten musst, passe den regulären Ausdruck entsprechend an deine Bedürfnisse an.

Validieren und Protokollieren:

  • logAndExit verarbeitet die Validierungsantwort. Es filtert veraltete Felder, protokolliert sie und beendet den Prozess mit einem Erfolgs- oder Fehlercode basierend auf den Ergebnissen.

function logAndExit(response) { const deprecatedFields = response .filter((item) => item.deprecated.length > 0) .map((item) => { item.deprecated.forEach((itemDeprecated) => { console.log( `Deprecated fields on: ${item.source.name || ""}`, itemDeprecated.message ); }); return item.deprecated; }) .flat(); process.exit(deprecatedFields.length > 0 ? 1 : 0); }

Skript ausführen:

  • In der main-Funktion koordinieren wir den gesamten Prozess. Wir beginnen mit dem Laden des Schemas vom Endpunkt, lesen dann die Abfragedefinitionen aus der Datei, extrahieren die Abfragen und validieren sie gegen das Schema. Die Ergebnisse werden protokolliert und der Prozess wird basierend auf dem Validierungsergebnis beendet.

async function main() { try { const schema = await loadSchemaFromEndpoint(GRAPHQL_ENDPOINT); const definitionsData = await readDefinitionsFromFile(DEFINITIONS_FILE_PATH); const sources = extractQueriesFromDefinitions(definitionsData); const response = validate(schema, sources, { strictDeprecated: true, }); logAndExit(response); } catch (error) { console.error("An error occurred:", error); process.exit(1); } }
  • Schließlich rufen wir die main-Funktion auf, um den Validierungsprozess zu starten.

main();
  • Wir können es mit dem folgenden Befehl testen:

node graphql-validator/graphql-validator-script.js
output bash image

Vollständiger Code:

const { loadSchema } = require("@graphql-tools/load"); const { UrlLoader } = require("@graphql-tools/url-loader"); const { validate } = require("@graphql-inspector/core"); const fs = require("fs").promises; const path = require("path"); const gql = require("graphql-tag"); const GRAPHQL_ENDPOINT = "http://localhost:3000/graphql"; const DEFINITIONS_FILE_PATH = path.join( __dirname, "./graphql-definitions.txt" ); async function loadSchemaFromEndpoint(endpoint) { return loadSchema(endpoint, { loaders: [new UrlLoader()], }); } async function readDefinitionsFromFile(filePath) { return fs.readFile(filePath, "utf-8"); } function extractQueriesFromDefinitions(data) { const regex = /(query|mutation)\s+[a-zA-Z0-9_]+\s*\([^)]*\)\s*\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/gm; const sources = []; for (const match of data.matchAll(regex)) { const query = match[0]; const parsedQuery = gql`${query}`; const queryName = parsedQuery.definitions[0].name?.value; sources.push({ ...parsedQuery.loc.source, name: queryName }); } return sources; } function logAndExit(response) { const deprecatedFields = response .filter((item) => item.deprecated.length > 0) .map((item) => { item.deprecated.forEach((itemDeprecated) => { console.log( `Deprecated fields on: ${item.source.name || ""}`, itemDeprecated.message ); }); return item.deprecated; }) .flat(); process.exit(deprecatedFields.length > 0 ? 1 : 0); } async function main() { try { const schema = await loadSchemaFromEndpoint(GRAPHQL_ENDPOINT); const definitionsData = await readDefinitionsFromFile(DEFINITIONS_FILE_PATH); const sources = extractQueriesFromDefinitions(definitionsData); const response = validate(schema, sources, { strictDeprecated: true, }); logAndExit(response); } catch (error) { console.error("An error occurred:", error); process.exit(1); } } main();

Verwendung der Pipeline zur Automatisierung

Integriere das Validierungsskript in deine CI/CD-Pipeline, um die Schema-Validierung zu automatisieren. Hier ist ein Beispiel mit GitHub Actions:

Erstellen Sie eine Workflow-Datei:

  • Füge eine Datei .github/workflows/schema-validation.yml zu deinem Repository hinzu.

Definieren Sie den Workflow:

name: GraphQL Schema Validation on: [pull_request] jobs: validate-schema: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: '18' - run: cd graphql-validator && npm install - run: node graphql-validator/graphql-validator-script.js

Mit diesem Setup wird bei jedem Push und Pull Request die Schema-Validierung ausgelöst, wodurch sichergestellt wird, dass alle Änderungen an deinem Schema sofort validiert werden und Fehler frühzeitig im Entwicklungsprozess erkannt werden.

Schlussfolgerung

Die Automatisierung der Validierung deiner GraphQL-Abfragen ist ein entscheidender Schritt zur Aufrechterhaltung der Integrität deiner Frontend-Anwendungen. Durch die Nutzung von @graphql-tools und GraphQL Inspector kannst du sicherstellen, dass deine Abfragen mit dem sich entwickelnden Schema kompatibel bleiben. Diese Automatisierung spart nicht nur Zeit, sondern erhöht auch die Zuverlässigkeit deiner Anwendung. Die Integration dieser Validierung in deine CI/CD-Pipeline stellt sicher, dass alle Probleme frühzeitig erkannt werden, was deinen Entwicklungsprozess reibungslos und effizient gestaltet