Was ist eine API?
Eine API (“Application Programming Interface”) ist eine Schnittstelle mit der eine Software Daten mit einer Anderen kommunizieren kann. Warum?
Zum Einen kann man über eine API einzelne Module in einem Programm klar voneinander trennen und somit bei Bedarf ganze Blöcke einfach austauschen. Nehmen wir an wir haben eine Website und wollen Benutzern ermöglichen Daten von dieser Website zu beziehen. Wir lassen natürlich nicht jeden an unsere Systeme. Also müssen wir einen Weg finden über den ein Benutzer diese Daten abfragen kann. Da kommt die sog. RESTful API ins Spiel.
Was ist eine RESTful API?
Die Representational State Transfer API, kurz: REST oder RESTful API, ist ein im Web-Bereich häufig eingesetzter API-Typ. Hierbei werden die Daten über das HTTP-Protokoll und zumeist im JSON-Format (Javascript Object Notation) übergeben. Die übergeben Daten sind dadurch leicht zu parsen.
Eine RESTful API ist nicht der einzige Typ der im Web-Bereich genutzt wird. Eine weitere Möglichkeit wäre die SOAP API (Simple Object Access Protocol), welche von Microsoft entwickelt wurde. Anders als die meisten RESTful APIs stützen sich SOAP APIs auf das XML-Format.
Dieser Post beschäftigt sich mit der oberen Variante – der JSON-API / REST.
Aufbau einer JSON-Response
Dies ist die Response, also die Antwort, unserer Beispiel-API die wir im Nachhinein bauen.
JSON besteht immer aus Key-Value-Paaren.Als Beispiel: Key “version” wurde der Wert “v1” zugewiesen. Diese Response kann vom Absender des HTTP-Requests angenommen und geparsed werden, um das Ergebnis dann weiter zu verarbeiten.
Unser Beispielprojekt
An einem Beispiel zu lernen ist immer einfacher als Dokumentation lesen.
In diesem und den folgenden Beiträgen werden wir eine Online-Pinnwand bauen, die mit einer API bedient wird. Also jeder kann dort Einträge über die API hinzufügen, abfragen, ändern, oder löschen. Das wird am Anfang erst einmal komplett ungesichert geschehen.
Wir werden das Projekt mit jedem Teil erweitern. Wir Benutzerkonten ein, verschiedene Authentifizierungsmethoden begutachten, Pagination einsetzen etc. Am Ende soll dann eine API entstehen, welche von Entwicklern verwendet werden kann.
Schritt 1 – Das Projekt aufsetzen
Als erstes wird natürlich das Projekt erstellt. Ich persönlich nutze dafür Laragon, aber wer lieber Composer benutzt, wird mit diesem Command wahrscheinlich glücklicher.
$ composer create-project laravel/laravel notes-api
Anschließend folgen die üblichen Installationsschritte wie das Installieren der Abhängigkeiten und der Anpassung der .env-Datei.
$ composer i && npm i
$ php artisan key:generate // Wenn der App-Key nicht gesetzt sein sollte
Wenn alle Abhängigkeiten installiert sind, muss die .env angepasst werden.
Für den Anfang muss hier nur der Datenbankname hinterlegt werden.
Jetzt können wir uns an den eigentlichen Code machen. Zuerst erstellen wir unser Notes-Model mitsamt der Migration.
$ php artisan make:model Note -m
Unsere Note bekommt 4 Felder. Der Titel und der Inhalt sind Pflichtfelder da sonst der gesamte Post keinen Sinn machen würde. Sei es aus Datenschutzgründen sind die Felder für den Namen und die Email des Autoren nullable (also nicht zwingend erforderlich).
Damit ist die Migration fertig.
Schritt 2 – Controller und FormRequests
Jeder Laravel Entwickler kennt die FormRequests, welche einem das Validieren von Anfragen erleichtern. Wir können diese auch für unsere API Anfragen nutzen.
$ php artisan make:controller NoteController –api –requests –model=Note
Dieses Command erstellt uns neben dem Controller auch gleich die dazugehörigen FormRequests. Und das Ganze ist auch noch auf APIs ausgerichtet.
Dies hat uns nun 3 neue Dateien erzeugt.
- app\Http\Controller\NoteController.php
- app\Http\Requests\StoreNoteRequest.php
- app\Http\Requests\UpdateNoteRequest.php
Wir haben nun also den Controller mit den 5 API-Methoden sowie 2 FormRequests für die beiden “Schreib”-Routen.
Den Controller bearbeiten wir später. Wenden wir uns hier erstmal den FormRequests zu. Hier müssen wir aus den authorize-Methoden erst einmal true zurückliefern, da wir vorerst keinerlei Authorization bzw. Authentication verwenden werden.
Anschließend fügen wir unsere Validationrules ein.
[CODE]
Schritt 3 – Wir brauchen Testdaten!
Nichts leichter als das! Wir erstellen uns eine Factory für unser Note-Model.
$ php artisan make:factory NoteFactory
Die fertig erstellte Factory finden wir unter database/factories/NoteFactory.php. In der Factory selbst müssen wir nur folgende Definition hinterlegen.
[CODE]
Nun registrieren wird die Factory noch im Model. Das sollte dann ungefähr so aussehen.
[CODE]
Wichtig dabei ist, das der HasFactory-Trait importiert und eingebunden ist.
Wenn alles fertig ist können wir in einer Tinker-Session unsere Testdaten generieren.
$ php artisan tinker
Note::factory()->count(10)->create();
Die Factory erstellt jetzt mit Faker die Datensätze und speichert diese in die Datenbank.
Schritt 4 – Das Routing und API Resources
Jetzt könnten wir anfangen die Routen auf die übliche Art und Weise zu schreiben. Da wir uns aber in Laravel 8 befinden haben wir Zugriff auf die sog. ApiResources welche seit Laravel 7 zur Verfügung stehen. Diese erleichtern uns die Umwandlung unserer Daten in das JSON-Format.
API Resources
Für jedes Model können API Resources erstellt werden. Aber was verbirgt sich genau hinter API Resources?
Zu Anfang erstellen wir uns die beiden Resource-Klassen. Die Erste ist die Resource selbst. Diese ist dazu gedacht einen einzelnen Datensatz zurück zu geben.
$ php artisan make:resource NoteResource
Wir finden die erstellte Resource nun unter app\Http\Resources\NoteResource
Hier definieren wir jetzt welche Daten zurückgeliefert werden sollen.
[CODE]
$ php artisan make:resource NoteCollection –collection
In der NoteCollection müssen wir nur noch die Collection unter dem Key “data” zurückliefern
und schon können wir uns um das letzte Element unserer Response kümmern.
Zusätzlich liefern wir noch Metadaten zurück. In unserem Fall ist das der Key “success” und der Key “version”. Für diesen Fall gibt es die with()-Methode in unseren Resources. Da wir diese aber bei beiden Ressourcen hinzufügen müssten, und damit eine Code Duplication hätten, lagern wir diesen Code in einen Trait aus. Ich habe diesen Trait HasMetaInformation genannt und ihn unter app\Http\Resources\Traits angelegt. Der Inhalt des Traits sollte dann am Ende ungefähr so aussehen.
[CODE]
Schritt 5 – Zeit Antworten zu liefern
Index und Show
Route: GET http://deine-domain.test/api/v1/notes
GET http://deine-domain.test/api/v1/notes/{note-id}
Jetzt machen wir uns daran die Requests auch tatsächlich zu beantworten. Dazu wenden wir uns dem NoteController zu und dort fangen wir mit den beiden Abfragemethoden (index und show) an.
Diese Methoden sind sehr übersichtlich gestaltet, verdeutlichen aber gut den Unterschied zwischen API Resources und API Collections. Über die Index-Methode/Route wollen wir eine Liste von Beiträgen zurückliefern. Daher wrappen wir diese vorher mittels der ResourceCollection in eine eigene Liste. Über die Show-Methode/Route liefern wir hingegen einen ausgewählten Beitrag zurück. Also eine einzelne Resource. Natürlich können mit Einzelresourcen auch mehrere Beiträge zurück geliefert werden, bei diesen besteht aber zur Zeit nur beschränkter Zugriff auf etwaige Methoden wie z. B. die with()-Methode mit welcher wir unsere Metadaten zurückliefern.
Store und Update
Route: POST http://deine-domain.test/api/v1/notes
PUT|PATCH http://deine-domain.test/api/v1/notes/{note-id}
Nachdem wir nun das zurückliefern was wir selbst in der Datenbank haben, wird es Zeit neue Einträge hinzuzufügen bzw. diese zu bearbeiten. Was uns zu den Methoden store() und update() bringt.
Diese Methoden sind auch sehr übersichtlich gehalten. Wir finden hier als Type Hint unsere beiden Form Requests die einen ankommenden Request automatisch validieren bevor er in unsere Methoden kommt. Wir nutzen hier noch eine weitere Methode und zwar die writeResponse()-Methode. Diese habe ich zum Auslagern von Code benutzt, der sonst wieder doppelt wäre. Sie bekommt entweder die frisch erstellte Note oder die, welche wir gerade bearbeitet haben. Sollte diese Notes den Wert null haben brechen wir die Ausführung ab und liefern den Status 500 zurück. Wenn es sich wirklich um eine Note handelt liefern wir eine leere JSON-Response zurück und den übergebenen Statuscode. Dieser ist bei geglückten Requests in der Regel 200 OK.
Wichtig hierbei ist, dass diese Methoden ein JSON-Objekt erwarten, welches ungefähr so aussehen sollte.
Destroy
Route: DELETE http://deine-domain.test/api/v1/notes/{note-id}
Jetzt wird es Zeit etwas kaputt zu machen. Das bedeutet, dass wir uns jetzt der letzten Methode zuwenden. Die destroy-Methode wird genutzt um einen Datensatz dauerhaft aus der Datenbank zu entfernen.