https://d226lax1qjow5r.cloudfront.net/blog/blogposts/voice-chat-with-vue-and-express-dr/Elevate_ConversationVueJS-1.png

Erstellen einer Voice Chat Application mit Vue.js und Express

Zuletzt aktualisiert am April 30, 2021

Lesedauer: 10 Minuten

Das Hinzufügen eines Dienstes zu einer komplexen Webanwendung kann sich als schwierig erweisen, wenn es darum geht, diesen Dienst zu pflegen. Dies gilt umso mehr, wenn der Dienst eine Benutzerschnittstellenkomponente hat. Mit der Nexmo API können Sie einen browserinternen Voice-Chat erstellen, der die Grundlage für eine Vielzahl von Kommunikationsanwendungen bildet. Aber selbst die Organisation der Teile dieser grundlegenden Benutzeroberfläche kann schwierig sein. Komponenten wie die in Vue.js machen dies einfacher, indem sie ein Muster für die Templates, das Styling und das UI-Scripting bereitstellen, das eine einzelne UI-Komponente benötigen kann. Ein Express-Server, der mit den Nexmo-Tools verbunden ist, bietet eine leichtgewichtige Full-Stack-Lösung, die dank der Trennung von Belangen an jede reale Architektur angepasst werden kann.

Es gibt viele Möglichkeiten, wie man eine Anwendung mit Vue strukturieren kann. Für dieses Tutorial werde ich einen Remix eines Glitch-Projekt remixen, das relativ wenig Gerüst bietet, aber man kann auch ein Starterprojekt wählen, das von der Vue CLI oder eine Drittanbieter-Bibliothek die spezielle Funktionen wie Server-Side Rendering bietet. Da Ihr Code sowohl auf Vue als auch auf Express basieren wird, ist die einzige Voraussetzung, dass Ihr Setup beide enthält.

Vonage API-Konto

Um dieses Tutorial durchzuführen, benötigen Sie ein Vonage API-Konto. Wenn Sie noch keines haben, können Sie sich noch heute anmelden und mit einem kostenlosen Guthaben beginnen. Sobald Sie ein Konto haben, finden Sie Ihren API-Schlüssel und Ihr API-Geheimnis oben auf dem Vonage-API-Dashboard.

Hinzufügen von Nexmo zu Ihrem Projekt

Um eine Unterhaltung über den Browser zu erstellen, müssen Sie sowohl den Nexmo Client und Server Pakete installieren. Da der Benutzer Daten vom Client aus senden wird, müssen Sie auch body-parser in Express verwenden. Installieren Sie diese Pakete im Hauptverzeichnis Ihres Projekts mit npmoder über die Konsole in Glitch mit pnpm:

pnpm install nexmo@beta nexmo-client body-parser -s

Um die Nexmo-Tools zu nutzen, müssen Sie auch Ihre API-Anmeldedaten in der .env Datei angeben. Die Datei sollte in etwa wie folgt aussehen:

API_KEY="12ab3456" API_SECRET="123AbcdefghIJklM" APP_ID="a0b23456-c789-012d-3456-e789012f34a5" PRIVATE_KEY="/.data/private.key"

Abhängig von Ihrer Umgebung müssen Sie möglicherweise auch das dotenv Paket von npm installieren. Um Ihre Umgebungsvariablen aus .envzu importieren, müssen Sie nur eine einzige Zeile am Anfang Ihrer server.js Datei hinzufügen: require('dotenv').config();

Sie finden Ihren API-Schlüssel und Ihr Geheimnis auf der Seite Erste Schritte Seite in Ihrem Nexmo Dashboard. Gehen Sie unter dem Menü Voice auf Eine Applikation erstellen und klicken Sie auf "Generate public/private key pair", um Ihre private.key Datei herunterzuladen. Füllen Sie dann die Felder aus und klicken Sie auf "Anwendung erstellen", um Ihre Anwendungs-ID zu erhalten.

Stellen Sie sicher, dass Sie Ihre private.key Datei in Ihr Projekt und aktualisieren Sie den Pfad in .env auf den Ort, an dem Sie sie gespeichert haben. Es ist möglich, den Inhalt direkt in .enveinzufügen, aber die Formatierung kann zu Problemen führen. Im Allgemeinen ist es sinnvoller, die Datei in einer separaten Datei zu speichern.

Ein Server für API-Aufrufe

Die Rolle von Express.js in Ihrem Projekt wird darin bestehen, einen einfachen Server bereitzustellen, der die Nexmo-API aufruft, um einige Verwaltungsaufgaben auszuführen. Dies erfordert eine gewisse Einrichtung des Servers selbst, eine Nexmo-Instanz und Routendefinitionen für Ihre Serverendpunkte.

Unter server.jserstellen Sie den Server und weisen ihn an, JSON in Anfragekörpern zu parsen und statische Seiten aus dem public Verzeichnis. Als nächstes erstellen Sie ein Nexmo Objekt und übergeben ihm die Werte aus .env. Schließlich erstellen Sie Platzhalter für Ihre Routen und weisen den Server an, auf Ereignisse zu warten:

const express = require('express');
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(express.static('public'));

// create a Nexmo client
const Nexmo = require('nexmo');
const nexmo = new Nexmo({
  apiKey: process.env.API_KEY,
  apiSecret: process.env.API_SECRET,
  applicationId: process.env.APP_ID,
  privateKey: __dirname + process.env.PRIVATE_KEY 
}, {debug: true});

// the client calls this endpoint to request a JWT, passing it a username
app.post('/getJWT', function(req, res) {});

// the client calls this endpoint to get a list of all users in the Nexmo application
app.get('/getUsers', function(req, res) {});

// the client calls this endpoint to create a new user in the Nexmo application,
// passing it a username and optional display name
app.post('/createUser', function(req, res) {});

app.listen(process.env.PORT);

Server-Routen

Die drei auf dem Server definierten Routen ermöglichen es der Anwendung, Benutzer aufzulisten und zu erstellen, die einer Unterhaltung beitreten können, und sie zu authentifizieren. In einer Anwendung für den realen Einsatz würden Sie dies wahrscheinlich mit Ihrer eigenen Benutzerverwaltung anstelle einer Weboberfläche verbinden.

Die Route /getJWT liefert ein Token, das der Client zur Authentifizierung des aktuellen Benutzers verwenden kann. Das Erzeugen des JWT erfolgt mit einer einzigen Funktion, die jedoch mehrere Daten erfordert. Sie müssen Ihre Anwendungs-ID erneut angeben, ebenso wie subden Benutzernamen, den Sie authentifizieren wollen. Außerdem legen Sie die Gültigkeitsdauer und die zulässigen Pfade für das Token fest. Sie können das neu erstellte Token an den Client senden:

// the client calls this endpoint to request a JWT, passing it a username
app.post('/getJWT', function(req, res) {
  const jwt = nexmo.generateJwt({
    application_id: process.env.APP_ID,
    sub: req.body.name,
    exp: Math.round(new Date().getTime()/1000)+3600,
    acl: {
      "paths": {
        "/v1/users/**":{},
        "/v1/conversations/**":{},
        "/v1/sessions/**":{},
        "/v1/devices/**":{},
        "/v1/image/**":{},
        "/v3/media/**":{},
        "/v1/push/**":{},
        "/v1/knocking/**":{}
      }
    }
  });
  res.send({jwt: jwt});
});

Der /getUsers Pfad führt ebenfalls einen einzigen Aufruf durch und gibt sein Ergebnis zurück, aber wir wollen ihn für die Verwendung in einer Weboberfläche ein wenig aufräumen. Bevor Sie die Liste aller Benutzer in dieser Anwendung zurückgeben, können Sie Systembenutzer herausfiltern, deren IDs mit dem Präfix NAM-. In einer realen Anwendung, in der die Benutzer-IDs den Konten innerhalb Ihrer größeren Anwendung zugeordnet sind, würden Sie sich diesen Schritt wahrscheinlich sparen und die Liste unverändert zurückgeben:

// the client calls this endpoint to get a list of all users in the Nexmo application
app.get('/getUsers', function(req, res) {
  const users = nexmo.users.get({}, (err, response) => {
    if (err) {
      res.sendStatus(500);
    } else {
      let realUsers = response.filter(user => user.name.substring(0,4) !== 'NAM-');
      res.send({users: realUsers});
    }
  });
});

Die letzte Route, /createUsernimmt einige Benutzereingaben entgegen und fügt der Anwendung einen Benutzer hinzu. Da die create Funktion sowohl einen Benutzernamen als auch einen Anzeigenamen als Eingabe benötigt, gibt es in diesem Code die Möglichkeit, einen separaten Anzeigenamen zu setzen, den wir jedoch nicht in die Benutzeroberfläche aufnehmen. Daher sucht der Endpunkt nur nach einem name vom Client und gibt, sobald er einen Benutzer mit diesem erstellt hat, dessen ID zurück:

// the client calls this endpoint to create a new user in the Nexmo application,
// passing it a username and optional display name
app.post('/createUser', function(req, res) {
  nexmo.users.create({
    name: req.body.name,
    display_name: req.body.display_name || req.body.name
  },(err, response) => {
    if (err) {
      res.sendStatus(500);
    } else {
      res.send({id: response.id});
    }
  });
});

Die Vue-App-Komponente

Alle Vue-Komponenten für dieses Projekt befinden sich in dem Verzeichnis src Verzeichnis. Das Projekt, das ich remixe, enthält bereits eine main.js Datei, die eine Vue-Instanz erzeugt, sowie eine Container-Komponente in app.vue. main.js macht nichts weiter als die App-Komponente zu rendern:

var Vue = require('vue');
var App = require('./app.vue');

var vm = new Vue({
  el: '#app',
  render: createElement => {
    return createElement(App)
  }
});

Dies funktioniert in Verbindung mit public/index.htmlwo ein div mit der ID app das einzige Element auf der Seite ist:

<!DOCTYPE html>
<html>
<head>
  <title>VueJS + Express Template</title>
  <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
  <div id="app"></div>
  <script src="build.js"></script>
</body>
</html>

Für den Fall, dass wir später mehr hinzufügen wollen, lassen wir die App Komponente an Ort und Stelle und laden eine Nexmo Komponente hinein, anstatt sie App durch Nexmo. Wenn Sie bereits eine app.vue Datei haben, können Sie deren Inhalt durch eine einfache Vorlage und ein Skript ersetzen, das nur die Nexmo Komponente lädt:

template>
  <div class="app">
    <Nexmo/>
  </div>
</template>

<script>
import Nexmo from './nexmo.vue';

export default {
  name: 'App',
  components: {
    Nexmo
  }
}
</script>

Die Nexmo-Komponente

Die Nexmo Komponente ist der Punkt, an dem die Dinge anfangen, interessant zu werden. Sie können sie erstellen unter nexmo.vue erstellen und eine Vorlage am Anfang der Datei hinzufügen, die User und Conversation Komponenten. Für Userruft ein Aktualisierungshaken eine Funktion getJWT in dem Skript auf, das Sie als nächstes hinzufügen werden. Sie können auch einen Verweis auf die Komponente hinzufügen, um später auf sie zuzugreifen:

<template>
  <div class="nexmo">
    <User @hook:updated="userUpdated" ref="user" />
    <Conversation/>
  </div>
</template>

Unterhalb der Vorlage fügen Sie ein Skript-Tag hinzu, das die Logik der Komponente enthält. Nach dem Importieren der beiden Unterkomponenten und des Nexmo Client SDK exportieren Sie eine Vue-Komponente namens Nexmo. Sie enthält einige leere data Eigenschaften, die Teil ihres Zustands sein werden, sowie ihre Unterkomponenten und einige Methoden, die Sie als nächstes definieren werden:

<script>
  import User from './user.vue';
  import Conversation from './conversation.vue';
  import nexmoClient from 'nexmo-client';
  
  export default {
    name: 'Nexmo',
    data: () => ({
      app: null,
      token: null,
      invites: [],
      loggedIn: false
    }),
    components: {
      User,
      Conversation
    },
    methods: {}
  };
</script>

Die Eigenschaft methods Eigenschaft wird zwei Funktionen definieren, eine zum Abrufen eines JWT vom Server und eine für die Anmeldung. Die Funktion getJWT Funktion wird durch den Update-Hook auf Ihrer User Komponente aufgerufen, daher sollte zuerst geprüft werden, ob diese Komponente eine username Eigenschaft enthält. Wenn dies der Fall ist, kann sie den serverseitigen /getJWT Endpunkt mit fetch. Er übergibt den stringifizierten username Wert, und wenn alles reibungslos funktioniert, erhält er ein JWT zurück. Es speichert das JWT als Eigenschaft der Instanz und ruft die login Funktion auf.

Die Funktion login Funktion wird ein tatsächlicher Nexmo-Client instanziiert. Sie melden Ihren Benutzer mit seinem JWT an, setzen dann ein Flag, wenn dies erfolgreich war, und speichern einen Verweis auf die Nexmo-Anwendung. Sobald Sie die Anwendung haben, können Sie die Unterhaltungen abrufen, zu denen der aktuelle Benutzer eingeladen ist:

    methods: {
      getJWT: function() {
        var username = this.$refs.user.username;
        if (!username) {
          return;
        }
        var vm = this;
        fetch('/getJWT', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            name: username
          })
        })
        .then(results => results.json())
        .then(data => {
          vm.token = data.jwt;
          vm.login();
        });
      },
      login: function() {
        let nexmo = new nexmoClient();
        nexmo.login(this.token).then(app => {
          this.loggedIn = true;
          this.app = app;
          app.getConversations().then(convos => {
            this.invites = Array.from(convos.entries());
          });
        });
      }
    }

Die Benutzerkomponente

Die User Komponente unter src/user.vue ist die erste Stelle, an der Sie eine Vorlage haben, die etwas anderes tut als eine Unterkomponente zu rendern. Dies ist ein weiterer Teil, den Sie in einer Produktionsanwendung weglassen könnten, aber in diesem Fall ist die Benutzeranmeldungsschnittstelle Teil Ihrer Nexmo-Anwendung. Das Template zeigt den verbundenen Benutzer an, wenn einer existiert. Wenn nicht, zeigt es ein Formular mit zwei Pfaden an. Der erste ermöglicht die Auswahl eines bestehenden Benutzers aus einem Dropdown-Menü. Wenn eine Auswahl getroffen wird, wird der Benutzer sofort durch die setExistingUser Funktion aktualisiert.

Ein Benutzer kann auch einen neuen Benutzernamen eingeben und auf die Schaltfläche "Senden" klicken. Dies ruft die createUser Funktion auf:

<template>
  <div v-if="userId" class="userinfo userconnected">
    Connected as <span class="username">{{username}}</span>
  </div>
  <div v-else class="userinfo">
    <label>User name: 
      <select v-on:change="setExistingUser">
        <option value=""></option>
        <option v-for="item in currentUsers" v-bind:value="item.id">
          {{item.name}}
        </option>
      </select>
    </label>
    <input type="text" v-on:change="setUsername" />
    <button v-on:click="createUser">Create user</button>
  </div>
</template>

Skript der Benutzerkomponente

Die script für die Komponente hat ein paar verschiedene Dinge zu tun, aber keine komplexe Logik. Das meiste, was sie tut, ist das Laden und Speichern von Eigenschaften. Das komplexe Zeug passiert innerhalb des Vue-Frameworks selbst, in Funktionen wie dem Update-Hook in Ihrer Nexmo Komponente.

Da es nichts zu importieren gibt, können Sie sofort eine User Komponente exportieren. Das einzige data sind Eigenschaften für die ID und den Namen des Benutzers sowie eine Liste der aktuellen Benutzer in der Anwendung.

Die Komponente verfügt über vier Methoden zur Unterstützung des Formulars in der Vorlage. Die getUsers Funktion ruft /getUsers auf dem Server auf, um die Liste der Benutzer abzurufen. Sie werden sich daran erinnern, dass Sie alle notwendigen Filterlogiken serverseitig behandelt haben. Wenn also kein Fehler auftritt, können Sie diese Eigenschaft einfach in der Komponente einstellen.

setExistingUser wird aufgerufen durch ein onchange Ereignis auf dem Benutzer-Dropdown aufgerufen. Es speichert den Benutzernamen und die Benutzer-ID der getroffenen Auswahl. Für neue Benutzer, setUsername wird ebenfalls durch ein Ereignis onchangeaufgerufen, dieses Mal für das Textfeld. Die Aktualisierung des neuen Benutzernamens in der Komponente bei jeder Änderung erspart es Ihnen, einen Verweis auf das Textfeldelement zu erhalten. Wenn ein Benutzer auf die Schaltfläche "Benutzer erstellen" klickt, createUser aufgerufen, wobei der Benutzername im Status an den Server gesendet und die zurückgegebene Benutzer-ID gespeichert wird.

Nach methodsruft diese Komponente auch beforeMount auf, um sicherzustellen, dass die Liste der Benutzer bei der ersten Initialisierung geladen wird:

<template>
  ...
</template>

<script>  
export default {
  name: 'User',
  data: () => ({
    userId: undefined,
    username: null,
    currentUsers: []
  }),
  methods: {
    getUsers: function() {
      var vm = this;
      fetch('/getUsers', {
        method: 'GET'
      }).then(results => results.json())
      .then(data => {
        vm.currentUsers = data.users;
      });
    },

    setExistingUser: function(evt) {
      this.username = evt.target[evt.target.selectedIndex].text;
      this.userId = evt.target.value;
    },

    setUsername: function(evt) {
      this.username = evt.target.value;
    },

    createUser: function() {
      var vm = this;
      fetch('/createUser', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          name: vm.username
        })
      }).then(results => results.json())
        .then(data => { 
          vm.userId = data.id;
        });
    }
  },
  beforeMount() {
    this.getUsers()
  }
};
</script>

Die Gesprächskomponente

Bis jetzt haben Sie den Code geschrieben, um Ihre Nexmo-App zu erstellen, einen Benutzer anzulegen und sich anzumelden. Der Code sollte gerade separat genug sein, dass Sie Änderungen vornehmen können, um die Bedürfnisse Ihres individuellen Projekts zu unterstützen, während Sie immer noch die wesentlichen Informationen für Ihre größere Vue-App bereitstellen. Jetzt können Sie diese Teile verwenden, um einer Konversation beizutreten. Die Konversation ist der Ausgangspunkt für eine Vielzahl von Kommunikationsarten, die Sie mit Nexmo's API auf dem Client durchführen möchten.

Die Komponente wäre überschaubarer, wenn einige Unterkomponenten aufgeteilt würden (z. B. die Audiosteuerung). Zur besseren Übersichtlichkeit können Sie jedoch den gesamten Code in conversation.vue.

Die Vorlage

Auf der obersten Ebene der Vorlage befindet sich eine Bedingung, um festzustellen, ob ein laufendes Gespräch existiert. Wenn dies der Fall ist, werden Sie ein Audio-Element für den Ton und zwei Schaltflächen zum Aktivieren und Deaktivieren des Tons anzeigen. Wenn sie angeklickt werden, rufen sie enableAudio und disableAudioauf.

Wenn es keine aktuelle Unterhaltung gibt, muss der Benutzer einer beitreten oder eine beginnen. Wenn der Benutzer zu einer Konversation eingeladen wurde oder bereits eine begonnen hat, erscheint diese im invites Array der übergeordneten Nexmo Komponente. Die Werte in invites füllen ein Dropdown-Menü mit Konversationen, und die Auswahl einer Konversation führt zum Aufruf der joinConversation Funktion auf. Unabhängig davon, ob der Benutzer bereits eine Unterhaltung geführt hat oder nicht inviteshat, wird ihm eine Schaltfläche angezeigt, mit der er ein neues Gespräch beginnen kann:

<template>
  <div v-if="current_conv" class="conversation">
    <audio ref="audio">
      <source/>
    </audio>
    <button v-on:click="enableAudio" v-bind:disabled="audioOn">
      Enable audio
    </button>
    <button v-on:click="disableAudio" v-bind:disabled="!audioOn">
      Disable audio
    </button>
  </div>
  <div v-else class="conversation">
    <label v-if="$parent.invites.length">Choose an active conversation: 
      <select v-on:change="joinConversation">
        <option value="0">-</option>
        <option v-for="invite in $parent.invites" v-bind:value="invite[0]">
          {{invite[1].name}}
        </option>
      </select> or
    </label>
    <button v-on:click="createConversation" :disabled="!$parent.loggedIn">
      Start conversation
    </button>
  </div>
</template>

Das Drehbuch

Auch in dieser Komponente besteht das einzige, was auf der obersten Ebene des script Tags ist das Exportieren einer Conversation Komponente. Die einzigen data sind die aktuelle Konversation und ein Flag, das anzeigt, ob Audio aktiviert ist.

Die methods die die Komponente enthält, sind alle recht einfach. createConversation ruft die newConversation Funktion der App auf, die in der übergeordneten Nexmo Komponente gespeichert ist, und speichert dann die erstellte Konversation. joinConversation macht dasselbe, außer dass es die Funktion der App aufruft getConversation Funktion aufruft und ihr die ID der im Dropdown-Menü ausgewählten Konversation übergibt.

Unter enableAudiomüssen Sie zunächst die Medien für die aktuelle Unterhaltung aktivieren. Dadurch erhalten Sie einen Stream, den Sie als srcObject oder src des audio Elements in Ihrer Vorlage festlegen können. Sobald die Metadaten geladen sind, können Sie den Stream abspielen und das Flag der Komponente audioOn Flagge der Komponente auf true. Die disableAudio Funktion, die aufgerufen wird, wenn die Schaltfläche "Disable audio" angeklickt wird, ist einfacher. Sie muss lediglich die Medien in current_conv deaktivieren und dann das audioOn Flagge wieder auf false:

<template>
  ...
</template>

<script>
  export default {
    name: 'Conversation',
    data: () => ({
      current_conv: undefined,
      audioOn: false
    }),
    methods: {
      createConversation: function() {
        var vm = this;
        this.$parent.app.newConversation().then(conv => {
          conv.join();
          vm.current_conv = conv;
        });
      },
  
      joinConversation: function(evt) {
        var vm = this;
        this.$parent.app.getConversation(evt.target.value).then(conv => {
          conv.join();
          vm.current_conv = conv;
        });
      },

      enableAudio: function() {
        var vm = this;
        this.current_conv.media.enable().then(stream => {
          // Older browsers may not have srcObject
          if ('srcObject' in vm.$refs.audio) {
            vm.$refs.audio.srcObject = stream;
          } else {
            // Avoid using this in new browsers, as it is going away.
            vm.$refs.audio.src = window.URL.createObjectURL(stream);
          }
          vm.$refs.audio.onloadedmetadata = () => {
            vm.$refs.audio.play();
            vm.audioOn = true;
          }
        });
      },

      disableAudio: function() {
        var vm = this;
        this.current_conv.media.disable().then(() => {
          vm.audioOn = false;
        });
      }
    }
  };
</script>

Der Rest

Es gibt ein paar Dinge, die wir noch nicht behandelt haben, die hoffentlich von der Vue-Boilerplate geliefert werden, die Sie gewählt haben, oder die nicht unbedingt die Logik Ihrer Anwendung beeinflussen müssen. Zum Beispiel, in meinem eigenen Code verlasse ich mich auf Browserify und Vueifysowie ein bisschen CSS, das Teil des Projekts war, das ich neu gemischt habe. Der Build-Schritt, der die Vue-Seite der Anwendung zum Laufen bringt, ist in "scripts" definiert in package.json:

"compile": "browserify -t vueify -e src/main.js -o public/build.js"

Nächste Schritte

Der Code, den Sie geschrieben haben, ist ein guter Ausgangspunkt für Ihre Arbeit in der Praxis. Wie bereits erwähnt, werden Sie wahrscheinlich das System zur Verwaltung der Testbenutzer durch etwas ersetzen wollen, das die Konversationsmitglieder mit Ihren eigenen authentifizierten Benutzern verknüpft. Wenn Sie Ihre Konversation erstellt haben, können Sie Nachrichten senden und empfangen, Anrufe entgegennehmen und Audiokonferenzen einrichten.

Lesen Sie mehr über das Nexmo Client SDK und erfahren Sie, was Sie als nächstes tun können:

Teilen Sie:

https://a.storyblok.com/f/270183/250x250/f231d97f1b/garann-means.png
Garann MeansEntwickler Pädagoge

Ich bin ein JavaScript-Entwickler und ein Developer Educator bei Vonage. Im Laufe der Jahre habe ich mich für Templates, Node.js, progressive Web-Apps und Offline-First-Strategien begeistert, aber was ich immer geliebt habe, ist eine nützliche, gut dokumentierte API. Mein Ziel ist es, Ihre Erfahrung mit unseren APIs so gut wie möglich zu gestalten.