Deno – Einführung & Entwicklung einer einfachen REST API

Keine Kommentare

Was ist Deno?

Deno Logo

Deno (ein Anagramm von „Node“ 🤯), ist eine JavaScript und TypeScript Runtime, die
seit Mai 2020 in der Version 1.0 verügbar ist. Deno wurde von Ryan Dahl, dem ursprünglichen Entwickler von Node.js, entwickelt und soll einige konzeptionelle
Einschränkungen und Mängel von Node.js beheben.

Deno ist Open-Source (MIT) in Rust programmiert, basiert auf der V8 JavaScript Engine und inkludiert einen
TypeScript Compiler. Dieser erlaubt es neben JavaScript auch direkt TypeScript Programme über die Kommandozeile auszuführen.

Was ist das besondere an Deno?

Deno soll einige bekannte Mängel von Node.js beheben. Dazu zählen unter anderem:

Ein schlecht konzipiertes Modulsystem mit einem stark zentralisiertem
Verteilungsmechanismus.

  • Deno setzt hier folgendermaßen an:
    • Deno verwendet ECMAScript Module (anstelle der bei Node.js üblichen
      CommonJS Module)
    • Deno Module können überall gehostet werden – ein zentrales Repository (wie
      bei Node.js das npm Registry) gibt es nicht. Module können direkt über
      ihre URL importiert werden und werden immer lokal gecached und kompiliert.
      Außerdem werden Module nicht auf neuere Versionen aktualisiert, außer wenn
      man das ausdrücklich angibt.

Viele veraltete Legacy-APIs, die aber weiterhin unterstützt werden
müssen.

  • Das macht Deno anders:
    • Module via require() zu importieren wird nicht unterstützt,
      außschließlich der ECMAScript Modul import Syntax wird
      unterstützt
    • Deno verwendet moderne ECMAScript-Funktionen in allem internen APIs und
      Standardbibliotheken. Alle asynchronen Funktionen nutzen Promises – eine
      auf Callbacks basierende API wie bei Node.js gibt es nicht.

Fehlende Berücksichtigung von Security-Aspekten.

  • Deno verhält sich hier anders, denn:
    • Standardmäßig dürfen Programme, die mit Deno ausgeführt werden, nicht auf
      die Festplatte, das Netzwerk, auf Unterprozesse oder auf
      Umgebungsvariablen zugreifen. Dies muss explizit über
      Kommandozeilenargumente erlaubt werden (z.B.: --allow-read,
      --allow-net=api.codecentric.com, etc.)
    • Bei unbehandelten Fehlern stürzt die Deno Runtime (im Gegensatz zu
      Node.js) immer direkt ab. Dadurch werden nicht vorhersehbare Ereignisse
      verhindert, die auftreten können, wenn ein Programm nach einem
      unbehandelten Fehlern weiterläuft.

Zudem soll Deno out-of-the-box eine sehr gute Developer-Experience bieten, u.A.
durch:

  • TypeScript Support
  • Eingebautes Tooling:

    • Unit Testing (deno test)
    • Code Formatting (deno fmt)
    • Linting (deno lint)
    • Dependency Checking (deno info)
  • Eingebauter live-reload File-Watcher (seit Deno 1.4):
    deno run --watch, muss aktuell zusätzlich noch mit der
    --unstable Flag ausgeführt werden, da das File-Watcher-Feature
    noch nicht stabil ist
  • Eingebautes Bundling (via deno bundle) um alle verwendeten
    Module und den eigenen Code in eine einzige JavaScript Datei zu bündeln

Projekt-Setup & Hello World

Doch genug der Theorie, wie startet man am besten mit der Deno Entwicklung?

  1. Deno installieren: Die Anleitungen dazu findet ihr auf der offiziellen
    Homepage von Deno
  2. Gehostetes „Hello-World“ Modul ausführen

    • Das „Hello-World“ Modul ist hier gehostet:
      https://deno.land/std@0.69.0/examples/welcome.ts
    • Tipp: clickt auf den Modul-Link, alle bei Deno-Land gehostete Module
      können auch direkt im Browser inspiziert werden!

    • Programm über die Kommandozeile ausführen:
    • deno run https://deno.land/std@0.69.0/examples/welcome.ts
    • Ergebnis:
    • Download https://deno.land/std@0.69.0/examples/welcome.ts
      Check https://deno.land/std@0.69.0/examples/welcome.ts
      Welcome to Deno 🦕
  3. Eigenes "Hello-World" Modul ausführen
    • Legt eine hello.ts Datei an
    • Befüllt sie mit eurem Hello World Code, z.B.
    • console.log("Hello codecentric 👋");
    • Führt sie mit deno run hello.ts aus
    • Ergebnis:
    • Check file:///path/to/file/hello.ts
      Hello codecentric 👋

Eine einfache REST API mit Deno & Oak

Um etwas über das Hello-World Beispiel hinaus einen Einblick in den aktuellen Entwicklungsstand von Deno zu bekommen, wollen wir in diesem Abschnitt eine sehr einfache REST API bauen. Hierzu nutzen wir das Framework oak. Oak ist ein Middleware-Framework für das in Deno inkludierte http Modul. Leser\*innen, die bereits mit Node.js und Express oder Koa gearbeitet haben, dürften sich im folgenden Code wie Zuhause fühlen 😉.
  • Zuerst installieren wir oak als dependency via npm install ..
  • Ach nein, halt! Da war ja was...
  • In Deno werden alle dependencies einfach in der TypeScript Datei, in der sie verwendet werden, direkt über eine URL importiert!
  • Wir legen uns also eine Datei app.ts an, und in diese kommt der folgende Code:
  • Zu sehen ist, dass wir nun direkt über eine URL auf das im Deno-Land gehostete Modul oak zugreifen. Innerhalb der URL wird auch direkt die verwendete Versionsnummer festgelegt
  • Jetzt führen wir diese Datei einfach via deno run app.ts aus und Voilá!
  • error: Uncaught PermissionDenied: network access to "localhost:1337", run again with the --allow-net flag
  • Dieser Fehler demonstriert das am Anfang beschriebene Sicherheitskonzept, welches sehr eng in Deno integriert ist, sehr gut. Wie bereits oben erwähnt dürfen Applikationen nicht ohne weiteres auf das Netzwerk zugreifen.
  • Um dies zu erlauben, müssen wir das in der Fehlernachricht angegebene Kommandozeilen-Argument --allow-net dem deno run Befehl mitgeben
  • Wir führen als deno run --allow-net app.ts aus und sehen Listening on port 1337.... Fazit: Die Applikation läuft.
Employee UML

Das Datenmodell, für welches wir in diesem Blogpost eine REST API bauen wollen,
sieht wie folgt aus:

Wir erstellen jetzt also eine employeeModel.ts Datei und fügen sowohl diese Interface Spezfikiation als auch eine Grundmenge an "bereits existierenden Employees" hinzu. Die API in diesem Beispiel wird alle Daten in Memory halten und nichts persistieren, wir werden also keine Verbindung zu einer Datenbank aufbauen. Nun sieht die employeeModel.ts wie folgt aus:

Desweiteren brauchen wir einen employeeController.ts, in dem wir die verschiedenen HTTP Methoden, die unsere API unterstützen soll, implementieren.

Wir fangen mit einer Methode an, die eine Liste alle Mitarbeiter ausgibt. Auch hier wird wieder das oak Modul direkt über eine URL importiert. Desweiteres importieren wir die Liste aller Employees vom gerade erstellten lokal liegenden Modul employeeModel.ts. Achtung: die Dateiendung bei Imports ist wichtig!

In der app.ts nutzen wir nun den oak-Router, um alle GET-Requests auf localhost:1337/employees mit der getAllEmployees Methode aufzulösen. Hierzu importieren wir zuerst den Controller und registrieren danach die Methode im Router.

Jetzt können wir die Anwendung erneut starten, und danach mit einem HTTP Client unserer Wahl einen GET-Request gegen localhost:1337/employees abfeuern. Wir sehen, der Endpunkt liefert die zuvor im employeeModel.ts definierten Employees aus:

Get all employees

Auf die selbe Art und Weise implementieren wir jetzt für jede HTTP-Methode eine Funktion im employeeController.ts:

GET für einzelne Employees
- via ID in der URL (z.B. localhost:1337/employees/m-11)

POST um neue Employees anzulegen
- mit JSON im POST-Body

PUT um einzelne Employees zu updaten
- via ID in der URL
- Daten im JSON im POST-Body

DELETE um vorhandene Employees zu löschen
- via ID in der URL

Zusätlich implementieren wir noch rudimentäres Fehler-Handling falls Employees nicht gefunden werden können oder der gelieferte JSON-Body nicht dekodiert werden konnte. Am Ende sieht unser employeeController.ts dann wie folgt aus:

Nun können wir in unserer app.ts die neu im employeeController.ts implementierten Funktionen importieren und mit den entsprechenden HTTP-Methoden auf dem /employees Endpunkt verknüpfen:

Jetzt können wir über unseren HTTP Client beliebig Employees anzeigen, anlegen, aktualisieren und löschen:

Get single employee

Update single employee

Damit sind wir am Ende unserer Beispiel-Implementierung angekommen. Man sieht, die Implementierung an sich unterscheidet sich nicht großartig von einem Node.js Projekt, welches TypeScript nutzt - die Unterschiede sind eher im Dependency Management und in der Kommandozeile zu finden. Es ist wichtig anzumerken, dass wir bei unserer Beispiel-Implementierung einige Themen (die in einer "vollständigen" REST-API eigentlich enthalten sind) aus Platzgründen nicht berücksichtigt haben, wie z.B.:

  • Anschluss an eine Datenbank bzw. Nutzung eines ORM. Hier gibt es
    aufgrund des jungen Alters von Deno noch kein "best-practice"
    Modul, aber Projekte wie denodb und cotton sehen auch aktuell schon vielversprechend aus.
  • Logging. Deno bietet hier mit dem Log Modul eine in der Standardbibliothek enthaltene Lösung an.
  • Validierung der im POST / PUT Request gesendeten Daten. Hier gibt es verschiedene existierende Module, wie z.B. validator-deno, computed_types oder validasaur.
  • Tests. Auch hier bietet Deno mit dem deno test Kommandozeilen-Befehl und dem Test-Syntax in folgender Form eine eingebaute Lösung an:
  • Deno.test("simple addition test", () => {
        const x = 1 + 2;
        assertEquals(x, 3);
    });

Fazit

Deno ist auf jeden Fall ein technisch spannendes Projekt mit interessanten neuen Ansätzen, die sich meiner Meinung nach noch bewähren müssen. Deno ist einfach zu benutzen und durch den eingebaute TypeScript Support macht es Spaß, damit zu programmieren. Den Stimmen die behaupten, Deno wäre ein besseres Node.js, kann ich allerdings (zum aktuellen Zeitpunkt noch) nicht zustimmen.

Das junge Alter der Runtime bringt viele kleine Probleme & Instabilitäten sowie ein Mangel an Modulen, die ohne Probleme genutzt werden können (die meisten npm Module können nicht ohne weiteres genutzt werden). Deswegen ist Deno aus meiner Sicht aktuell für Produktivanwendungen eher ungeeignet.

Desweiteren bin ich gespannt, wie sich der Ansatz von "jede Datei importiert Module direkt über URLs" im Laufe der Zeit schlägt - gerade in großen Projekten mit vielen Abhängigkeiten könnte das schnell sehr unübersichtlich werden. Ein oft vorgeschlagenes deps.ts file, welches alle Abhängigkeiten importiert und re-exportiert, sodass lokale Module nur deps.ts importieren müssen, wirkt für mich wieder sehr ähnlich zur bekannten package.json - von der Deno ja eigentlich wegkommen wollte.

Ich persönlich werden Deno zukünftig sicherlich für kleinere Skripte nutzen, für die ich bisher meistens Node.js genutzt habe. Hier ist der eingebaute TypeScript Support und der direkte import von Abhängigkeiten via URL sehr praktisch und macht das skripten durchaus angenehmer.

Felix Magnus

Felix arbeitet als IT-Consultant mit dem Schwerpunkt Webentwicklung für codecentric. Desweiteren sind Themen wie Testing, DevOps und CI / CD ein wichtiger Bestandteil seines täglichen Arbeitens.

Über 1.000 Abonnenten sind up to date!

Die neuesten Tipps, Tricks, Tools und Technologien. Jede Woche direkt in deine Inbox.

Kostenfrei anmelden und immer auf dem neuesten Stand bleiben!
(Keine Sorge, du kannst dich jederzeit abmelden.)

* Hiermit willige ich in die Erhebung und Verarbeitung der vorstehenden Daten für das Empfangen des monatlichen Newsletters der codecentric AG per E-Mail ein. Ihre Einwilligung können Sie per E-Mail an datenschutz@codecentric.de, in der Informations-E-Mail selbst per Link oder an die im Impressum genannten Kontaktdaten jederzeit widerrufen. Von der Datenschutzerklärung der codecentric AG habe ich Kenntnis genommen und bestätige dies mit Absendung des Formulars.

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.