Erstellen einer 100 % serverlosen REST-API mit Firebase Functions & FaunaDB

❥ Sponsor

Indie- und Enterprise-Webentwickler setzen gleichermaßen auf eine serverlose Architektur für moderne Anwendungen. Serverlose Architekturen skalieren typischerweise gut, vermeiden die Notwendigkeit der Serverbereitstellung und sind vor allem einfach und kostengünstig einzurichten! Deshalb glaube ich, dass die nächste Evolutionsstufe der Cloud serverlos ist, da sie es Entwicklern ermöglicht, sich auf das Schreiben von Anwendungen zu konzentrieren.

Mit diesem Gedanken im Hinterkopf bauen wir eine REST-API (denn werden wir jemals aufhören, diese zu erstellen?) unter Verwendung von 100 % serverloser Technologie.

Wir werden dies mit Firebase Cloud Functions und FaunaDB, einer global verteilten serverlosen Datenbank mit nativem GraphQL, tun.

Für diejenigen, die mit Firebase vertraut sind, wissen sie, dass Googles serverlose Tools zum Erstellen von Apps auch mehrere Datenspeicheroptionen bieten: Firebase Realtime Database und Cloud Firestore. Beide sind gültige Alternativen zu FaunaDB und sind effektiv serverlos.

Aber warum FaunaDB wählen, wenn Firestore ein ähnliches Versprechen bietet und mit Googles Werkzeugkasten verfügbar ist? Da unsere Anwendung ziemlich einfach ist, spielt das nicht so eine große Rolle. Der Hauptunterschied besteht darin, dass FaunaDB bei wachsender Anwendung und dem Hinzufügen mehrerer Sammlungen immer noch Konsistenz über mehrere Sammlungen hinweg bietet, während Firestore dies nicht tut. In diesem Fall habe ich meine Wahl aufgrund einiger anderer netter Vorteile von FaunaDB getroffen, die Sie beim Lesen entdecken werden — und die großzügige kostenlose Stufe von FaunaDB schadet auch nicht. 😉

In diesem Beitrag werden wir abdecken

  • Installation der Firebase CLI-Tools
  • Erstellen eines Firebase-Projekts mit Hosting- und Cloud-Function-Funktionen
  • Weiterleitung von URLs an Cloud Functions
  • Erstellen von drei REST-API-Aufrufen mit Express
  • Einrichten einer FaunaDB-Sammlung zur Verfolgung Ihrer (meiner) Lieblingsvideospiele
  • Erstellen von FaunaDB-Dokumenten, Zugriff darauf mit der JavaScript-Client-API von FaunaDB und Ausführen von Abfragen auf grundlegendem und mittlerem Niveau
  • Und mehr, natürlich!

Richten Sie ein lokales Firebase Functions-Projekt ein

Für diesen Schritt benötigen Sie Node v8 oder höher. Installieren Sie firebase-tools global auf Ihrem Rechner

$ npm i -g firebase-tools

Melden Sie sich dann bei Firebase mit diesem Befehl an

$ firebase login

Erstellen Sie ein neues Verzeichnis für Ihr Projekt, z. B. mkdir serverless-rest-api und navigieren Sie hinein.

Erstellen Sie ein Firebase-Projekt in Ihrem neuen Verzeichnis, indem Sie firebase init ausführen.

Wählen Sie bei Aufforderung Functions und Hosting aus.

Wählen Sie „functions“ und „hosting“ aus, wenn die Bubbles erscheinen, erstellen Sie ein brandneues Firebase-Projekt, wählen Sie JavaScript als Ihre Sprache und wählen Sie ja (y) für die restlichen Optionen.

Erstellen Sie ein neues Projekt und wählen Sie dann JavaScript als Ihre Cloud Function-Sprache.

Sobald Sie fertig sind, wechseln Sie in das Verzeichnis functions. Hier befindet sich Ihr Code und hier werden Sie einige NPM-Pakete hinzufügen.

Ihre API benötigt Express, CORS und FaunaDB. Installieren Sie alles mit dem Folgenden

$ npm i cors express faunadb

Richten Sie FaunaDB mit NodeJS und Firebase Cloud Functions ein

Bevor Sie FaunaDB verwenden können, müssen Sie sich für ein Konto anmelden.

Wenn Sie angemeldet sind, rufen Sie Ihre FaunaDB-Konsole auf und erstellen Sie Ihre erste Datenbank, nennen Sie sie „Games“.

Sie werden feststellen, dass Sie Datenbanken innerhalb anderer Datenbanken erstellen können. Sie könnten also eine Datenbank für die Entwicklung, eine für die Produktion erstellen oder sogar eine kleine Datenbank pro Unit-Test-Suite erstellen. Vorerst brauchen wir aber nur ‚Games‘, also machen wir weiter.

Erstellen Sie eine neue Datenbank und nennen Sie sie „Games“.

Wechseln Sie dann zu Collections und erstellen Sie Ihre erste Collection namens ‚games‘. Collections enthalten Ihre Dokumente (in diesem Fall Spiele) und sind das Äquivalent zu einer Tabelle in anderen Datenbanken – machen Sie sich keine Sorgen über Zahlungsdetails, Fauna hat eine großzügige kostenlose Stufe, die Lese- und Schreibvorgänge, die Sie in diesem Tutorial durchführen, werden diese kostenlose Stufe definitiv nicht überschreiten. Sie können Ihre Nutzung jederzeit in der FaunaDB-Konsole überwachen.

Für den Zweck dieser API stellen Sie sicher, dass Sie Ihre Collection ‚games‘ nennen, da wir Ihre (meine) Lieblingsvideospiele mit dieser nerdigen kleinen API verfolgen werden.

Erstellen Sie eine Collection in Ihrer Games-Datenbank und nennen Sie sie „Games“.

Wechseln Sie zu Security und erstellen Sie einen neuen Schlüssel und nennen Sie ihn „Personal Key“. Es gibt 3 verschiedene Arten von Schlüsseln: Admin/Server/Client. Ein Admin-Schlüssel dient zur Verwaltung mehrerer Datenbanken. Ein Server-Schlüssel ist typischerweise das, was Sie in einem Backend verwenden, das Ihnen die Verwaltung einer Datenbank ermöglicht. Schließlich ist ein Client-Schlüssel für nicht vertrauenswürdige Clients wie Ihren Browser gedacht. Da wir diesen Schlüssel verwenden werden, um in einer serverlosen Backend-Umgebung auf eine FaunaDB-Datenbank zuzugreifen, wählen Sie ‚Server key‘.

Unter der Registerkarte Sicherheit erstellen Sie einen neuen Schlüssel. Nennen Sie ihn Personal Key.

Speichern Sie den Schlüssel irgendwo, Sie werden ihn bald brauchen.

Erstellen Sie eine Express REST-API mit Firebase Functions

Firebase Functions können direkt auf externe HTTPS-Anfragen reagieren, und die Funktionen übergeben Standard-Node-Request- und Response-Objekte an Ihren Code — großartig. Dies macht Google Cloud Function-Anfragen für Middleware wie Express zugänglich.

Öffnen Sie index.js in Ihrem functions-Verzeichnis, **löschen Sie den vorausgefüllten Code** und fügen Sie Folgendes hinzu, um Firebase Functions zu aktivieren

const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)

Importieren Sie die FaunaDB-Bibliothek und richten Sie sie mit dem im vorherigen Schritt generierten Geheimnis ein

admin.initializeApp(...)
 
const faunadb = require('faunadb')
const q = faunadb.query
const client = new faunadb.Client({
  secret: 'secrety-secret...that’s secret :)'
})

Erstellen Sie dann eine einfache Express-App und aktivieren Sie CORS, um Cross-Origin-Anfragen zu unterstützen

const client = new faunadb.Client({...})
 
const express = require('express')
const cors = require('cors')
const api = express()
 
// Automatically allow cross-origin requests
api.use(cors({ origin: true }))

Sie sind bereit, Ihre erste Firebase Cloud Function zu erstellen, und das ist so einfach wie das Hinzufügen dieses Exports

api.use(cors({...}))
 
exports.api = functions.https.onRequest(api)

Dies erstellt eine Cloud-Funktion namens „api“ und leitet alle Anfragen direkt an Ihren Express-Server api weiter.

Leiten Sie eine API-URL an eine Firebase HTTPS Cloud Function weiter

Wenn Sie jetzt bereitstellen, wäre die öffentliche URL Ihrer Funktion ungefähr so: https://projekt-name.firebaseapp.com/api. Das ist ein sperriger Name für einen Zugangspunkt, wenn ich das so sagen darf (und ich habe es gesagt, weil ich das hier geschrieben habe… wer hat sich diesen nutzlosen Satz ausgedacht?)

Um dieses Dilemma zu lösen, werden Sie die Firebase-Hosting-Optionen verwenden, um URL-Globs an Ihre neue Funktion weiterzuleiten.

Öffnen Sie firebase.json und fügen Sie den folgenden Abschnitt unmittelbar unter dem Array „ignore“ hinzu

"ignore": [...],
"rewrites": [
  {
    "source": "/api/v1**/**",
    "function": "api"
  }
]

Diese Einstellung weist alle /api/v1/...-Anfragen Ihrer brandneuen Funktion zu, wodurch sie von einer Domain erreichbar wird, die Menschen gerne in ihre Texteditoren eingeben.

Damit sind Sie bereit, Ihre API zu testen. Ihre API, die… nichts tut!

Antworten Sie auf API-Anfragen mit Express und Firebase Functions

Bevor Sie Ihre Funktion lokal ausführen, geben wir Ihrer API etwas zu tun.

Fügen Sie diese einfache Route zu Ihrer index.js-Datei hinzu, direkt über Ihrer Exportanweisung

api.get(['/api/v1', '/api/v1/'], (req, res) => {
  res
    .status(200)
    .send(`<img src="https://media.giphy.com/media/hhkflHMiOKqI/source.gif">`)
})
 
exports.api = ...

Speichern Sie Ihre index.js-Datei, öffnen Sie Ihre Kommandozeile und wechseln Sie in das Verzeichnis functions.

Wenn Sie Firebase global installiert haben, können Sie Ihr Projekt ausführen, indem Sie Folgendes eingeben: firebase serve.

Dieser Befehl führt sowohl die Hosting- als auch die Funktionsumgebungen von Ihrem Rechner aus.

Wenn Firebase lokal in Ihrem Projektverzeichnis installiert ist, öffnen Sie package.json, entfernen Sie den Parameter --only functions aus Ihrem serve-Befehl und führen Sie dann npm run serve von Ihrer Kommandozeile aus.

Besuchen Sie localhost:5000/api/v1/ in Ihrem Browser. Wenn alles richtig eingerichtet wurde, werden Sie von einem GIF aus einem meiner Lieblingsfilme begrüßt.

Und wenn es nicht auch einer Ihrer Lieblingsfilme ist, werde ich es nicht persönlich nehmen, aber ich werde sagen, dass es andere Tutorials gibt, die Sie lesen könnten, Bethany.

Sie können jetzt den Hosting- und Funktionsemulator weiterlaufen lassen. Sie werden sich automatisch aktualisieren, wenn Sie Ihre index.js-Datei bearbeiten. Nett, oder?

FaunaDB-Indizierung

Um Daten in Ihrer games-Sammlung abzufragen, benötigt FaunaDB einen Index.

Indizes optimieren generell die Abfrageleistung in allen Arten von Datenbanken, aber in FaunaDB sind sie zwingend erforderlich und Sie müssen sie im Voraus erstellen.

Als Entwickler, der gerade mit FaunaDB beginnt, fühlte sich diese Anforderung wie eine digitale Hürde an.

„Warum kann ich nicht einfach Daten abfragen?“, grimassierte ich, als die rechte Seite meines Mundes versuchte, meine Augenbraue zu treffen.

Ich musste die Dokumentation lesen und mich damit vertraut machen, wie Indizes und die Fauna Query Language (FQL) tatsächlich funktionieren; während Cloud Firestore Indizes automatisch erstellt und mir super einfache Möglichkeiten bietet, auf meine Daten zuzugreifen. Was ist los?

Typische Datenbanken lassen Sie einfach tun, was Sie wollen, und wenn Sie nicht innehalten und nachdenken: „Ist das performant?“ oder „Wie viele Lesevorgänge wird mich das kosten?“, könnten Sie langfristig ein Problem haben. Fauna verhindert dies, indem es einen Index verlangt, wann immer Sie abfragen.
Als ich komplexe Abfragen mit FQL erstellte, begann ich, das Maß an Verständnis zu schätzen, das ich hatte, als ich sie ausführte. Während Firestore Ihnen einfach kostenlose Süßigkeiten gibt und hofft, dass Sie nie fragen, woher sie kommen, da es alle Bedenken (wie Leistung und, wichtiger: Kosten) abstrahiert.

Grundsätzlich verfügt FaunaDB über die Flexibilität einer NoSQL-Datenbank gepaart mit der Leistungsdämpfung, die man von einer relationalen SQL-Datenbank erwartet.

Wir werden gleich weitere Beispiele dafür sehen, wie und warum.

Hinzufügen von Dokumenten zu einer FaunaDB-Sammlung

Öffnen Sie Ihr FaunaDB-Dashboard und navigieren Sie zu Ihrer games-Sammlung.

Klicken Sie hier auf NEW DOCUMENT und fügen Sie die folgenden BioShock-Titel zu Ihrer Sammlung hinzu

{
  "title": "BioShock",
  "consoles": [
    "windows",
    "xbox_360",
    "playstation_3",
    "os_x",
    "ios",
    "playstation_4",
    "xbox_one"
  ],
  "release_date": Date("2007-08-21"),
  "metacritic_score": 96
}

{
  "title": "BioShock 2",
  "consoles": [
    "windows",
    "playstation_3",
    "xbox_360",
    "os_x"
  ],
  "release_date": Date("2010-02-09"),
  "metacritic_score": 88
}{
  "title": "BioShock Infinite",
  "consoles": [
    "windows",
    "playstation_3",
    "xbox_360",
    "os_x",
    "linux"
  ],
  "release_date": Date("2013-03-26"),
  "metacritic_score": 94
}

Wie bei anderen NoSQL-Datenbanken sind die Dokumente JSON-ähnliche Textblöcke mit Ausnahme einiger Fauna-spezifischer Objekte (wie Date, das im Feld „release_date“ verwendet wird).

Wechseln Sie nun zum Shell-Bereich und leeren Sie Ihre Abfrage. Fügen Sie Folgendes ein

Map(Paginate(Match(Index("all_games"))),Lambda("ref",Var("ref")))

Und klicken Sie auf die Schaltfläche „Run Query“. Sie sollten eine Liste von drei Elementen sehen: Verweise auf die Dokumente, die Sie gerade erstellt haben.

Klicken Sie im Shell-Bereich auf das Abfragefeld, löschen Sie es, fügen Sie die bereitgestellte Abfrage ein und klicken Sie auf „Run Query“.

Es ist schon etwas alt, aber hier ist, was die Abfrage tut.

Index("all_games") erstellt einen Verweis auf den all_games-Index, den Fauna automatisch für Sie generiert hat, als Sie Ihre Sammlung eingerichtet haben. Diese Standardindizes sind nach Referenz organisiert und geben Referenzen als Werte zurück. In diesem Fall verwenden wir die Match-Funktion auf dem Index, um eine Menge von Referenzen zurückzugeben. Da wir nirgendwo filtern, erhalten wir jedes Dokument in der ‚games‘-Sammlung.

Die von Match zurückgegebene Menge wird dann an Paginate übergeben. Diese Funktion fügt, wie Sie erwarten würden, Paginierungsfunktionalität hinzu (vorwärts, rückwärts, überspringen). Schließlich übergeben Sie das Ergebnis von Paginate an Map, das, ähnlich wie sein Software-Gegenstück, es Ihnen ermöglicht, eine Operation auf jedes Element in einer Menge auszuführen und ein Array zurückzugeben. In diesem Fall gibt es einfach ref (die Referenz-ID) zurück.

Wie bereits erwähnt, gibt der Standardindex nur Referenzen zurück. Die Lambda-Operation, die wir an Map übergeben haben, holt dieses ref-Feld aus jedem Eintrag in der paginierten Menge. Das Ergebnis ist ein Array von Referenzen.

Nachdem Sie eine Liste von Referenzen haben, können Sie die Daten hinter der Referenz mit einer anderen Funktion abrufen: Get.

Umschließen Sie Var("ref") mit einem Get-Aufruf und führen Sie Ihre Abfrage erneut aus, die dann so aussehen sollte

Map(Paginate(Match(Index("all_games"))),Lambda("ref",Get(Var("ref"))))

Anstelle eines Referenzarrays sehen Sie nun den Inhalt jedes Videospieldokuments.

Umschließen Sie Var("ref") mit einer Get-Funktion und führen Sie die Abfrage erneut aus.

Nachdem Sie nun eine Vorstellung davon haben, wie Ihre Spieldokumente aussehen, können Sie mit der Erstellung von REST-Aufrufen beginnen, beginnend mit einem POST.

Erstellen Sie eine serverlose POST-API-Anfrage

Ihr erster API-Aufruf ist unkompliziert und zeigt, wie Express in Kombination mit Cloud Functions es Ihnen ermöglicht, alle Routen über eine einzige Methode zu bedienen.

Fügen Sie dies unter der vorherigen (und makellosen) API-Anfrage hinzu

api.get(['/api/v1', '/api/v1/'], (req, res) => {...})
 
api.post(['/api/v1/games', '/api/v1/games/'], (req, res) => {
  let addGame = client.query(
    q.Create(q.Collection('games'), {
      data: {
        title: req.body.title,
        consoles: req.body.consoles,
        metacritic_score: req.body.metacritic_score,
        release_date: q.Date(req.body.release_date)
      }
    })
  )
  addGame
    .then(response => {
      res.status(200).send(`Saved! ${response.ref}`)
      return
    })
    .catch(reason => {
      res.error(reason)
    })
})

Bitte sehen Sie über die fehlende Eingabevalidierung für dieses Beispiel hinweg (alle Mitarbeiter müssen Eingaben bereinigen, bevor sie den Arbeitsraum verlassen).

Aber wie Sie sehen können, ist das Erstellen neuer Dokumente in FaunaDB kinderleicht.

Das q-Objekt fungiert als Abfrage-Builder-Schnittstelle, die eins zu eins mit FQL-Funktionen übereinstimmt (die vollständige Liste der FQL-Funktionen finden Sie hier).

Sie führen eine Create aus, übergeben Ihre Collection und fügen Datenfelder hinzu, die direkt aus dem Body der Anfrage stammen.

client.query gibt ein Promise zurück, dessen Erfolgsstatus einen Verweis auf das neu erstellte Dokument liefert.

Und um sicherzustellen, dass es funktioniert, geben Sie den Verweis an den Aufrufer zurück. Sehen wir es uns in Aktion an.

Testen Sie Firebase Functions lokal mit Postman und cURL

Verwenden Sie Postman oder cURL, um die folgende Anfrage an localhost:5000/api/v1/ zu stellen, um Halo: Combat Evolved zu Ihrer Spieleliste hinzuzufügen (oder welcher Halo auch immer Ihr Favorit ist, aber absolut nicht 4, 5, Reach, Wars, Wars 2, Spartan…)

$ curl https://:5000/api/v1/games -X POST -H "Content-Type: application/json" -d '{"title":"Halo: Combat Evolved","consoles":["xbox","windows","os_x"],"metacritic_score":97,"release_date":"2001-11-15"}'

Wenn alles richtig gelaufen ist, sollten Sie eine Referenz als Antwort auf Ihre Anfrage erhalten und ein neues Dokument in Ihrer FaunaDB-Konsole angezeigt werden.

Nachdem Sie nun einige Daten in Ihrer games-Sammlung haben, lernen wir, wie Sie sie abrufen können.

FaunaDB-Datensätze mit einer REST-API-Anfrage abrufen

Vorhin habe ich erwähnt, dass jede FaunaDB-Abfrage einen Index erfordert und dass Fauna Sie daran hindert, ineffiziente Abfragen durchzuführen. Da unsere nächste Abfrage nach Spielen gefiltert nach einer Spielekonsole sucht, können wir nicht einfach eine traditionelle `where`-Klausel verwenden, da diese ohne Index ineffizient sein könnte. In Fauna müssen wir zuerst einen Index definieren, der es uns erlaubt zu filtern.

Um zu filtern, müssen wir angeben, auf welche Begriffe wir filtern wollen. Und mit Begriffen meine ich die Felder des Dokuments, auf die Sie voraussichtlich suchen werden.

Navigieren Sie zu Indexes in Ihrer FaunaDB-Konsole und erstellen Sie einen neuen.

Nennen Sie ihn games_by_console, setzen Sie data.consoles als einzigen Begriff, da wir nach Konsolen filtern werden. Setzen Sie dann data.title und ref als Werte. Werte werden nach Bereich indiziert, aber sie sind auch nur die Werte, die von der Abfrage zurückgegeben werden. Indizes sind in diesem Sinne ein wenig wie Ansichten, Sie können einen Index erstellen, der eine andere Kombination von Feldern zurückgibt, und jeder Index kann eine andere Sicherheit haben.

Um den Anfrage-Overhead zu minimieren, haben wir die Antwortdaten (z. B. Werte) auf Titel und Referenz beschränkt.

Ihr Bildschirm sollte diesem ähneln

Unter Indizes erstellen Sie einen neuen Index namens games_by_console mit den oben genannten Parametern.

Klicken Sie auf „Speichern“, wenn Sie bereit sind.

Mit Ihrem vorbereiteten Index können Sie Ihren nächsten API-Aufruf entwerfen.

Ich habe mich entschieden, Konsolen als Pfad darzustellen, bei dem der Konsolenidentifikator der alleinige Parameter ist, z. B. /api/v1/console/playstation_3, nicht unbedingt Best Practice, aber auch nicht die schlechteste — komm schon.

Fügen Sie diese API-Anfrage zu Ihrer index.js-Datei hinzu

api.post(['/api/v1/games', '/api/v1/games/'], (req, res) => {...})
 
api.get(['/api/v1/console/:name', '/api/v1/console/:name/'], (req, res) => {
  let findGamesForConsole = client.query(
    q.Map(
      q.Paginate(q.Match(q.Index('games_by_console'), req.params.name.toLowerCase())),
      q.Lambda(['title', 'ref'], q.Var('title'))
    )
  )
  findGamesForConsole
    .then(result => {
      console.log(result)
      res.status(200).send(result)
      return
    })
    .catch(error => {
      res.error(error)
    })
})

Diese Abfrage sieht ähnlich aus wie die, die Sie in Ihrem SHELL verwendet haben, um alle Spiele abzurufen, aber mit einer leichten Modifikation. Diese Abfrage sieht ähnlich aus wie die, die Sie in Ihrem SHELL verwendet haben, um alle Spiele abzurufen, aber mit einer leichten Modifikation. Beachten Sie, wie Ihre Match-Funktion jetzt einen zweiten Parameter (req.params.name.toLowerCase()) hat, der der Konsolenidentifikator ist, der über die URL übergeben wurde.

Der Index, den Sie gerade erstellt haben, games_by_console, enthielt einen Begriff (das Konsolen-Array), der dem Parameter entspricht, den wir dem Match-Parameter übergeben haben. Grundsätzlich sucht die Match-Funktion nach der Zeichenfolge, die Sie als zweiten Argument übergeben, im Index. Das nächste interessante Stück ist die Lambda-Funktion. Ihre erste Begegnung mit Lambda enthielt eine einzelne Zeichenfolge als erstes Argument von Lambda: „ref.“

Der games_by_console-Index gibt jedoch zwei Felder pro Ergebnis zurück, die beiden Werte, die Sie zuvor beim Erstellen des Index angegeben haben (data.title und ref). Grundsätzlich erhalten wir eine paginierte Menge, die Tupel aus Titeln und Referenzen enthält, wir benötigen aber nur Titel. Falls Ihre Menge mehrere Werte enthält, ist der Parameter Ihrer Lambda eine Array. Der Array-Parameter oben (`[‘title’, ‘ref’]`) besagt, dass der erste Wert an die Variable text gebunden ist und der zweite an die Variable ref. text-Parameter. Diese Variablen können dann weiter in der Abfrage über Var(‚title‘) abgerufen werden. In diesem Fall wurden sowohl „title“ als auch „ref“ vom Index zurückgegeben, und Ihre Map- mit Lambda-Funktion durchläuft diese Liste von Ergebnissen und gibt einfach nur die Liste der Titel für jedes Spiel zurück.

In Fauna werden Abfragen zusammengesetzt, bevor sie ausgeführt werden. Wenn Sie var q = q.Match(q.Index('games_by_console'))) schreiben, enthält die Variable nur eine Abfrage, aber es wurde noch keine Abfrage ausgeführt. Erst wenn Sie die Abfrage mit client.query(q) zur Ausführung übergeben, wird sie ausgeführt. Sie können sogar JavaScript-Variablen in anderen Fauna FQL-Funktionen übergeben, um mit der Komposition von Abfragen zu beginnen. Dies ist ein großer Vorteil der Abfrage in Fauna im Vergleich zu den verketteten asynchronen Abfragen, die für Firestore erforderlich sind. Wenn Sie jemals sehr komplexe Abfragen in SQL dynamisch generiert haben, werden Sie auch die Komposition und die weniger deklarative Natur von FQL zu schätzen wissen.

Speichern Sie index.js und testen Sie Ihre API mit diesem

$ curl https://:5000/api/v1/xbox
{"data":["Halo: Combat Evolved"]}

Nett, oder? Aber Match gibt nur Dokumente zurück, deren Felder exakt übereinstimmen, was dem Benutzer, der nach einem Spiel sucht, dessen Titel er kaum noch kennt, nicht hilft.

Obwohl Fauna (noch) keine Fuzzy-Suche über Indizes bietet, können wir eine ähnliche Funktionalität bereitstellen, indem wir einen Index für alle Wörter in der Zeichenfolge erstellen. Oder wenn wir wirklich flexible Fuzzy-Suche wollen, können wir die Filter-Syntax verwenden. Beachten Sie, dass dies aus Leistungs- oder Kostengründen nicht unbedingt eine gute Idee ist... aber hey, wir tun es, weil wir es können und weil es ein großartiges Beispiel dafür ist, wie flexibel FQL ist!

FaunaDB-Dokumente nach Suchzeichenfolge filtern

Der letzte API-Aufruf, den wir erstellen werden, ermöglicht es Benutzern, Titel nach Namen zu finden. Gehen Sie zurück in Ihre FaunaDB-Konsole, wählen Sie INDEXES und klicken Sie auf NEW INDEX. Nennen Sie den neuen Index games_by_title und lassen Sie die Terms leer, Sie werden sie nicht benötigen.

Anstatt sich auf Match zu verlassen, um den Titel mit der Suchzeichenfolge zu vergleichen, werden Sie jeden Eintrag in Ihrer Sammlung durchlaufen, um Titel zu finden, die die Suchanfrage enthalten.

Erinnern Sie sich, wie wir erwähnt haben, dass Indizes ein wenig wie Ansichten sind. Um nach Titel zu filtern, müssen wir `data.title` als einen Wert einschließen, der vom Index zurückgegeben wird. Da wir Filter auf die Ergebnisse von Match anwenden, müssen wir sicherstellen, dass Match den Titel zurückgibt, damit wir damit arbeiten können.

Fügen Sie data.title und ref als Values hinzu. Vergleichen Sie Ihren Bildschirm mit meinem

Erstellen Sie einen weiteren Index namens games_by_title mit den oben genannten Parametern.

Klicken Sie auf „Speichern“, wenn Sie bereit sind.

Zurück in index.js, fügen Sie Ihren vierten und letzten API-Aufruf hinzu

api.get(['/api/v1/console/:name', '/api/v1/console/:name/'], (req, res) => {...})
 
api.get(['/api/v1/games/', '/api/v1/games'], (req, res) => {
  let findGamesByName = client.query(
    q.Map(
      q.Paginate(
        q.Filter(
          q.Match(q.Index('games_by_title')),
          q.Lambda(
            ['title', 'ref'],
            q.GT(
              q.FindStr(
                q.LowerCase(q.Var('title')),
                req.query.title.toLowerCase()
              ),
              -1
            )
          )
        )
      ),
      q.Lambda(['title', 'ref'], q.Get(q.Var('ref')))
    )
  )
  findGamesByName
    .then(result => {
      console.log(result)
      res.status(200).send(result)
      return
    })
    .catch(error => {
      res.error(error)
    })
})

Tief durchatmen, denn ich weiß, dass es viele Klammern gibt (Lisp-Programmierer werden das lieben), aber sobald Sie die Komponenten verstanden haben, ist die vollständige Abfrage ziemlich leicht verständlich, da sie im Grunde wie das Programmieren ist.

Beginnend mit der ersten neuen Funktion, die Sie entdecken, Filter. Filter ist wieder sehr ähnlich zu dem Filter, dem Sie in Programmiersprachen begegnen. Er reduziert ein Array oder eine Menge auf eine Teilmenge basierend auf dem Ergebnis einer Lambda-Funktion.

In diesem Filter schließen Sie alle Spieletitel aus, die die Suchanfrage des Benutzers nicht enthalten.

Sie tun dies, indem Sie das Ergebnis von FindStr (eine String-Find-Funktion ähnlich wie JavaScript indexOf) mit -1 vergleichen. Ein nicht-negativer Wert hier bedeutet, dass FindStr die Suchanfrage des Benutzers in einer kleingeschriebenen Version des Spieletitels gefunden hat.

Und das Ergebnis dieses Filters wird an Map übergeben, wo jedes Dokument abgerufen und in die endgültige Ergebnisausgabe eingefügt wird.

Jetzt denken Sie vielleicht offensichtlich: Das Vergleichen von Zeichenfolgen über vier Einträge hinweg ist billig, 2 Millionen…? Nicht wirklich.

Dies ist eine ineffiziente Methode, um eine Textsuche durchzuführen, aber sie erfüllt ihren Zweck für dieses Beispiel. (Vielleicht hätten wir dafür ElasticSearch oder Solr verwenden sollen?) Nun, in diesem Fall ist FaunaDB perfekt als zentrales System, um Ihre Daten sicher aufzubewahren und diese Daten an eine Suchmaschine zu speisen, dank des temporalen Aspekts, der es Ihnen erlaubt, Fauna zu fragen: „Hey, gib mir die letzten Änderungen seit Zeitstempel X?“. Sie könnten also ElasticSearch daneben einrichten und FaunaDB (bald werden sie Push-Nachrichten haben) verwenden, um es bei Änderungen zu aktualisieren. Wer das einmal gemacht hat, weiß, wie schwer es ist, eine solche externe Suche aktuell und korrekt zu halten, FaunaDB macht es ziemlich einfach.

Testen Sie die API, indem Sie nach „Halo“ suchen

$ curl https://:5000/api/v1/games?title=halo

Das dürfen Sie auf keinen Fall vergessen: Eine Firebase-Optimierung

Viele Code-Snippets von Firebase Cloud Functions treffen eine furchtbar falsche Annahme: dass jede Funktionsausführung unabhängig von einer anderen ist.

In Wirklichkeit können Firebase Function-Instanzen für kurze Zeit „heiß“ bleiben, bereit, nachfolgende Anfragen auszuführen.

Das bedeutet, dass Sie Ihre Variablen „lazy“ laden und die Ergebnisse cachen sollten, um die Berechnungszeit (und die Kosten!) während Spitzenaktivitäten zu reduzieren. Hier ist, wie:

let functions, admin, faunadb, q, client, express, cors, api
 
if (typeof api === 'undefined') {
... // dump the existing code here
}
 
exports.api = functions.https.onRequest(api)

Stellen Sie Ihre REST-API mit Firebase Functions bereit

Stellen Sie schließlich sowohl Ihre Funktionen als auch Ihre Hosting-Konfiguration mit dem Befehl firebase deploy von Ihrer Shell aus bei Firebase bereit.

Ohne einen benutzerdefinierten Domainnamen beziehen Sie sich auf Ihre Firebase-Subdomain, wenn Sie API-Anfragen stellen, z. B. https://{projekt-name}.firebaseapp.com/api/v1/.

Was als Nächstes?

FaunaDB hat mich zu einem gewissenhaften Entwickler gemacht.

Wenn ich andere schemalose Datenbanken verwende, beginne ich mit guten Absichten und behandle Dokumente so, als hätte ich sie mit einem DDL instanziiert (strikte Typen, Versionsnummern, das ganze Programm).

Während das mich eine Weile lang organisiert hält, fallen bald Standards zugunsten von Geschwindigkeit und meine Dokumente zerfallen: übrig bleiben veraltete Formatierung und Zombie-Daten.

Indem es mich zwingt, darüber nachzudenken, wie ich meine Daten abfrage, welche Indizes ich benötige und wie ich diese Daten am besten bearbeite, bevor sie an meinen Server zurückkehren, bleibe ich mir meiner Dokumente bewusst.

Um mir zu helfen, für immer organisiert zu bleiben, hilft mir mein Katalog (in der FaunaDB-Konsole) von Indizes, alles im Auge zu behalten, was meine Dokumente bieten.

Und durch die Einbindung dieser breiten Palette von arithmetischen und sprachlichen Funktionen direkt in die Abfragesprache ermutigt mich FaunaDB, die Effizienz zu maximieren und meine Datenspeicherrichtlinien genau zu überwachen. Angesichts des erschwinglichen Preismodells würde ich eher 10.000+ Datenmanipulationen auf den Servern von FaunaDB durchführen als auf einer einzelnen Cloud Function.

Aus diesen und weiteren Gründen ermutige ich Sie, einen Blick auf diese Funktionen zu werfen und die anderen leistungsstarken Funktionen von FaunaDB in Betracht zu ziehen.