Platform

Open Source

Documenso SDK und API V2

Documenso SDK und API V2

31.01.2025

Documenso Typescript SDK
Documenso Typescript SDK

Zu Beginn des Jahres 2024 haben wir beschlossen, eine öffentliche API zu erstellen, um den Nutzern die Möglichkeit zu geben, Documenso in ihre Plattformen und maßgeschneiderte Workflows zu integrieren.

Als wir die Version 1 der API implementierten, entschieden wir uns, die öffentliche API von der internen zu entkoppeln, was uns mehr Flexibilität und Kontrolle verschaffte. Sie können mehr über die Architektur der Version 1 im Artikel „Building the Documenso Public API - The Why and How“ lesen.

Innerhalb weniger Monate nach der Veröffentlichung wurden die Herausforderungen bei der Wartung beider APIs offensichtlich. Jede Änderung erforderte eine Implementierung in beiden APIs sowie doppelte Testbemühungen. Protokollierung, Debugging und allgemeine Wartung wurden zunehmend komplexer.

Als sich diese Herausforderungen häuften, bemerkten wir, dass wir allmählich vernachlässigten, die öffentliche API mit den neuesten Änderungen auf dem Laufenden zu halten.

Die API neu überdenken und unsere Richtung

Im Laufe der Zeit begannen wir, Ideen zu erkunden, wie wir mit den APIs vorankommen könnten.

Wir fragten uns - warum nicht eine einzige API erstellen, die sowohl unsere Anwendung als auch die Öffentlichkeit konsumieren können? Das hatten wir bereits beim Bau der Version 1 in Betracht gezogen, uns jedoch wegen des Wunsches nach mehr Flexibilität und Kontrolle dagegen entschieden.

Eine Kern-API würde es uns ermöglichen:

  • Unsere Ressourcen darauf zu konzentrieren, eine einzige API zu erstellen

  • Funktionen sofort sowohl an die App als auch an die öffentliche API zu liefern

  • Die Testabdeckung zu verbessern

  • Protokollierung, Debugging und Wartung an einem Ort zu erledigen

Um das zu erreichen, mussten wir entscheiden, ob wir komplett auf ts-rest oder tRPC als Rückgrat unserer Kern-API setzen.

Nach sorgfältiger Überlegung haben wir uns für tRPC entschieden, weil:

  1. Die Erstellung einer OpenAPI-Spezifikation mit tRPC viel einfacher war (wenn auch weniger flexibel)

  2. Die Arbeit mit tRPC für Entwickler viel einfacher ist

  3. Unsere gesamte API bereits in tRPC ist, sodass wir nichts portieren müssten

SDKs und bessere Dokumentation

Der nächste Schritt nach der Entscheidung, die Version 2 der API zu erstellen, war es, eine bessere Erfahrung für unsere Nutzer bei der Verwendung der API zu bieten.

Dafür haben wir Speakeasy ausgewählt, um SDKs zu erstellen, und Scalar für eine bessere API-Dokumentation mit interaktiven Beispielen.

Version 2 API aufbauen

Vorbereitung

Bevor wir mit der Arbeit an der Version 2 API begannen, hatten wir einige Ziele, die wir erreichen wollten, um eine bessere Erfahrung für Entwickler und API-Nutzer zu bieten

  • Die Typsicherheit zu verbessern

  • Konsistente Antworten

  • Sinnvolle Benennungen

Um dies zu erreichen, haben wir unsere Datenbank umgestaltet und diese Prisma-Generatoren implementiert, um die erforderlichen Typen und Schemata für unsere Datenbank bereitzustellen:

Die Kombination dieser Generatoren ermöglicht es uns, Zod-Schemata direkt aus unseren Prisma-Modellen zu exportieren, was uns wiederum ermöglicht, stark typisierte Antworten für die API zu erhalten.

model Document {
  externalId     String?    /// @zod.string.describe("A custom external...")
  userId         Int        /// @zod.number.describe("The ID of the...")
  authOptions    Json?      /// [DocumentAuthOptions] @zod.custom.use(...)
  ...
}

Implementierung

Um eine REST-API aus tRPC zu erstellen, haben wir ein Open-Source-Paket namens trpc-to-openapi genutzt, das es uns ermöglicht:

  • Ein REST-API-Endpunkt zu erstellen

  • Eine OpenAPI-Spezifikation zu generieren, die wir zur Erstellung von SDKs verwenden werden

Aktuell befindet sich die Version 2 API noch in der Beta-Phase, sodass die Struktur und der Code Änderungen unterliegen werden. Unsere Struktur ähnelt sehr der Art und Weise, wie tRPC-Apps normalerweise erstellt werden:

  1. Router - Kombiniert alle Routen

  2. Route - Verarbeitet die Anfrage, die Antwort und den Endpunkt

  3. Implementierung - Verarbeitet den eigentlichen Code, der für das Ereignis erforderlich ist

Router

// File: packages/trpc/router/document.ts

export const documentRouter = router({
  findDocuments: findDocumentRoute,
  getDocument: getDocumentRoute,
  createDocument: createDocumentRoute,
  deleteDocument: deleteDocumentRoute,
})

Route

Jede Route verwaltet die OpenAPI-Spezifikation und die Anforderungs-/Antwortschemata.

Dies ermöglicht uns eine klare Trennung zwischen der API-Anfrage und der Implementierung, die als Nächstes besprochen wird.

// File: packages/trpc/router/routes/documents/get-document.ts

export const ZGetDocumentRequestSchema = z.object({
  documentId: z.string(),
});

export const ZGetDocumentResponseSchema = ZDocumentSchema;

export const getDocumentRoute = authenticatedProcedure
  .meta({
    openapi: {
      method: 'GET',
      path: '/document/{documentId}',
      summary: 'Get document',
      description: 'Get a document by ID',
      tags: ['Documents'],
    },
  })
  .input(ZGetDocumentRequestSchema)
  .output(ZGetDocumentResponseSchema)
  .query(async ({ input, ctx }) => {
    // Any specfic logic required to map the request to the implementation.

    // Implementation will go here.
    return getDocument();
  });

Implementierung

Durch das Entkoppeln der Implementierung von der tatsächlichen Anfrage können wir Code zwischen verschiedenen Ereignissen wiederverwenden. Es kann lockerer und flexibler sein, da es nicht direkt an die API-Anfrage gebunden sein muss.

Durch diese Vorgehensweise können wir dieses Code-Stück dort erweitern und wiederverwenden, wo es nötig ist, beispielsweise während Server-Side-Rendering oder Middleware-Callbacks.

// File: packages/lib/server-only/document/get-document.ts
export const getDocument = () => {
  // Implementation.
}

SDK

Durch die Verwendung von trpc-to-openapi zur Generierung einer OpenAPI-Spezifikation können wir 3rd-Party-Dienste nutzen, um ein SDK für uns zu erstellen.

Es gibt mehrere Möglichkeiten, ein SDK basierend auf OpenAPI zu generieren; wir haben die folgenden Lösungen untersucht:

Am Ende haben wir uns für Speakeasy entschieden, da es die beste Kompatibilität und Dokumentation mit unserer Lösung zu haben scheint.

Das resultierende SDK ist vollständig typensicher und einfach zu verwenden.

import { Documenso } from "@documenso/sdk-typescript";

const documenso = new Documenso({
  apiKey: "<API_KEY>",
});

await documenso.documents.createV0({
  title: "Hello World!",
});

Was kommt als Nächstes?

Die Version 2 API befindet sich derzeit in der Beta-Phase, und wir arbeiten mit der Community daran, sie zu verbessern und sie für die stabile Veröffentlichung vorzubereiten. Probieren Sie die Version 2 aus und teilen Sie uns Ihr Feedback oder Ihre Vorschläge mit.

Ressourcen:

Wenn Sie eine direkt Diskussion führen möchten, treten Sie bitte unserem Discord-Server bei.