Entwicklung
API
08.03.2024
Dieser Artikel beschreibt den Prozess des Aufbaus der öffentlichen API für Documenso. Er beginnt mit einer Erklärung, warum die API für ein Unternehmen zur digitalen Dokumentenunterzeichnung überhaupt benötigt wurde. Danach geht es um die Schritte, die wir unternommen haben, um sie zu erstellen. Schließlich werden die Anforderungen präsentiert, die wir erfüllen mussten, sowie die Einschränkungen, innerhalb derer wir arbeiten mussten.
Warum die öffentliche API
Wir haben uns entschieden, die öffentliche API zu erstellen, um eine neue Möglichkeit der Interaktion mit Documenso zu eröffnen. Während die Webanwendung ihre Aufgabe gut erfüllt, gibt es Anwendungsfälle, in denen dies nicht ausreicht. In diesen Fällen möchten die Nutzer möglicherweise programmatisch mit der Plattform interagieren. Dies geschieht normalerweise, um Documenso in andere Anwendungen zu integrieren.
Mit der neuen öffentlichen API ist das jetzt möglich. Sie können die Funktionen von Documenso in anderen Anwendungen integrieren, um Aufgaben zu automatisieren, benutzerdefinierte Lösungen zu erstellen und individuelle Arbeitsabläufe zu erstellen, um nur einige Beispiele zu nennen.
Die API bietet zum Zeitpunkt des Schreibens dieses Artikels 12 Endpunkte:
- (GET)
/api/v1/documents
- alle Dokumente abrufen- (POST)
/api/v1/documents
- ein neues Dokument hochladen und eine vorab signierte URL erhalten- (GET)
/api/v1/documents/{id}
- ein bestimmtes Dokument abrufen- (DELETE)
/api/v1/documents/{id}
- ein bestimmtes Dokument löschen- (POST)
/api/v1/templates/{templateId}/create-document
- ein neues Dokument aus einer vorhandenen Vorlage erstellen- (POST)
/api/v1/documents/{id}/send
- ein Dokument zur Unterzeichnung senden- (POST)
/api/v1/documents/{id}/recipients
- einen Dokumentempfänger erstellen- (PATCH)
/api/v1/documents/{id}/recipients/{recipientId}
- die Details eines Dokumentempfängers aktualisieren- (DELETE)
/api/v1/documents/{id}/recipients/{recipientId}
- einen bestimmten Empfänger aus einem Dokument löschen- (POST)
/api/v1/documents/{id}/fields
- ein Feld für ein Dokument erstellen- (PATCH)
/api/v1/documents/{id}/fields
- die Details eines Dokumentfelds aktualisieren- (DELETE)
/api/v1/documents/{id}/fields
- ein Feld aus einem Dokument löschen
Schauen Sie sich die API-Dokumentation an.
Darüber hinaus ermöglicht es uns auch, die Plattform zu verbessern, indem wir andere Integrationen in Documenso bringen, wie z.B. Zapier.
Zusammenfassend erweitert die neue öffentliche API die Möglichkeiten von Documenso, bietet den Nutzern mehr Flexibilität und eröffnet eine breitere Welt von Möglichkeiten.
Die richtige Vorgehensweise und Technologie wählen
Sobald wir uns entschieden hatten, die API zu erstellen, mussten wir die Vorgehensweise und Technologien auswählen, die wir verwenden wollten. Es gab 2 Optionen:
Eine zusätzliche Anwendung entwickeln
Die API im bestehenden Code veröffentlichen
1. Eine zusätzliche Anwendung entwickeln
Das würde bedeuten, eine neue Codebasis zu erstellen und die API von Grund auf neu zu entwickeln. Eine separate App für die API hätte Vorteile wie:
geringere Latenzantworten
Unterstützung größerer Dateiuploads
Trennung zwischen den Apps (Documenso und der API)
Individualisierbarkeit und Flexibilität
einfacheres Testen und Debuggen
Dieser Ansatz hat erhebliche Vorteile. Ein großer Nachteil ist jedoch, dass er zusätzliche Ressourcen erfordert.
Wir müssten viel Zeit nur für die grundlegenden Dinge aufwenden, wie den grundlegenden Server aufzubauen und zu konfigurieren. Danach würden wir Zeit mit der Implementierung der Endpunkte und der Autorisierung verbringen, unter anderem. Wenn der Aufbau abgeschlossen ist, gibt es eine weitere Anwendung, die bereitgestellt und verwaltet werden muss. All dies würde unsere bereits begrenzten Ressourcen strapazieren.
Also haben wir uns gefragt, ob es einen anderen Weg gibt, dies zu tun, ohne die Qualität der API und das Entwicklererlebnis zu opfern.
2. Die API im bestehenden Code veröffentlichen
Die andere Option bestand darin, die API im bestehenden Code zu veröffentlichen. Anstatt alles von Grund auf neu zu schreiben, könnten wir den Großteil unseres vorhandenen Codes verwenden.
Da wir tRPC für unsere interne API (Backend) verwenden, haben wir nach Lösungen gesucht, die gut mit tRPC funktionieren. Wir haben die Auswahl auf folgende Optionen eingegrenzt:
Beide Technologien ermöglichen es, öffentliche APIs zu erstellen. Die Technologie trpc-openapi
ermöglicht es Ihnen, tRPC-Prozeduren einfach in REST-Endpunkte umzuwandeln. Es ist eher wie ein Plugin für tRPC.
Auf der anderen Seite ist ts-rest
eher eineStandalone-Lösung. ts-rest
ermöglicht es Ihnen, einen Vertrag für die API zu erstellen, der sowohl auf dem Client als auch auf dem Server verwendet werden kann. Sie können den Vertrag in Ihrer Anwendung konsumieren und implementieren, wodurch End-to-End-Typensicherheit und ein RPC-ähnlicher Client bereitgestellt wird.
Sie können hier einen Vergleich zwischen trpc-openapi und ts-rest sehen.
Der Hauptunterschied zwischen den beiden besteht darin, dass trpc-openapi
wie ein Plugin ist, das die Fähigkeiten von tRPC erweitert, während ts-rest
die Werkzeuge zum Erstellen einer eigenständigen API bereitstellt.
Unsere Wahl
Nach der Analyse und dem Vergleich der beiden Optionen haben wir uns für ts-rest
aufgrund seiner Vorteile entschieden. Hier ist ein Abschnitt aus der ts-rest
-Dokumentation, der den Nagel auf den Kopf trifft:
tRPC hat viele Plugins, um dieses Problem zu lösen, indem die API-Implementierung in eine REST-ähnliche API mapping, jedoch sind diese Ansätze oft ein wenig umständlich und verringern die Sicherheit des Systems insgesamt, ts-rest erledigt dieses schwere Heben in den Client- und Serverimplementierungen, anstatt eine zweite Abstraktionsschicht und API-Endpunkt(e) definieren zu müssen.
API-Anforderungen
Wir haben die folgenden Anforderungen für die API definiert:
Die API sollte pfadbasiertes Versionieren verwenden (z. B. "/v1")
Das System sollte Bearer-Token zur Authentifizierung der API verwenden
Das API-Token sollte eine zufällige Zeichenkette von 32 bis 40 Zeichen sein
Das System sollte das Token hashen und den Hashwert speichern
Das System sollte das API-Token nur bei seiner Erstellung anzeigen
Die API sollte selbstgenerierte Dokumentation wie Swagger haben
Benutzer sollten in der Lage sein, einen API-Schlüssel zu erstellen
Benutzer sollten in der Lage sein, einen Token-Namen auszuwählen
Benutzer sollten in der Lage sein, ein Ablaufdatum für das Token auszuwählen
Der Benutzer sollte zwischen 7 Tagen, 1 Monat, 3 Monaten, 6 Monaten, 12 Monaten und nie wählen können
Das System sollte alle Tokens des Benutzers auf der Einstellungsseite anzeigen
Das System sollte den Token-Namen, das Erstellungsdatum, das Ablaufdatum und einen Löschen-Button anzeigen
Benutzer sollten in der Lage sein, einen API-Schlüssel zu löschen
Benutzer sollten in der Lage sein, alle Dokumente aus ihrem Konto abzurufen
Benutzer sollten in der Lage sein, ein neues Dokument hochzuladen
Benutzer sollten nach einem erfolgreichen Upload eine S3-vorab signierte URL erhalten
Benutzer sollten in der Lage sein, ein bestimmtes Dokument anhand seiner ID aus ihrem Konto abzurufen
Benutzer sollten in der Lage sein, ein bestimmtes Dokument anhand seiner ID aus ihrem Konto zu löschen
Benutzer sollten in der Lage sein, ein neues Dokument aus einer vorhandenen Dokumentvorlage zu erstellen
Benutzer sollten in der Lage sein, ein Dokument zur Unterzeichnung an 1 oder mehr Empfänger zu senden
Benutzer sollten in der Lage sein, einen Empfänger für ein Dokument zu erstellen
Benutzer sollten in der Lage sein, die Details eines Empfängers zu aktualisieren
Benutzer sollten in der Lage sein, einen Empfänger aus einem Dokument zu löschen
Benutzer sollten in der Lage sein, ein Feld (z. B. Unterschrift, E-Mail, Name, Datum) für ein Dokument zu erstellen
Benutzer sollten in der Lage sein, ein Feld für ein Dokument zu aktualisieren
Benutzer sollten in der Lage sein, ein Feld aus einem Dokument zu löschen
Einschränkungen
Wir hatten auch mit den folgenden Einschränkungen zu kämpfen, während wir die API entwickelten:
1. Ressourcen
Begrenzte Ressourcen waren eine der Hauptbeschränkungen. Wir sind ein neues Start-up mit einem relativ kleinen Team. Der Aufbau und die Wartung einer zusätzlichen Anwendung würden unsere begrenzten Ressourcen überstrapazieren.
2. Technologie-Stack
Eine weitere Einschränkung war der Technologie-Stack. Unser Technologie-Stack umfasst unter anderem TypeScript, Prisma und tRPC. Wir verwenden auch Vercel für das Hosting.
Infolgedessen wollten wir Technologien verwenden, mit denen wir vertraut sind. Dies ermöglichte es uns, unser bestehendes Wissen zu nutzen und Konsistenz über unsere Anwendungen hinweg zu gewährleisten.
Die Verwendung vertrauter Technologien bedeutete auch, dass wir die API schneller entwickeln konnten, da wir keine Zeit mit dem Erlernen neuer Technologien verbringen mussten. Wir konnten auch vorhandenen Code und Werkzeuge nutzen, die in unserer Hauptanwendung verwendet werden.
Es sei darauf hingewiesen, dass dies keine dauerhafte Entscheidung ist. Wir sind offen dafür, die API in eine andere Codebasis/Technologie-Stack zu verschieben, wenn es sinnvoll ist (z. B. die API wird stark genutzt und benötigt bessere Leistung).
3. Datei-Uploads
Aufgrund unserer aktuellen Architektur unterstützen wir Datei-Uploads mit einer maximalen Größe von 50 MB. Um dies zu umgehen, haben wir einen zusätzlichen Schritt zum Hochladen von Dokumenten erstellt.
Benutzer stellen eine POST-Anfrage an den /api/v1/documents
Endpunkt, und die API antwortet mit einer S3-vorab signierten URL. Die Benutzer stellen dann eine 2. Anfrage an die vorab signierte URL mit ihrem Dokument.
Wie wir die API gebaut haben
Unsere Codebasis ist ein Monorepo, daher haben wir ein neues API-Paket im packages
Verzeichnis erstellt. Es enthält sowohl die API-Implementierung als auch die Dokumentation. Die beiden Hauptblöcke der Implementierung bestehen aus dem API-Vertrag und dem Code für die API-Endpunkte.
In wenigen Worten definiert der API-Vertrag die API-Struktur, das Format der Anfragen und Antworten, wie API-Aufrufe authentifiziert werden, die verfügbaren Endpunkte und ihre zugehörigen HTTP-Verben. Sie können den API-Vertrag auf GitHub erkunden.
Dann gibt es den Implementierungsteil, der der tatsächliche Code für jeden im API-Vertrag definierten Endpunkt ist. Die Implementierung ist der Teil, in dem der API-Vertrag zum Leben erweckt und funktionsfähig gemacht wird.
Nehmen wir den Endpunkt /api/v1/documents
als Beispiel.
Der API-Vertrag spezifiziert die folgenden Dinge für getDocuments
:
die erlaubte HTTP-Anfragemethode ist GET, sodass beispielsweise der Versuch, eine POST-Anfrage zu stellen, zu einem Fehler führt
der Pfad ist
/api/v1/documents
die Abfrageparameter, die der Benutzer mit der Anfrage übergeben kann
in diesem Fall -
page
undperPage
die erlaubten Antworten und deren Schema
200
gibt ein Objekt zurück, das ein Array aller Dokumente und ein FeldtotalPages
enthält, das selbsterklärend ist401
gibt ein Objekt mit einer Nachricht wie "Nicht autorisiert" zurück404
gibt ein Objekt mit einer Nachricht wie "Nicht gefunden" zurück
Die Implementierung dieses Endpunkts muss den Vertrag vollständig erfüllen; andernfalls wird ts-rest
sich beschweren und Ihre API möglicherweise nicht wie beabsichtigt funktionieren.
Die Funktion getDocuments
aus der implementation.ts
-Datei wird ausgeführt, wenn der Benutzer den Endpunkt aufruft.
Es gibt auch ein Middleware, authenticatedMiddleware
, das die Authentifizierung für API-Anfragen behandelt. Es stellt sicher, dass das API-Token vorhanden ist und das verwendete Token die entsprechenden Berechtigungen für die Ressource hat, auf die zugegriffen wird.
So funktionieren auch die anderen Endpunkte. Der Code unterscheidet sich, aber die Prinzipien bleiben gleich. Sie können die API-Implementierung und den Middleware-Code auf GitHub erkunden.
Dokumentation
Für die Dokumentation haben wir uns entschieden, Swagger UI zu verwenden, das automatisch die Dokumentation aus der OpenAPI-Spezifikation generiert.
Die OpenAPI-Spezifikation beschreibt eine API, die die verfügbaren Endpunkte und ihre HTTP-Anfragemethoden, Authentifizierungsmethoden usw. enthält. Sie dient dazu, sowohl Maschinen als auch Menschen zu helfen, die API zu verstehen, ohne den Code ansehen zu müssen.
Die Documenso OpenAPI-Spezifikation ist live hier.
Glücklicherweise macht es ts-rest
nahtlos, die OpenAPI-Spezifikation zu generieren.
Dann nimmt die Swagger UI die OpenAPI-Spezifikation als Prop und generiert die Dokumentation. Der folgende Code zeigt die Komponente, die für die Generierung der Dokumentation verantwortlich ist.
Zuletzt erstellen wir einen API-Endpunkt, um die Swagger-Dokumentation anzuzeigen. Der folgende Code importiert dynamisch die OpenApiDocsPage
-Komponente und zeigt sie an.
Sie können auf die Dokumentation zugreifen und damit experimentieren unter documenso.com/api/v1/openapi. Sie sollten eine Seite sehen, die wie im Screenshot unten dargestellt aussieht.
Dieser Artikel zeigt, wie man Swagger-Dokumentation für eine Next.js-API generiert.
So haben wir also die erste Iteration der öffentlichen API erstellt, nachdem wir alle Einschränkungen und die aktuellen Bedürfnisse berücksichtigt haben. Der GitHub-Pull-Request für die API ist auf GitHub öffentlich verfügbar. Sie können ihn in Ihrem eigenen Tempo durchsehen.
Fazit
Die aktuelle Architektur und Vorgehensweise funktionieren gut für unsere aktuellen Anforderungen und Bedürfnisse. Dennoch wird sich unsere Architektur und Vorgehensweise mit unserem Wachstum und unserer Weiterentwicklung voraussichtlich anpassen müssen. Wir überwachen regelmäßig die API-Nutzung und -Leistung und sammeln Feedback von Benutzern. Dies ermöglicht es uns, Verbesserungsmöglichkeiten zu finden, die Bedürfnisse unserer Benutzer zu verstehen und fundierte Entscheidungen über die nächsten Schritte zu treffen.