Der Autor hat Girls Who Code dazu ausgewählt, im Rahmen des Programms Write for DOnations eine Spende zu erhalten.

Einführung

GraphQL ist ein API-Standard, der von Facebook als Alternative zu REST-APIs entwickelt wurde und im Open-Source-Modell bereitgestellt wird. Im Gegensatz zu REST-APIs verwendet GraphQL ein typisiertes System zur Definition seiner Datenstruktur, wobei alle Daten, die gesendet und empfangen werden, einem vordefinierten Schema entsprechen müssen. Außerdem macht die Sprache einen einzigen Endpunkt für die gesamte Kommunikation verfügbar (anstelle unterschiedlicher URLs für verschiedene Ressourcen) und löst das Overfetching-Problem dadurch, dass nur die Daten zurückgegeben werden, die der Client angefordert hat. Somit werden kleinere und präzisere Antworten generiert.

In diesem Tutorial erstellen Sie ein Backend für einen URL-Shortener – einen Dienst, der aus einer beliebigen URL eine kürzere, besser lesbare Version generiert. Außerdem erfahren Sie mehr über GraphQL-Konzepte wie Abfragen und Mutationen sowie Tools (wie die GraphiQL-Oberfläche). Vielleicht haben Sie solche Dienste zuvor bereits verwendet, wie z. B. bit.ly.

Da GraphQL eine sprachunabhängige Technologie ist, wird sie auf verschiedene Sprachen und Frameworks aufgesetzt. Hier verwenden Sie die allgemeine Python-Programmiersprache, das Django-Web-Framework sowie die Graphene-Django-Bibliothek als GraphQL-Python-Implementierung mit spezifischen Integrationen für Django.

Voraussetzungen

  • Um mit diesem Tutorial fortzufahren, muss auf Ihrem Entwicklungsrechner Python Version 3.5 oder höher installiert sein. Um Python zu installieren, folgen Sie unserem Tutorial Installieren und Einrichten einer lokalen Programmierumgebung für Python 3 für Ihr Betriebssystem. Stellen Sie sicher, dass Sie auch eine virtuelle Umgebung einrichten und starten; um den Vorgaben in diesem Tutorial zu folgen, können Sie Ihr Projektverzeichnis shorty nennen.

  • Grundlegende Kenntnisse über Django sind erwünscht, aber nicht zwingend. Wenn Sie neugierig sind, können Sie dieser Django-Entwicklungsreihe folgen, die von der DigitalOcean-Community erstellt wurde.

Schritt 1 — Einrichten des Django-Projekts

In diesem Schritt installieren Sie alle erforderlichen Tools für die Anwendung und Einrichtung Ihres Django-Projekts.

Sobald Sie Ihr Projektverzeichnis erstellt und Ihre virtuelle Umgebung gestartet haben (wie in den Voraussetzungen beschrieben), installieren Sie die erforderlichen Pakete mit pip, dem Python-Paketmanager. In diesem Tutorial werden Django Version 2.1.7 und Graphene-Django Version 2.2.0 oder höher installiert:

  • pip install "django==2.1.7" "graphene-django>==2.2.0"

Sie haben nun alle Werkzeuge, die Sie zur Erledigung Ihrer Aufgaben benötigen. Als Nächstes erstellen Sie ein Django-Projekt mit dem Befehl django-admin. Ein Projekt ist der standardmäßige Django-Baustein: ein Satz von Ordnern und Dateien mit allem, was für die Entwicklung einer Webanwendung erforderlich ist. In diesem Fall nennen Sie Ihr Projekt shorty und erstellen es in Ihrem aktuellen Ordner, indem Sie am Ende . angeben:

  • django-admin startproject shorty .

Nach der Erstellung Ihres Projekts führen Sie die Django-Migrationen durch. Diese Dateien enthalten von Django generierten Python-Code und sind für die Änderung der Struktur der Anwendung entsprechend den Django-Modellen verantwortlich. Änderungen können beispielsweise die Erstellung einer Tabelle beinhalten. Standardmäßig verfügt Django über einen eigenen Satz von Migrationen, die für Subsysteme wie Django-Authentifizierung zuständig sind. Daher müssen Sie sie mit dem folgenden Befehl ausführen:

  • python manage.py migrate

Dieser Befehl verwendet den Python-Interpreter, um ein Django-Skript namens manage.py aufzurufen, das für die Verwaltung verschiedener Aspekte Ihres Projekts verantwortlich ist, wie das Erstellen von Anwendungen oder die Ausführung von Migrationen.

Sie erhalten einen Output, der dem Folgenden ähnelt:

Output
Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying sessions.0001_initial... OK

Sobald die Datenbank von Django bereit ist, starten Sie ihren lokalen Entwicklungsserver:

  • python manage.py runserver

Dies ergibt:

Output
Performing system checks... System check identified no issues (0 silenced). March 18, 2020 - 15:46:15 Django version 2.1.7, using settings 'shorty.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C.

Dieser Befehl übernimmt die Eingabeaufforderung in Ihrem Terminal und startet den Server.

Besuchen Sie die Seite http://127.0.0.1:8000 in Ihrem lokalen Browser. Sie sehen diese Seite:

Titelseite des lokalen Django-Servers

Um den Server zu stoppen und zu Ihrem Terminal zurückzukehren, drücken Sie Strg+C. Wann immer Sie auf den Browser zugreifen müssen, stellen Sie sicher, dass der vorhergehende Befehl ausgeführt wird.

Als Nächstes beenden Sie diesen Schritt, indem Sie im Projekt die Django-Graphene-Bibliothek aktivieren. Django nutzt das Konzept von Apps, von Webanwendungen mit einer spezifischen Aufgabe. Ein Projekt besteht aus einer oder mehreren Apps. Öffnen Sie jetzt die Datei shorty/settings.py in Ihrem bevorzugten Text-Editor. In diesem Tutorial wird vim verwendet:

  • vim shorty/settings.py

Die Datei settings.py verwaltet alle Einstellungen in Ihrem Projekt. Suchen Sie darin nach dem Eintrag INSTALLED_APPS und fügen Sie die Zeile 'graphene_django' hinzu:

shorty/shorty/settings.py
...
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'graphene_django',
]
...

Dadurch erfährt Django, dass Sie eine App namens graphene_django verwenden werden, die Sie in Schritt 1 installiert haben.

Fügen Sie am unteren Ende der Datei die folgende Variable hinzu:

shorty/shorty/settings.py
...
GRAPHENE = {
    'SCHEMA': 'shorty.schema.schema',
}

Diese letzte Variable verweist auf Ihr zentrales Schema, das Sie später erstellen werden. In GraphQL enthält ein Schema alle Objekttypen, wie Ressourcen, Abfragen und Mutationen. Stellen Sie sich ein Schema als Dokumentation aller Daten und Funktionen vor, die in Ihrem System verfügbar sind.

Speichern und schließen Sie die Datei nach Vornahme der Änderungen.

Jetzt haben Sie das Django-Projekt konfiguriert. Im nächsten Schritt werden Sie eine Django-App und ihre Modelle erstellen.

Schritt 2 — Einrichten einer Django-App und ihrer Modelle

Eine Django-Plattform besteht normalerweise aus einem Projekt und vielen Anwendungen oder Apps. Eine App beschreibt eine Reihe von Funktionen innerhalb eines Projekts und kann, falls gut gestaltet, in Django wiederverwendet werden.

In diesem Schritt erstellen Sie eine App namens shortener, die für die tatsächliche URL-Verkürzung verantwortlich ist. Geben Sie für die Erstellung des Fundaments den folgenden Befehl in Ihr Terminal ein:

  • python manage.py startapp shortener

Hier haben Sie den Parameter startapp app_name verwendet, um manage.py anzuweisen, eine App namens shortener zu erstellen.

Um die App-Erstellung abzuschließen, öffnen Sie die Datei shorty/settings.py.

  • vim shorty/settings.py

Fügen Sie den Namen der App dem gleichen INSTALLED_APPS-Eintrag hinzu, den Sie zuvor geändert haben:

shorty/shorty/settings.py
...
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'graphene_django'
    'shortener',
]
...

Speichern und schließen Sie die Datei.

Nachdem Sie Ihren shortener zu shorty/settings.py hinzugefügt haben, können Sie mit der Erstellung der Modelle für Ihr Projekt fortfahren. Modelle sind eine der Schlüsselfunktionen in Django. Sie werden verwendet, um eine Datenbank auf eine “Python-artige” Weise darzustellen, sodass Sie Daten mit Python-Code verwalten, abfragen und speichern können.

Bevor die Datei models.py für Änderungen geöffnet wird, bietet Ihnen dieses Tutorial einen Überblick über die Änderungen, die Sie vornehmen werden.

Ihre Modelldatei (shortener/models.py) wird nach dem Austausch des vorhandenen Codes den folgenden Inhalt umfassen:

shorty/shortener/models.py
from hashlib import md5

from django.db import models

Hier importieren Sie die erforderlichen Pakete, die von Ihrem Code benötigt werden. Sie fügen oben die Zeile from hashlib import md5 hinzu, um die Standardbibliothek von Python zu importieren, die zur Erstellung eines Hash der URL verwendet wird. Die Zeile from django.db import models ist eine Django-Hilfe für das Erstellen von Modellen.

Warnung: Dieses Tutorial verweist auf Hash als das Ergebnis einer Funktion, die eine Eingabe nimmt und immer dieselbe Ausgabe zurückgibt. In diesem Tutorial wird für Demonstrationszwecke die MD5-Hash-Funktion verwendet.

Beachten Sie, dass MD5 Kollisionsprobleme aufweist und in der Produktion vermieden werden sollte.

Als Nächstes fügen Sie ein Modell namens URL mit den folgenden Feldern hinzu:

  • full_url: die URL, die verkürzt werden soll.
  • url_hash: ein kurzer Hashwert, der die vollständige URL repräsentiert.
  • clicks: gibt darüber Auskunft, wie oft auf die kurze URL zugegriffen wurde.
  • created_at: Datum und Uhrzeit der Erstellung der URL.
shorty/shortener/models.py
...

class URL(models.Model):
    full_url = models.URLField(unique=True)
    url_hash = models.URLField(unique=True)
    clicks = models.IntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)

Sie generieren den url_hash, indem Sie den MD5-Hash-Algorithmus auf das Feld full_url anwenden und nur die ersten 10 Zeichen nutzen, die bei der save()-Methode des Modells zurückgegeben werden; diese wird jedes Mal ausgeführt, wenn Django einen Eintrag in der Datenbank speichert. Außerdem verfolgen URL-Shortener meist, wie oft auf einen Link geklickt wurde. Sie können dies erreichen, indem Sie die Methode clicked() aufrufen, wenn die URL von einem Benutzer besucht wird.

Die genannten Operationen werden mit diesem Code in Ihrem URL-Modell hinzugefügt:

shorty/shortener/models.py
...

    def clicked(self):
        self.clicks += 1
        self.save()

    def save(self, *args, **kwargs):
        if not self.id:
            self.url_hash = md5(self.full_url.encode()).hexdigest()[:10]

        return super().save(*args, **kwargs)

Nachdem Sie den Code überprüft haben, öffnen Sie jetzt die Datei shortener/models.py:

  • vim shortener/models.py

Ersetzen Sie den Code mit dem folgenden Inhalt:

shorty/shortener/models.py
from hashlib import md5

from django.db import models


class URL(models.Model):
    full_url = models.URLField(unique=True)
    url_hash = models.URLField(unique=True)
    clicks = models.IntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)

    def clicked(self):
        self.clicks += 1
        self.save()

    def save(self, *args, **kwargs):
        if not self.id:
            self.url_hash = md5(self.full_url.encode()).hexdigest()[:10]

        return super().save(*args, **kwargs)

Speichern und schließen Sie die Datei.

Um diese Änderungen in der Datenbank anzuwenden, müssen Sie die entsprechenden Migrationen erstellen, indem Sie den folgenden Befehl ausführen:

  • python manage.py makemigrations

Damit erhalten Sie die folgende Ausgabe:

Output
Migrations for 'shortener': shortener/migrations/0001_initial.py - Create model URL

Dann führen Sie die Migrationen aus:

  • python manage.py migrate

Sie sehen in Ihrem Terminal folgende Ausgabe:

Output
Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions, shortener Running migrations: Applying shortener.0001_initial... OK

Nachdem Sie nun die Modelle eingerichtet haben, werden Sie im nächsten Schritt den GraphQL-Endpunkt und eine Abfrage erstellen.

Schritt 3 — Erstellen von Abfragen

Die REST-Architektur macht verschiedene Ressourcen in verschiedenen Endpunkten verfügbar, jeweils mit einer genau definierten Datenstruktur. Zum Beispiel können Sie unter /api/users eine Benutzerliste abrufen und dabei immer dieselben Felder erwarten. GraphQL hingegen verfügt über einen einzigen Endpunkt für alle Interaktionen und verwendet Abfragen für den Zugriff auf Daten. Der größte und wichtigste Unterschied besteht darin, dass Sie eine Abfrage verwenden können, um alle Ihre Benutzer im Rahmen einer einzigen Anfrage abzurufen.

Erstellen Sie zunächst eine Abfrage, um alle URLs abzurufen. Sie benötigen mehrere Dinge:

  • Einen URL-Typ, der mit Ihrem zuvor definierten Modell verknüpft ist.
  • Eine Abfrageanweisung namens urls.
  • Eine Methode zum Auflösen Ihrer Abfrage, was bedeutet, dass alle URLs aus der Datenbank abgerufen und an den Client zurückgegeben werden.

Erstellen Sie eine neue Datei namens shortener/schema.py:

  • vim shortener/schema.py

Fügen Sie zunächst die import-Anweisungen von Python hinzu:

shorty/shortener/schema.py
import graphene
from graphene_django import DjangoObjectType

from .models import URL

Die erste Zeile sorgt für den Import der wichtigsten graphene-Bibliothek, die die grundlegenden GraphQL-Typen enthält, z. B. List. Der DjangoObjectType ist ein Helfer, der Sie bei der Erstellung einer Schemadefinition aus einem beliebigen Django-Modell unterstützt,während die dritte Zeile dafür sorgt, dass Ihr zuvor erstelltes URL-Modell importiert wird.

Danach erstellen Sie einen neuen GraphQL-Typ für das Modell URL, indem Sie die folgenden Zeilen hinzufügen:

shorty/shortener/schema.py
...
class URLType(DjangoObjectType):
    class Meta:
        model = URL

Fügen Sie schließlich folgende Zeilen hinzu, um einen Abfragetyp für das URL-Modell zu erstellen:

shorty/shortener/schema.py
...
class Query(graphene.ObjectType):
    urls = graphene.List(URLType)

    def resolve_urls(self, info, **kwargs):
        return URL.objects.all()

Dieser Code erstellt eine Query-Klasse mit einem Feld namens urls, das eine Liste des zuvor definierten URLType ist. Wenn Sie die Abfrage mit der Methode resolve_urls auflösen, geben Sie alle in der Datenbank gespeicherten URLs zurück.

Die vollständige Datei shortener/schema.py wird hier angezeigt:

shorty/shortener/schema.py
import graphene
from graphene_django import DjangoObjectType

from .models import URL


class URLType(DjangoObjectType):
    class Meta:
        model = URL


class Query(graphene.ObjectType):
    urls = graphene.List(URLType)

    def resolve_urls(self, info, **kwargs):
        return URL.objects.all()

Speichern und schließen Sie die Datei.

Alle Abfragen müssen jetzt dem Hauptschema hinzugefügt werden. Stellen Sie sich das Schema als Behälter für alle Ihre Ressourcen vor.

Erstellen Sie eine neue Datei im Pfad shorty/schema.py und öffnen Sie sie mit Ihrem Editor:

  • vim shorty/schema

Importieren Sie die folgenden Python-Pakete, indem Sie folgende Zeilen hinzufügen. Die erste Zeile enthält, wie bereits erwähnt, die grundlegenden GraphQL-Typen. Die zweite Zeile importiert die zuvor erstellte Schemadatei.

shorty/shorty/schema.py
import graphene

import shortener.schema

Fügen Sie als Nächstes die Haupt-Query-Klasse hinzu. Sie wird via Vererbung alle Abfragen und künftig erstellten Operationen enthalten:

shorty/shorty/schema.py
...
class Query(shortener.schema.Query, graphene.ObjectType):
    pass

Erstellen Sie schließlich die Variable schema:

shorty/shorty/schema.py
...
schema = graphene.Schema(query=Query)

Die SCHEMA-Einstellung, die Sie in Schritt 2 definiert haben, verweist auf die Variable schema, die Sie gerade erstellt haben.

Die vollständige Datei shorty/schema.py wird hier angezeigt:

shorty/shorty/schema.py
import graphene

import shortener.schema


class Query(shortener.schema.Query, graphene.ObjectType):
    pass

schema = graphene.Schema(query=Query)

Speichern und schließen Sie die Datei.

Aktivieren Sie als Nächstes den GraphQL-Endpunkt und die GraphiQL-Oberfläche, die eine grafische Weboberfläche ist, die der Interaktion mit dem GraphQL-System dient.

Öffnen Sie die Datei shorty/urls.py:

  • vim shorty/urls.py

Zu Lernzwecken löschen Sie die Dateiinhalte und speichern sie, damit Sie ganz neu anfangen können.

Die ersten Zeilen, die Sie hinzufügen, sind Python-Importanweisungen:

shorty/shorty/urls.py
from django.urls import path
from django.views.decorators.csrf import csrf_exempt

from graphene_django.views import GraphQLView

Die Funktion path wird von Django verwendet, um eine aufrufbare URL für die GraphiQL-Oberfläche zu erstellen. Danach importieren Sie csrf_exempt, was es Clients ermöglicht, Daten an den Server zu senden. Eine vollständige Erklärung finden Sie in der Graphene-Dokumentation. In der letzten Zeile haben Sie über GraphQLView den tatsächlichen Code importiert, der für die Oberfläche verantwortlich ist.

Erstellen Sie als Nächstes eine Variable namens urlpatterns.

shorty/shorty/urls.py
...
urlpatterns = [
    path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))),
]

Diese wird den gesamten Code zusammenfügen, der erforderlich ist, um die GraphiQL-Oberfläche im Pfad graphql/ verfügbar zu machen:

Die vollständige Datei shortener/urls.py wird hier angezeigt:

shorty/shorty/urls.py
from django.urls import path
from django.views.decorators.csrf import csrf_exempt

from graphene_django.views import GraphQLView

urlpatterns = [
    path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))),
]

Speichern und schließen Sie die Datei.

Führen Sie zurück im Terminal den Befehl python manage.py runserver aus (wenn er nicht bereits ausgeführt wird):

  • python manage.py runserver

Öffnen Sie Ihren Webbrowser unter der Adresse http://localhost:8000/graphql. Ihnen wird der folgende Bildschirm angezeigt:

GraphiQL-Oberfläche

GraphiQL ist eine Oberfläche, in der Sie GraphQL-Anweisungen ausführen und die Ergebnisse anzeigen können. Eine Funktion ist der Abschnitt Docs oben rechts. Da alles in GraphQL typisiert ist, erhalten Sie eine freie Dokumentation für alle Ihre Typen, Abfragen, Mutationen usw.

Nach Erkundung der Seite fügen Sie im Haupttextbereich Ihre erste Abfrage ein:

query {
  urls {
    id
    fullUrl
    urlHash
    clicks
    createdAt
  }
}

Dieser Inhalt zeigt, wie eine GraphQL-Abfrage strukturiert ist: Erstens verwenden Sie das Schlüsselwort query, um dem Server zu sagen, dass Sie nur einige Daten zurückgeben möchten. Als Nächstes verwenden Sie das Feld urls, das in der Datei shortener/schema.py in der Klasse Query definiert ist. Daraus fordern Sie ausdrücklich an, dass alle im URL-Modell definierten Felder mit Höckerschrift definiert werden, was die Standardeinstellung für GraphQL ist.

Klicken Sie nun oben links auf die Wiedergabepfeiltaste.

Sie erhalten folgende Antwort, in der steht, dass Sie immer noch keine URLs haben:

Output
{ "data": { "urls": [] } }

Dies zeigt, dass GraphQL funktioniert. Drücken Sie in Ihrem Terminal Strg+C, um Ihren Server anzuhalten.

In diesem Schritt haben Sie viel erreicht: Sie haben den GraphQL-Endpunkt erstellt, eine Abfrage ausgeführt, um alle URLs abzurufen, und die GraphQL-Oberfläche aktiviert. Jetzt erstellen Sie Mutationen, um die Datenbank zu ändern.

Schritt 4 — Erstellen von Mutationen

Die meisten Anwendungen bieten eine Möglichkeit, den Datenbankzustand zu ändern, indem sie Daten hinzufügen, aktualisieren oder löschen. In GraphQL werden diese Operationen Mutationen genannt. Sie sehen wie Abfragen aus, verwenden aber Argumente, um Daten an den Server zu senden.

Um Ihre erste Mutation zu erstellen, öffnen Sie shortener/schema.py:

  • vim shortener/schema.py

Am Ende der Datei fügen Sie zunächst eine neue Klasse namens CreateURL hinzu:

shorty/shortener/schema.py
...
class CreateURL(graphene.Mutation):
    url = graphene.Field(URLType)

Diese Klasse erbt das Hilfsprogramm graphene.Mutation, um über die Fähigkeiten einer GraphQL-Mutation zu verfügen. Außerdem verfügt sie über einen Eigenschaftsnamen url, der den Inhalt definiert, der vom Server nach Fertigstellung der Mutation zurückgegeben wird. In diesem Fall ist es die Datenstruktur URLType.

Fügen Sie als Nächstes in der bereits definierten Klasse eine Unterklasse namens Arguments hinzu:

shorty/shortener/schema.py
...
    class Arguments:
        full_url = graphene.String()

Damit wird definiert, welche Daten vom Server akzeptiert werden. Hier erwarten Sie einen Parameter namens full_url mit einem String-Inhalt:

Fügen Sie jetzt folgende Zeilen hinzu, um die mutate-Methode zu erstellen:

shorty/shortener/schema.py
...

    def mutate(self, info, full_url):
        url = URL(full_url=full_url)
        url.save()

Diese mutate-Methode erledigt viele Aufgaben, indem sie Daten vom Client empfängt und in der Datenbank speichert. Am Ende gibt sie die Klasse selbst zurück, die das neu erstellte Element enthält.

Erstellen Sie schließlich eine Klasse Mutation, die alle Mutationen für Ihre App enthält, indem Sie folgende Zeilen hinzufügen:

shorty/shortener/schema.py
...

class Mutation(graphene.ObjectType):
    create_url = CreateURL.Field()

Bisher verfügen Sie nur über eine Mutation namens create_url.

Die vollständige Datei shortener/schema.py wird hier angezeigt:

shorty/shortener/schema.py
import graphene
from graphene_django import DjangoObjectType

from .models import URL


class URLType(DjangoObjectType):
    class Meta:
        model = URL


class Query(graphene.ObjectType):
    urls = graphene.List(URLType)

    def resolve_urls(self, info, **kwargs):
        return URL.objects.all()


class CreateURL(graphene.Mutation):
    url = graphene.Field(URLType)

    class Arguments:
        full_url = graphene.String()

    def mutate(self, info, full_url):
        url = URL(full_url=full_url)
        url.save()

        return CreateURL(url=url)


class Mutation(graphene.ObjectType):
    create_url = CreateURL.Field()

Schließen und speichern Sie die Datei.

Um das Hinzufügen der Mutation abzuschließen, ändern Sie die Datei shorty/schema.py:

  • vim shorty/schema.py

Ändern Sie die Datei, indem Sie den folgenden hervorgehobenen Code einfügen:

shorty/shorty/schema.py

import graphene

import shortener.schema


class Query(shortener.schema.Query, graphene.ObjectType):
    pass


class Mutation(shortener.schema.Mutation, graphene.ObjectType):
    pass


schema = graphene.Schema(query=Query, mutation=Mutation)

Speichern und schließen Sie die Datei. Wenn Sie den lokalen Server noch nicht ausführen, starten Sie ihn:

  • python manage.py runserver

Navigieren Sie in Ihrem Webbrowser zu http://localhost:8000/graphql. Führen Sie Ihre erste Mutation in der GraphiQL-Weboberfläche aus, indem Sie folgende Anweisung ausführen:

mutation {
  createUrl(fullUrl:"https://www.digitalocean.com/community") {
    url {
      id
      fullUrl
      urlHash
      clicks
      createdAt
    }
  }
}

Sie haben die Mutation mit dem Namen createURL, das Argument fullUrl und die Daten erstellt, die Sie in der im Feld url definierten Antwort erhalten möchten.

Die Ausgabe enthält die URL-Informationen, die Sie gerade im GraphQL-Feld data erstellt haben, wie hier gezeigt:

Output
{ "data": { "createUrl": { "url": { "id": "1", "fullUrl": "https://www.digitalocean.com/community", "urlHash": "077880af78", "clicks": 0, "createdAt": "2020-01-30T19:15:10.820062+00:00" } } } }

Damit wurde der Datenbank eine URL mit ihrer Hashversion hinzugefügt, wie Sie im Feld urlHash sehen können. Versuchen Sie, die im letzten Schritt erstellte Abfrage auszuführen, um ihr Ergebnis zu sehen:

query {
  urls {
    id
    fullUrl
    urlHash
    clicks
    createdAt
  }
}

Die Ausgabe zeigt die gespeicherte URL:

Output
{ "data": { "urls": [ { "id": "1", "fullUrl": "https://www.digitalocean.com/community", "urlHash": "077880af78", "clicks": 0, "createdAt": "2020-03-18T21:03:24.664934+00:00" } ] } }

Sie können auch versuchen, dieselbe Abfrage auszuführen, wobei Sie aber nur nach den gewünschten Feldern fragen.

Probieren Sie es als Nächstes noch einmal mit einer anderen URL:

mutation {
  createUrl(fullUrl:"https://www.digitalocean.com/write-for-donations/") {
    url {
      id
      fullUrl
      urlHash
      clicks
      createdAt
    }
  }
}

Der Output sieht wie folgt aus:

Output
{ "data": { "createUrl": { "url": { "id": "2", "fullUrl": "https://www.digitalocean.com/write-for-donations/", "urlHash": "703562669b", "clicks": 0, "createdAt": "2020-01-30T19:31:10.820062+00:00" } } } }

Das System ist jetzt in der Lage, Kurz-URLs zu erstellen und aufzulisten. Im nächsten Schritt aktivieren Sie Benutzer, um eine URL anhand ihrer Kurzversion aufzurufen und die Benutzer auf die korrekte Seite weiterzuleiten.

Schritt 5 — Erstellen des Zugriffs-Endpunkts

In diesem Schritt verwenden Sie Django Views – eine Methode, die eine Anfrage übernimmt und eine Antwort zurückgibt –, um alle Benutzer, die auf den Endpunkt http://localhost:8000/url_hash zugreifen, zur vollständigen URL umzuleiten.

Öffnen Sie die Datei shortener/views.py mit Ihrem Editor:

  • vim shortener/views.py

Importieren Sie zunächst zwei Pakete, indem Sie den Inhalt durch die folgenden Zeilen ersetzen:

shorty/shortener/views.py
from django.shortcuts import get_object_or_404, redirect

from .models import URL

Diese werden später noch genauer erläutert.

Als Nächstes erstellen Sie eine Django-Ansicht namens root. Fügen Sie am Ende Ihrer Datei den für die Ansicht verantwortlichen Codeausschnitt hinzu:

shorty/shortener/views.py
...

def root(request, url_hash):
    url = get_object_or_404(URL, url_hash=url_hash)
    url.clicked()

    return redirect(url.full_url)

Dadurch wird ein Argument namens url_hash von der URL empfangen, die von einem Benutzer angefordert wurde. Innerhalb der Funktion versucht die erste Zeile, mit dem Argument url_hash die URL aus der Datenbank abzurufen. Wenn die URL nicht gefunden wird, gibt sie den Fehler HTTP 404 an den Client zurück, was bedeutet, dass die Ressource fehlt. Anschließend wird die Eigenschaft clicked des URL-Eintrags inkrementiert, sodass verfolgt wird, wie oft auf die URL zugegriffen wird. Am Ende leitet sie den Client zur angeforderten URL weiter.

Die vollständige Datei shortener/views.py wird hier angezeigt:

shorty/shortener/views.py
from django.shortcuts import get_object_or_404, redirect

from .models import URL


def root(request, url_hash):
    url = get_object_or_404(URL, url_hash=url_hash)
    url.clicked()

    return redirect(url.full_url)

Speichern und schließen Sie die Datei.

Öffnen Sie als Nächstes shorty/urls.py:

  • vim shorty/urls.py

Fügen Sie den folgenden hervorgehobenen Code hinzu, um die root-Ansicht zu aktivieren.

shorty/shorty/urls.py

from django.urls import path
from django.views.decorators.csrf import csrf_exempt

from graphene_django.views import GraphQLView

from shortener.views import root


urlpatterns = [
    path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))),
    path('<str:url_hash>/', root, name='root'),
]

Die root-Ansicht wird im Pfad / Ihres Servers zugänglich sein und akzeptiert einen url_hash als Zeichenfolgenparameter.

Speichern und schließen Sie die Datei. Wenn Sie den lokalen Server nicht ausführen, starten Sie ihn durch Ausführung des Befehls python manage.py runserver.

Um Ihre neue Hinzufügung zu testen, öffnen Sie Ihren Webbrowser und rufen Sie die URL http://localhost:8000/077880af78 auf. Beachten Sie, dass der letzte Teil der URL der Hash ist, der von der Mutation in Schritt 5 erstellt wurde. Sie werden zur URL-Seite des Hash, in diesem Fall die Website der DigitalOcean-Community, umgeleitet.

Nachdem nun die URL-Umleitung funktioniert, machen Sie die Anwendung sicherer, indem Sie bei Ausführung der Mutation eine Fehlerbehandlung implementieren.

Schritt 6 — Implementieren der Fehlerbehandlung

Die Behandlung von Fehlern ist bei allen Anwendungen eine bewährte Methode, da Entwickler normalerweise nicht kontrollieren, was an den Server gesendet wird. Auf diese Weise können Sie versuchen, Fehler vorauszusehen und ihre Auswirkungen zu minimieren. In einem komplexen System wie GraphQL könnten viele Dinge schiefgehen: von Clients, die die falschen Daten anfragen, bis hin zum Server, der den Zugriff auf die Datenbank verliert.

Als typisiertes System kann GraphQL mit einer Operation namens Schemaüberprüfung alles überprüfen, was ein Client anfragt und empfängt. Sie können dies testen, indem Sie eine Abfrage mit einem nicht vorhandenen Feld erstellen.

Navigieren Sie in Ihrem Browser erneut zu http://localhost:8000/graphql und führen Sie die nächste Abfrage innerhalb der GraphiQL-Oberfläche mit dem Feld iDontExist aus:

query {
  urls {
    id
    fullUrl
    urlHash
    clicks
    createdAt
    iDontExist
  }
}

Da in Ihrer Abfrage kein iDontExist-Feld vorhanden ist, gibt GraphQL eine Fehlermeldung zurück:

Output
{ "errors": [ { "message": "Cannot query field \"iDontExist\" on type \"URLType\".", "locations": [ { "line": 8, "column": 5 } ] } ] }

Dies ist wichtig, da im typisierten GraphQL-System das Ziel darin besteht, nur die Daten zu senden und zu empfangen, die bereits im Schema definiert sind.

Die aktuelle Anwendung akzeptiert jede beliebige Zeichenfolge im Feld full_url. Das Problem besteht darin, dass Sie, wenn jemand eine schlecht konstruierte URL versendet, den Benutzer beim Versuch, die gespeicherten Daten abzurufen, ins Nirgendwo umleiten. In diesem Fall müssen Sie überprüfen, ob die full_url richtig formatiert ist, bevor sie in der Datenbank gespeichert wird; falls es Fehler gibt, lösen Sie die Ausnahme GraphQLError mit einer benutzerdefinierten Nachricht aus.

Lassen Sie uns diese Funktion in zwei Schritten implementieren. Öffnen Sie zunächst die Datei shortener/models.py:

  • vim shortener/models.py

Fügen Sie die hervorgehobenen Zeilen im import-Abschnitt hinzu:

shorty/shortener/models.py
from hashlib import md5

from django.db import models
from django.core.validators import URLValidator
from django.core.exceptions import ValidationError

from graphql import GraphQLError
...

Der URLValidator ist ein Django-Hilfsprogramm zur Überprüfung einer URL-Zeichenfolge, während GraphQLError von Graphene dazu verwendet wird, um Ausnahmen mit einer benutzerdefinierten Nachricht auszulösen.

Überprüfen Sie als Nächstes die vom Benutzer empfangene URL, bevor Sie sie in der Datenbank speichern. Aktivieren Sie diese Operation, indem Sie in der Datei shortener/models.py den hervorgehobenen Code hinzufügen:

shorty/shortener/models.py
class URL(models.Model):
    full_url = models.URLField(unique=True)
    url_hash = models.URLField(unique=True)
    clicks = models.IntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)

    def clicked(self):
        self.clicks += 1
        self.save()

    def save(self, *args, **kwargs):
        if not self.id:
            self.url_hash = md5(self.full_url.encode()).hexdigest()[:10]

        validate = URLValidator()
        try:
            validate(self.full_url)
        except ValidationError as e:
            raise GraphQLError('invalid url')

        return super().save(*args, **kwargs)

Zunächst instanziiert dieser Code den URLValidator in der Variable validate. Innerhalb des Blocks try/except überprüfen Sie mit validate() die empfangene URL und lösen einen GraphQLError mit der benutzerdefinierten Nachricht Ungültige URL aus, wenn etwas schiefgegangen ist.

Die vollständige Datei shortener/models.py wird hier angezeigt:

shorty/shortener/models.py
from hashlib import md5

from django.db import models
from django.core.validators import URLValidator
from django.core.exceptions import ValidationError

from graphql import GraphQLError


class URL(models.Model):
    full_url = models.URLField(unique=True)
    url_hash = models.URLField(unique=True)
    clicks = models.IntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)

    def clicked(self):
        self.clicks += 1
        self.save()

    def save(self, *args, **kwargs):
        if not self.id:
            self.url_hash = md5(self.full_url.encode()).hexdigest()[:10]

        validate = URLValidator()
        try:
            validate(self.full_url)
        except ValidationError as e:
            raise GraphQLError('invalid url')

        return super().save(*args, **kwargs)

Speichern und schließen Sie die Datei. Wenn Sie den lokalen Server noch nicht ausführen, starten Sie ihn mit dem Befehl python manage.py runserver.

Testen Sie als Nächstes Ihre neue Fehlerbehandlung unter http://localhost:8000/graphql. Versuchen Sie, in der GraphiQL-Oberfläche eine neue URL mit einer ungültigen full_url zu erstellen:

mutation {
  createUrl(fullUrl:"not_valid_url"){
    url {
      id
      fullUrl
      urlHash
      clicks
      createdAt
    }
  }
}

Wenn Sie eine ungültige URL senden, wird Ihre Ausnahme mit der benutzerdefinierten Nachricht ausgelöst:

Output
{ "errors": [ { "message": "invalid url", "locations": [ { "line": 2, "column": 3 } ], "path": [ "createUrl" ] } ], "data": { "createUrl": null } }

Wenn Sie in Ihrem Terminal nachsehen, wo der Befehl python manage.py runserver ausgeführt wird, wird ein Fehler angezeigt:

Output
... graphql.error.located_error.GraphQLLocatedError: invalid url [30/Jan/2020 19:46:32] "POST /graphql/ HTTP/1.1" 200 121

Ein GraphQL-Endpunkt wird immer mit einem HTTP 200-Statuscode fehlschlagen, was in der Regel Erfolg bedeutet. Denken Sie daran, dass GraphQL zwar auf HTTP aufbaut, jedoch nicht die Konzepte von HTTP-Statuscodes oder HTTP-Methoden verwendet, wie REST es tut.

Mit implementierter Fehlerbehandlung können Sie nun ein Verfahren zum Filtern Ihrer Abfrage einrichten, um die Daten zu minimieren, die vom Server zurückgegeben werden.

Schritt 7 — Implementieren von Filtern

Stellen Sie sich vor, Sie haben den URL-Shortener genutzt, um eigene Links hinzuzufügen. Nach einer Weile gibt es so viele Einträge, dass es schwierig wird, die richtigen zu finden. Sie können dieses Problem durch Einsatz von Filtern lösen.

Filtern ist ein gängiges Konzept in REST-APIs, bei dem in der Regel ein Abfrageparameter mit einem Feld und Wert an die URL angehängt wird. Zum Beispiel können Sie zum Filtern aller Benutzer namens jojo GET /api/users?name=jojo verwenden.

In GraphQL verwenden Sie Abfrageparameter als Filter. Sie sorgen für eine schöne und saubere Oberfläche.

Sie können das Problem des Auffindens einer URL lösen, indem Sie es dem Client mithilfe des Felds full_url ermöglichen, URLs anhand von Namen zu filtern. Öffnen Sie dazu die Datei shortener/schema.py in Ihrem bevorzugten Editor.

  • vim shortener/schema.py

Importieren Sie zunächst die Methode Q in die hervorgehobene Zeile:

shorty/shortener/schema.py
import graphene
from graphene_django import DjangoObjectType
from django.db.models import Q

from .models import URL
...

Dies dient zum Filtern Ihrer Datenbankanfrage.

Als Nächstes schreiben Sie die gesamte Query-Klasse mit dem folgenden Inhalt neu:

shorty/shortener/schema.py
...
class Query(graphene.ObjectType):
    urls = graphene.List(URLType, url=graphene.String())

    def resolve_urls(self, info, url=None, **kwargs):
        queryset = URL.objects.all()

        if url:
            _filter = Q(full_url__icontains=url)
            queryset = queryset.filter(_filter)

        return queryset
...

Die von Ihnen vorgenommenen Änderungen lauten:

  • Hinzufügen des Filterparameters url innerhalb der Variable urls und der Methode resolve_url.
  • Wenn in resolve_urls ein Parameter namens url angegeben wird, werden beim Filtern der Datenbank mit der Methode Q(full_url__icontains=url) nur URLs zurückgegeben, die den angegebenen Wert enthalten.

Die vollständige Datei shortener/schema.py wird hier angezeigt:

shorty/shortener/schema.py
import graphene
from graphene_django import DjangoObjectType
from django.db.models import Q

from .models import URL


class URLType(DjangoObjectType):
    class Meta:
        model = URL


class Query(graphene.ObjectType):
    urls = graphene.List(URLType, url=graphene.String())

    def resolve_urls(self, info, url=None, **kwargs):
        queryset = URL.objects.all()

        if url:
            _filter = Q(full_url__icontains=url)
            queryset = queryset.filter(_filter)

        return queryset


class CreateURL(graphene.Mutation):
    url = graphene.Field(URLType)

    class Arguments:
        full_url = graphene.String()

    def mutate(self, info, full_url)
        url = URL(full_url=full_url)
        url.save()

        return CreateURL(url=url)


class Mutation(graphene.ObjectType):
    create_url = CreateURL.Field()

Speichern und schließen Sie die Datei. Wenn Sie den lokalen Server noch nicht ausführen, starten Sie ihn mit dem Befehl python manage.py runserver.

Testen Sie Ihre letzten Änderungen unter http://localhost:8000/graphql. Schreiben Sie in der GraphiQL-Oberfläche die folgende Anweisung. Dadurch werden alle URLs mit dem Wort community gefiltert:

query {
  urls(url:"community") {
    id
    fullUrl
    urlHash
    clicks
    createdAt
  }
}

Die Ausgabe umfasst nur einen Eintrag, da Sie nur eine URL mit der Zeichenfolge community hinzugefügt haben. Wenn Sie zuvor weitere URLs hinzugefügt haben, kann Ihre Ausgabe anders aussehen.

Output
{ "data": { "urls": [ { "id": "1", "fullUrl": "https://www.digitalocean.com/community", "urlHash": "077880af78", "clicks": 1, "createdAt": "2020-01-30T19:27:36.243900+00:00" } ] } }

Nun haben Sie die Möglichkeit, Ihre URLs zu durchsuchen. Bei zu vielen Links beschweren sich Ihre Clients ggf. jedoch, dass die URL-Liste mehr Daten zurückgibt, als ihre Apps bewältigen können. Um dieses Problem zu lösen, werden Sie Paginierung implementieren.

Schritt 8 — Implementieren von Paginierung

Clients, die Ihr Backend verwenden, beschweren sich möglicherweise darüber, dass die Antwortzeit zu lange oder die Größe übermäßig ist, falls es zu viele URL-Einträge gibt. Außerdem kann Ihre Datenbank Probleme damit bekommen, eine riesige Menge von Daten zusammenzustellen. Um dieses Problem zu lösen, können Sie es dem Client anhand eines Verfahrens namens Paginierung gestatten festzulegen, wie viele Elemente innerhalb einer Anfrage zurückgegeben werden.

Es gibt keine Standardmethode zur Implementierung dieser Funktion. Auch in REST-APIs sehen Sie sie ggf. in HTTP-Headern oder Abfrageparametern – mit verschiedenen Namen und Verhaltensweisen.

In dieser Anwendung werden Sie Paginierung implementieren, indem Sie zunächst zwei weitere Argumente für die URL-Abfrage aktivieren: first und skip. first wählt die erste Variablenzahl von Elementen aus, während skip angibt, wie viele Elemente vom Anfang aus übersprungen werden sollen. Wenn Sie zum Beispiel first == 10 und skip == 5 verwenden, werden die ersten 10 URLs abgerufen, jedoch 5 von ihnen übersprungen, sodass nur die verbleibenden 5 zurückgegeben werden.

Die Implementierung dieser Lösung ähnelt dem Hinzufügen eines Filters.

Öffnen Sie die Datei shortener/schema.py:

  • vim shortener/schema.py

Ändern Sie in der Datei die Query-Klasse, indem Sie die beiden neuen Parameter in der Variable urls und der Methode resolve_urls hinzufügen, wie im folgenden Code hervorgehoben:

shorty/shortener/schema.py
import graphene
from graphene_django import DjangoObjectType
from django.db.models import Q

from .models import URL


class Query(graphene.ObjectType):
    urls = graphene.List(URLType, url=graphene.String(), first=graphene.Int(), skip=graphene.Int())

    def resolve_urls(self, info, url=None, first=None, skip=None, **kwargs):
        queryset = URL.objects.all()

        if url:
            _filter = Q(full_url__icontains=url)
            queryset = queryset.filter(_filter)

        if first:
            queryset = queryset[:first]

        if skip:
            queryset = queryset[skip:]

        return queryset
...

Dieser Code verwendet die neu erstellten Parameter first und skip innerhalb der Methode resolve_urls zum Filtern der Datenbankabfrage.

Speichern und schließen Sie die Datei. Wenn Sie den lokalen Server noch nicht ausführen, starten Sie ihn mit dem Befehl python manage.py runserver.

Um die Paginierung zu testen, geben Sie in der GraphiQL-Oberfläche unter http://localhost:8000/graphql folgende Abfrage ein:

query {
  urls(first: 2, skip: 1) {
    id
    fullUrl
    urlHash
    clicks
    createdAt
  }
}

Ihr URL-Shortener gibt die zweite in Ihrer Datenbank erstellte URL zurück:

Output
{ "data": { "urls": [ { "id": "2", "fullUrl": "https://www.digitalocean.com/write-for-donations/", "urlHash": "703562669b", "clicks": 0, "createdAt": "2020-01-30T19:31:10.820062+00:00" } ] } }

Das bedeutet, dass die Paginierung funktioniert. Probieren Sie die Funktion aus, indem Sie weitere URLs hinzufügen und verschiedene Sätze von first und skip testen.

Zusammenfassung

Das gesamte GraphQL-Ökosystem wächst von Tag zu Tag und beruht auf einer aktiven Community. Bei Firmen wie GitHub und Facebook hat sich bereits gezeigt, dass es bereit für die Produktion ist; jetzt können Sie diese Technologie also für Ihre eigenen Projekte verwenden.

In diesem Tutorial haben Sie mit GraphQL, Python und Django unter Verwendung von Konzepten wie Abfragen und Mutationen einen URL-Shortener erstellt. Darüber hinaus wissen Sie jetzt, wie Sie diese Technologien nutzen können, um mit dem Django-Web-Framework Webanwendungen zu erstellen.

Weitere Informationen über GraphQL und die hier verwendeten Tools finden Sie auf der GraphQL-Website sowie der Website mit der Graphene-Dokumentation. Außerdem verfügt DigitalOcean über zusätzliche Tutorials für Python und Django, die Sie verwenden können, falls Sie mehr über diese beiden Lösungen erfahren möchten.

0 Comments

Creative Commons License