© Allen Paul Photography/Shutterstock.com
Run, Deno, Run – Teil 2

Erste Schritte mit Oak und REST


Im ersten Teil haben wir Deno kennengelernt, nun wollen wir anspruchsvollere Anwendungen damit bauen. Dafür werfen wir einen Blick auf Oak, ein Middleware-Framework für Deno und zeigen dann, wie man die Anwendung um REST ergänzt.

Oak ist momentan das beliebteste Werkzeug, um Webanwendungen mit Deno zu erstellen. Das Middleware Framework tritt bei Deno [1] an die Stelle, die Koa [2] und Express [3] im Fall von Node.js einnehmen. Wenn wir von Webanwendungen mit Deno sprechen, dann werden diese in der Regel nicht über den Browser aufgerufen – abgesehen vom serverseitigen Rendern einer Frontend-Anwendung. Stattdessen erstellen Sie mit Oak Serveranwendungen in Deno als Backend-Applikation – quasi die Schnittstelle zwischen Ihrer Frontend-Anwendung und einer potenziellen Datenbank oder anderen Datenquellen (beispielsweise REST APIs, GraphQL-APIs).

Hier zur besseren Einordnung eine Liste von möglichen Technologiestacks zum Erstellen von Client-Server-Architekturen:

  • React.js (Frontend) + Oak (Backend) + PostgreSQL (Datenbank)

  • Vue.js (Frontend) + Oak (Backend) + MongoDB (Datenbank)

  • Angular (Frontend) + Oak (Backend) + Neo4j (Datenbank)

Tauschen Sie Oak [4] mit anderen Backend Frameworks genauso aus, wie Sie React.js mit Vue.js und Angular.js ersetzen, wenn es um Frontend-Anwendungen geht. Das Deno-Ökosystem bietet nicht nur eine, sondern verschiedene Lösungen, die jede für sich ihre Stärken und Schwächen aufweist. Für unser Beispiel verwenden wir einen Oak-Server, da dies die beliebteste Wahl beim Erstellen von JavaScript-Backend-Anwendungen mit Deno ist.

Oak in Deno

Sehen wir uns das praktisch an und binden Oak in eine Deno-Anwendung ein. Öffnen Sie dazu das im ersten Teil erstellte Deno-Projekt oder erstellen Sie ein neues. Legen Sie dann eine TypeScript-Datei mit dem Namen src/server.ts an, in die Sie den Code aus Listing 1 kopieren. Der erledigt folgende Aufgaben: Er importiert Oak, erstellt eine Instanz einer Oak-Anwendung und ruft so einen Oak-Server auf. Ausführbar ist dieser Code erst nach dem nächsten Schritt, da bisher die Middleware fehlt. 'deno run --allow-net server.ts' würde Ihnen mit 'throw new TypeError(„There is no middleware to process requests.“); ' antworten.

Listing 1

import { Application } from 'https://deno.land/x/oak/mod.ts'; const port = 8000; const app = new Application(); app.addEventListener('listen', () => { console.log('Listening on localhost:${port}'); }); await app.listen({ port });

Wichtig ist, dass Sie den Event Listener (addEventListener) vor den eigentlichen Aufruf zum Mitverfolgen (listen) stellen, da sonst die Ereignisbehandlungsroutine niemals aufgerufen wird. Alles, was nach dem Start Ihrer Oak-Anwendung passiert, wird in die Rückruffunktion [5] von addEventListener aufgenommen. Die Methode listen verwendet als ersten Parameter ein Konfigurationsobjekt mit dem Port [6], das wir für die laufende Anwendung als Objekt mit Hilfe der seit ECMAScript 2015 möglichen Kurzschreibweisen für Eigenschaftsnamen [7] initialisieren. Deshalb ist es nach dem Aufruf des URI http://localhost: 8000 im Browser verfügbar.

Eine Oak-Anwendung verfügt über zwei Methoden: listen und use. Während Erstere den Server aufruft und Anforderungen mit Hilfe von registrierter Middleware verarbeitet, richtet die use-Methode sie ein. Das sehen wir uns ebenfalls praktisch an, indem wir eine elementare Middleware mit Oak aufbauen (Listing 2).

Listing 2

import { Application } from 'https://deno.land/x/oak/mod.ts'; const port = 8000; const app = new Application(); app.use((ctx) => { ctx.response.body = 'Hello Deno'; }); app.addEventListener('listen', () => { console.log(`Listening on localhost:${port}`); }); await app.listen({ port });

Diese simple Middleware verarbeitet als Funktion alle eingehenden Anforderungen für den Oak-Server. Probieren Sie es jetzt selbst aus, indem Sie deno run --allow-net server.ts in der Befehlszeile eingeben und http://localhost:8000 in Ihrem Browser aufrufen. Falls Sie das Beispiel genauso übernommen haben, werden Sie mit dem Text Hello Deno im Browser begrüßt, und in der Konsole finden Sie die Information Listening on localhost:8000 vor.

Kontext in Oak

Der Kontext in Oak ist nichts anderes als die eigentliche Anforderung, die die Middleware durchläuft. Im Code sehen Sie es oft als context oder ctx. Im vorherigen Codebeispiel haben wir das Kontextobjekt von Oak verwendet, um einen Text über unseren Browser zurückzugeben, indem wir den body des Antwortobjekts des Kontexts verwenden:

... app.use((ctx) => { ctx.response.body = 'Hello Deno'; }); ...

Das ist eine der einfachsten Verwendungen in einer Oak Middleware. Der Kontext enthält mehrere nützliche Eigenschaften. Beispielsweise haben Sie mit ctx.request Zugriff auf die aktuell vom Client eingehende Anforderung, während Sie gleichzeitig entscheiden, was mit ctx.response zurückgegeben wird. Im weiteren Verlauf dieser Einführung erfahren Sie, wie Sie den Kontext für verschiedene Anwendungsfälle verwenden.

Middleware in Oak

Im Wesentlichen handelt es sich bei jeder Oak-Anwendung nur um eine Reihe von Funktionsaufrufen. Wenn es mehr als eine Middleware gibt, ist es wichtig, verstanden zu haben, wie die Reihenfolge des Aufrufstapels festgelegt wird. Sehen wir uns als Erstes ein übersichtliches Beispiel an. Wir geben beim Besuch der Anwendung im Browser die HTTP-Methode und den URL der eingehenden Anforderung in der Befehlszeile aus (Listing 3).

Listing 3

import { Application } from 'https://deno.land/x/oak/mod.ts'; const port = 8000; const app = new Application(); app.use((ctx) => { console.log(HTTP ${ctx.request.method} on ${ctx.request.url}'); ctx.response.body = 'Hello Deno'; }); app.addEventListener('listen', () => { console.log(`Listening on localhost:${port}`); }); await app.listen({ port });

In der Befehlszeile sehen Sie den Eintrag HTTP GET on http://localhost:8000/. Jedes Mal, wenn ein Benutzer den URL in einem Browser öffnet, wird eine HTTP-GET-Methode für den Webserver aufgerufen. In unserem Fall übergibt die HTTP-Anforderung den Text Hello Deno an den Browser. Das ist unkompliziert. Was passiert aber, wenn wir zwei Middlewares anstelle von einer verwenden? Beispielsweise so, wie in Listing 4.

Listing 4

import { Application } from 'https://deno.land/x/oak/mod.ts'; const port = 8000; const app = new Application(); app.use((ctx) => { console.log('HTTP ${ctx.request.method} on ${ctx.request.url}'); }); app.use((ctx) => { console.log('returning a response ...'); ctx.response.body = 'Hello Deno'; }); app.addEventListener('listen', () => { console.log('Listening on localhost:${port}'); }); await app.listen({ port });

Die Befehlszeile protokolliert zwar HTTP GET on http://localhost:8000/, ignoriert aber returning a response ... Oak stoppt, nachdem die erste Middleware aufgerufen wurde. Um von einer Middleware zur nächsten zu wechseln, verwenden wir async/await mit der next-Funktion, wie in Listing 5 zu sehen.

Listing 5

... app.use(async (ctx, next) => { console.log('HTTP ${ctx.request.method} on ${ctx.request.url}'); await next(); }); app.use((ctx) => { console.log('returning a response ...'); ctx.response.body = 'Hello Deno'; }); ...

Die Ausgabe in der Befehlszeile sollte nun so aussehen:

HTTP GET on http://localhost:8000/ returning a response ...

Die Reihenfolge der Middlewares lässt sich nun verändern, indem Sie den Aufruf von next an eine andere Position verschieben (Listing 6).

Listing 6

... app.use(async (ctx, next) => { await next(); console.log('HTTP ${ctx.request.method} on ${ctx.request.url}'); }); app.use((ctx) => { console.log('returning a response ...'); ctx.response.body = 'Hello Deno'; }); ...

Überprüfen Sie die Ausgabe in der Befehlszeile. Dort sehen Sie nun eine andere Reihenfolge:

returning a response ... HTTP GET on http://localhost:8000/

Grundsätzlich ist next die nächste Middleware in der aufgerufenen Liste. Wenn next vor der eigentlichen Implementierungslogik auftritt, in der es verwendet wird (wie im letzten Beispiel), wird die nächste Middleware vor der aktuellen aufgerufen. Eine Middleware-Funktion kann, da es sich um eine Funktion handelt, als solche extrahiert und in Ihrer Deno-Anwendung als Middleware wiederverwendet werden. Eine Beispielimplementierung zeigt der Code in Listing 7.

Listing 7

import { Application, Context } from 'https://deno.land/x/oak/mod.ts'; const port = 8000; const app = new Application(); const logging = async (ctx: Context, next: Function) => { console.log('HTTP ${ctx.request.method} on ${ctx.request.url}'); await next(); }; app.use(logging); app.use((ctx) => { console.log('returning a response ...'); ctx.response.body = 'Hello Deno'; }); app.addEventListener('listen', () => { console.log('Listening on localhost:${port}'); }); await app.listen({ port });

Unterschiedliche abstrakte Bibliotheken sind für Oak verfügbar. Mit der use-Methode aktivieren wir Drittanbietermiddleware. Wenn Sie Deno für größere Projekte verwenden, werden Sie diese Möglichkeit zu schätzen wissen.

Routen in Oak

Wir verwenden Routen in Webanwendungen für das Backend, um URIs der Middleware zuzuordnen. Diese URIs stellen beispielsweise eine Textnachricht, eine HTML-Seite oder Daten in JSON über das REST API oder GraphQL bereit. In einer größeren Anwendung bedeutet dies, dass mehrere Routen vorhanden sind, die verschiedenen URIs zugeordnet sind. In Oak ist die Router-Middleware alles, was für Routen benötigt wird. Sie sind nichts anderes als eine weitere Abstraktion. Lassen Sie uns mit Oaks Router eine Route einrichten (Listing 8).

Listing 8

import { Application, Router } from 'https://deno.land/x/oak/mod.ts'; const port = 8000; const app = new Application(); const router = new Router(); router.get('/', (ctx) => { ctx.response.body = 'Hello Deno'; }); app.use(router.routes()); app.use(router.allowedMethods()); app.addEventListener('listen', () => { console.log('Listening on localhost:${port}'); }); await app.listen({ port });

Die Route im vorhergehenden Beispiel zeigt auf das Stammverzeichnis ('/') Ihrer Domain. Im Browser rufen Sie diese beispielsweise mit http://localhost:8000/ oder http://localhost:8000 auf – ohne den abschließenden Schrägstrich. Öffnen Sie nach dem Start der Deno-Anwendung den URI im Browser, um die Ausgabe zu überprüfen. Dort finden Sie jetzt den Text Hello Deno vor. Es gibt ein paar Fallstricke für die Router-Middleware. Beispielsweise ist diese für mehr als einen URI verwendbar, wie Listing 9 zeigt.

Listing 9

... router .get('/', (ctx) => { ctx.response.body = 'Hello Deno'; }) .get('/1', (ctx) => { ctx.response.body = 'Hello Deno 1'; }) .get('/2', (ctx) => { ctx.response.body = 'Hello Deno 2'; }); ...

Wenn Sie die laufende Anwendung im Browser besuchen, ist es möglich, zu jedem Pfad zu navigieren, um verschiedene Texte/Ansichten aufzurufen. Nutzen Sie deshalb mehr als einen Oak-Router, um diese innerhalb der Anwendung zu gruppieren (Listing 10).

Listing 10

... const routerOne = new Router(); routerOne.get('/1', (ctx) => { ctx.response.body = 'Hello Deno 1'; }); const routerTwo = new Router(); routerTwo.get('/2', (ctx) => { ctx.response.body = 'Hello Deno 2'; }); app.use(routerOne.routes()); app.use(routerOne.allowedMethods()); app.use(routerTwo.routes()); app.use(routerTwo.allowedMethods()); ...

Soweit zum einführende Oak-Beispiel, das Folgendes verdeutlicht: Im Wesentlichen handelt es sich bei jeder Oak-Anwendung um eine Reihe von Routing- und Middleware-Funktionsaufrufen. Wir haben uns die Verwendung von einer oder mehreren Routen angesehen. Zum Aktivieren dieser oder anderer Dienstprogramme (beispielsweise Protokollierung) haben wir Funktionsaufrufe verwendet. Der Quellcode zu diesem Abschnitt ist unter [8] zu finden. Middleware und Routing haben Zugriff auf das Oak-Kontextobjekt. A...

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

Angebote für Teams

Für Firmen haben wir individuelle Teamlizenzen. Wir erstellen Ihnen gerne ein passendes Angebot.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang