Erste Schritte zum Erstellen von GraphQL-APIs mit Node

Avatar of Adam Scott
Adam Scott am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Wir alle haben eine Reihe von Interessen und Leidenschaften. Zum Beispiel interessiere ich mich für JavaScript, Indie-Rock und Hip-Hop der 90er Jahre, obskuren Jazz, die Stadt Pittsburgh, Pizza, Kaffee und Filme mit John Lurie. Wir haben auch Familienmitglieder, Freunde, Bekannte, Kommilitonen und Kollegen, die ebenfalls ihre eigenen sozialen Beziehungen, Interessen und Leidenschaften haben. Einige dieser Beziehungen und Interessen überschneiden sich, wie mein Freund Riley, der mein Interesse an Hip-Hop und Pizza aus den 90er Jahren teilt. Andere tun dies nicht, wie mein Kollege Harrison, der Python gegenüber JavaScript bevorzugt, nur Tee trinkt und aktuelle Popmusik bevorzugt. Alles in allem haben wir jeweils einen verbundenen Graphen der Menschen in unserem Leben und die Art und Weise, wie sich unsere Beziehungen und Interessen überschneiden.

Diese Art von vernetzten Daten sind genau die Herausforderung, die GraphQL ursprünglich bei der API-Entwicklung lösen sollte. Durch das Schreiben einer GraphQL-API können wir Daten effizient verbinden, was die Komplexität und die Anzahl der Anfragen reduziert, während wir dem Client genau die Daten liefern, die er benötigt. (Wenn Sie mehr GraphQL-Metaphern mögen, lesen Sie Meeting GraphQL at a Cocktail Mixer.)

In diesem Artikel erstellen wir eine GraphQL-API in Node.js unter Verwendung des Pakets Apollo Server. Dazu werden wir grundlegende GraphQL-Themen untersuchen, ein GraphQL-Schema schreiben, Code zum Auflösen unserer Schema-Funktionen entwickeln und über die GraphQL Playground-Benutzeroberfläche auf unsere API zugreifen.

Was ist GraphQL?

GraphQL ist eine Open-Source-Abfrage- und Datenmanipulationssprache für APIs. Sie wurde mit dem Ziel entwickelt, einzelne Endpunkte für Daten bereitzustellen, sodass Anwendungen genau die Daten anfordern können, die benötigt werden. Dies hat den Vorteil, nicht nur unseren UI-Code zu vereinfachen, sondern auch die Leistung zu verbessern, indem die Menge der über das Netz zu sendenden Daten begrenzt wird.

Was wir bauen

Um diesem Tutorial folgen zu können, benötigen Sie Node v8.x oder neuer und etwas Vertrautheit mit der Kommandozeile. 

Wir werden eine API-Anwendung für Buchzitate erstellen, die es uns ermöglicht, bemerkenswerte Passagen aus dem, was wir lesen, zu speichern. Benutzer der API können „CRUD“-Operationen (Create, Read, Update, Delete) für ihre Zitate durchführen.

  • Erstellen Sie ein neues Zitat
  • Lesen Sie ein einzelnes Zitat sowie eine Liste von Zitaten
  • Aktualisieren Sie den Inhalt eines Zitats
  • Löschen Sie ein Zitat

Erste Schritte

Um zu beginnen, erstellen Sie zunächst ein neues Verzeichnis für unser Projekt, initialisieren Sie ein neues Node-Projekt und installieren Sie die benötigten Abhängigkeiten.

# make the new directory
mkdir highlights-api
# change into the directory
cd highlights-api
# initiate a new node project
npm init -y
# install the project dependencies
npm install apollo-server graphql
# install the development dependencies
npm install nodemon --save-dev

Bevor wir fortfahren, lassen Sie uns unsere Abhängigkeiten aufschlüsseln.

  • apollo-server ist eine Bibliothek, die es uns ermöglicht, mit GraphQL in unserer Node-Anwendung zu arbeiten. Wir werden sie als eigenständige Bibliothek verwenden, aber das Apollo-Team hat auch Middleware für die Arbeit mit bestehenden Node-Webanwendungen in ExpresshapiFastify und Koa erstellt.
  • graphql enthält die GraphQL-Sprache und ist eine erforderliche Peer-Abhängigkeit von apollo-server.
  • nodemon ist eine nützliche Bibliothek, die unser Projekt auf Änderungen überwacht und unseren Server automatisch neu startet.

Nachdem unsere Pakete installiert sind, erstellen wir als Nächstes die Stammdatei unserer Anwendung, die wir index.js nennen. Vorerst werden wir eine Nachricht in dieser Datei console.log()en.

console.log("📚 Hello Highlights");

Um unseren Entwicklungsprozess zu vereinfachen, aktualisieren wir das scripts-Objekt in unserer package.json-Datei, um das nodemon-Paket zu nutzen.

"scripts": {
  "start": "nodemon index.js"
},

Jetzt können wir unsere Anwendung starten, indem wir npm start in der Terminalanwendung eingeben. Wenn alles ordnungsgemäß funktioniert, sehen Sie 📚 Hello Highlights in Ihrem Terminal ausgegeben.

GraphQL-Schema-Typen

Ein Schema ist eine schriftliche Darstellung unserer Daten und Interaktionen. Durch die Anforderung eines Schemas erzwingt GraphQL einen strengen Plan für unsere API. Dies liegt daran, dass die API nur Daten zurückgeben und Interaktionen durchführen kann, die im Schema definiert sind. Die grundlegenden Bestandteile von GraphQL-Schemas sind Objekttypen. GraphQL enthält fünf integrierte Typen.

  • String: Eine Zeichenkette mit UTF-8-Zeichenkodierung.
  • Boolean: Ein Wahr-oder-falsch-Wert.
  • Int: Eine 32-Bit-Ganzzahl.
  • Float: Ein Gleitkommawert.
  • ID: Ein eindeutiger Bezeichner.

Mit diesen grundlegenden Komponenten können wir ein Schema für eine API erstellen. In einer Datei namens schema.js können wir die gql-Bibliothek importieren und die Datei für unsere Schema-Syntax vorbereiten.

const { gql } = require('apollo-server');

const typeDefs = gql`
  # The schema will go here
`;

module.exports = typeDefs;

Um unser Schema zu schreiben, definieren wir zuerst den Typ. Betrachten wir, wie wir ein Schema für unsere Highlight-Anwendung definieren könnten. Zuerst würden wir einen neuen Typ mit dem Namen Highlight erstellen.

const typeDefs = gql`
  type Highlight {
  }
`;

Jedes Highlight hat eine eindeutige ID, etwas Inhalt, einen Titel und einen Autor. Das Highlight-Schema sieht dann ungefähr so aus:

const typeDefs = gql`
  type Highlight {
    id: ID
    content: String
    title: String
    author: String
  }
`;

Einige dieser Felder können wir durch Hinzufügen eines Ausrufezeichens als erforderlich kennzeichnen.

const typeDefs = gql`
  type Highlight {
    id: ID!
    content: String!
    title: String
    author: String
  }
`;

Obwohl wir einen Objekttyp für unsere Highlights definiert haben, müssen wir auch beschreiben, wie ein Client diese Daten abrufen wird. Dies wird als query bezeichnet. Wir werden uns später noch genauer mit Abfragen befassen, aber vorerst beschreiben wir in unserem Schema die Möglichkeiten, wie jemand Highlights abrufen kann. Bei der Anforderung aller unserer Highlights werden die Daten als Array (dargestellt als [Highlight]) zurückgegeben, und wenn wir ein einzelnes Highlight abrufen möchten, müssen wir eine ID als Parameter übergeben.

const typeDefs = gql`
  type Highlight {
    id: ID!
    content: String!
    title: String
    author: String
  }
  type Query {
    highlights: [Highlight]!
    highlight(id: ID!): Highlight
  }
`;

Jetzt können wir in der Datei index.js unsere Typdefinitionen importieren und Apollo Server einrichten.

const {ApolloServer } = require('apollo-server');
const typeDefs = require('./schema');

const server = new ApolloServer({ typeDefs });

server.listen().then(({ url }) => {
  console.log(`📚 Highlights server ready at ${url}`);
});

Wenn wir den Node-Prozess am Laufen gehalten haben, wurde die Anwendung automatisch aktualisiert und neu gestartet. Wenn nicht, startet die Eingabe von npm start aus dem Projektverzeichnis im Terminalfenster den Server. Wenn wir uns das Terminal ansehen, sollten wir sehen, dass nodemon unsere Dateien überwacht und der Server auf einem lokalen Port läuft.

[nodemon] 2.0.2
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node index.js`
📚 Highlights server ready at https://:4000/

Das Aufrufen der URL im Browser startet die GraphQL Playground-Anwendung, die eine Benutzeroberfläche für die Interaktion mit unserer API bietet.

GraphQL-Resolver

Obwohl wir unser Projekt mit einem ersten Schema und der Apollo Server-Einrichtung entwickelt haben, können wir noch nicht mit unserer API interagieren. Dazu führen wir Resolver ein. Resolver führen genau die Aktion aus, die ihr Name andeutet; sie lösen die vom API-Benutzer angeforderten Daten auf. Wir werden diese Resolver schreiben, indem wir sie zuerst in unserem Schema definieren und dann die Logik in unserem JavaScript-Code implementieren. Unsere API wird zwei Arten von Resolvern enthalten: Queries und Mutations.

Fügen wir zunächst einige Daten zum Interagieren hinzu. In einer Anwendung würden dies typischerweise Daten sein, die wir aus einer Datenbank abrufen und in die wir schreiben, aber für unser Beispiel verwenden wir ein Array von Objekten. Fügen Sie in der Datei index.js folgendes hinzu:

let highlights = [
  {
    id: '1',
    content: 'One day I will find the right words, and they will be simple.',
    title: 'Dharma Bums',
    author: 'Jack Kerouac'
  },
  {
    id: '2',
    content: 'In the limits of a situation there is humor, there is grace, and everything else.',
    title: 'Arbitrary Stupid Goal',
    author: 'Tamara Shopsin'
  }
]

Queries

Eine Abfrage fordert spezifische Daten von einer API in ihrem gewünschten Format an. Die Abfrage gibt dann ein Objekt zurück, das die vom API-Benutzer angeforderten Daten enthält. Eine Abfrage modifiziert niemals die Daten; sie greift nur darauf zu. Wir haben bereits zwei Abfragen in unserem Schema geschrieben. Die erste gibt ein Array von Highlights zurück und die zweite gibt ein bestimmtes Highlight zurück. Der nächste Schritt ist das Schreiben der Resolver, die die Daten zurückgeben.

In der index.js-Datei können wir ein resolvers-Objekt hinzufügen, das unsere Abfragen enthalten kann.

const resolvers = {
  Query: {
    highlights: () => highlights,
    highlight: (parent, args) => {
      return highlights.find(highlight => highlight.id === args.id);
    }
  }
};

Die highlights-Abfrage gibt das vollständige Array der Highlight-Daten zurück. Die highlight-Abfrage akzeptiert zwei Parameter:parent und args. Der parent ist der erste Parameter jeder GraqhQL-Abfrage in Apollo Server und bietet eine Möglichkeit, auf den Kontext der Abfrage zuzugreifen. Der args-Parameter ermöglicht uns den Zugriff auf die vom Benutzer bereitgestellten Argumente. In diesem Fall werden Benutzer der API ein id-Argument übergeben, um ein bestimmtes Highlight abzurufen.

Wir können dann unsere Apollo Server-Konfiguration aktualisieren, um die Resolver einzuschließen.

const server = new ApolloServer({ typeDefs, resolvers });

Nachdem unsere Query-Resolver geschrieben und Apollo Server aktualisiert wurden, können wir jetzt die API über GraphQL Playground abfragen. Um auf GraphQL Playground zuzugreifen, besuchen Sie https://:4000 in Ihrem Webbrowser.

Eine Abfrage ist wie folgt formatiert:

query {
  queryName {
      field
      field
    }
}

Mit diesem Wissen können wir eine Abfrage schreiben, die die ID, den Inhalt, den Titel und den Autor für jedes unserer Highlights anfordert.

query {
  highlights {
    id
    content
    title
    author
  }
}

Nehmen wir an, wir hätten eine Seite in unserer Benutzeroberfläche, die nur die Titel und Autoren unserer hervorgehobenen Texte auflistet. Wir müssten den Inhalt für jedes dieser Highlights nicht abrufen. Stattdessen könnten wir eine Abfrage schreiben, die nur die benötigten Daten abruft.

query {
  highlights {
    title
    author
  }
}

Wir haben auch einen Resolver zum Abfragen einer einzelnen Notiz geschrieben, indem wir ein ID-Parameter mit unserer Abfrage einbeziehen. Dies können wir wie folgt tun:

query {
  highlight(id: "1") {
    content
  }
}

Mutations

Wir verwenden eine Mutation, wenn wir die Daten in unserer API ändern möchten. In unserem Highlight-Beispiel möchten wir eine Mutation schreiben, um ein neues Highlight zu erstellen, eine, um ein vorhandenes Highlight zu aktualisieren, und eine dritte, um ein Highlight zu löschen. Ähnlich wie eine Abfrage soll auch eine Mutation ein Ergebnis in Form eines Objekts zurückgeben, typischerweise das Endergebnis der durchgeführten Aktion.

Der erste Schritt zur Aktualisierung von etwas in GraphQL ist das Schreiben des Schemas. Wir können Mutationen in unser Schema aufnehmen, indem wir einen Mutationstyp zu unserer schema.js-Datei hinzufügen.

type Mutation {
  newHighlight (content: String! title: String author: String): Highlight!
  updateHighlight(id: ID! content: String!): Highlight!
  deleteHighlight(id: ID!): Highlight!
}

Unsere newHighlight-Mutation nimmt den erforderlichen Wert für content zusammen mit optionalen title - und author-Werten entgegen und gibt ein Highlight zurück. Die updateHighlight-Mutation erfordert, dass eine Highlight-id und content als Argumentwerte übergeben werden, und gibt das aktualisierte Highlight zurück. Schließlich akzeptiert die deleteHighlight-Mutation ein ID-Argument und gibt das gelöschte Highlight zurück.

Nachdem das Schema um Mutationen erweitert wurde, können wir nun die resolvers in unserer index.js-Datei aktualisieren, um diese Aktionen auszuführen. Jede Mutation aktualisiert unser highlights-Datenarray.

const resolvers = {
  Query: {
    highlights: () => highlights,
    highlight: (parent, args) => {
      return highlights.find(highlight => highlight.id === args.id);
    }
  },
  Mutation: {
    newHighlight: (parent, args) => {
      const highlight = {
        id: String(highlights.length + 1),
        title: args.title || '',
        author: args.author || '',
        content: args.content
      };
      highlights.push(highlight);
      return highlight;
    },
    updateHighlight: (parent, args) => {
      const index = highlights.findIndex(highlight => highlight.id === args.id);
      const highlight = {
        id: args.id,
        content: args.content,
        author: highlights[index].author,
        title: highlights[index].title
      };
      highlights[index] = highlight;
      return highlight;
    },
    deleteHighlight: (parent, args) => {
      const deletedHighlight = highlights.find(
        highlight => highlight.id === args.id
      );
      highlights = highlights.filter(highlight => highlight.id !== args.id);
      return deletedHighlight;
    }
  }
};

Mit diesen geschriebenen Mutationen können wir GraphQL Playground verwenden, um das Mutieren der Daten zu üben. Die Struktur einer Mutation ist fast identisch mit der einer Abfrage; sie gibt den Namen der Mutation an, übergibt die Argumentwerte und fordert spezifische Daten als Rückgabe an. Beginnen wir mit dem Hinzufügen eines neuen Highlights.

mutation {
  newHighlight(author: "Adam Scott" title: "JS Everywhere" content: "GraphQL is awesome") {
    id
    author
    title
    content
  }
}

Wir können dann Mutationen schreiben, um ein Highlight zu aktualisieren.

mutation {
  updateHighlight(id: "3" content: "GraphQL is rad") {
    id
    content
  }
}

Und um ein Highlight zu löschen.

mutation {
  deleteHighlight(id: "3") {
    id
  }
}

Zusammenfassung

Herzlichen Glückwunsch! Sie haben jetzt erfolgreich eine GraphQL-API mit Apollo Server erstellt und können GraphQL-Abfragen und -Mutationen gegen ein In-Memory-Datenobjekt ausführen. Wir haben eine solide Grundlage für die Erkundung der Welt der GraphQL-API-Entwicklung geschaffen.

Hier sind einige mögliche nächste Schritte zur Weiterentwicklung: