Platform

Open Source

Création du SDK et de l'API Documenso V2

Création du SDK et de l'API Documenso V2

31 janv. 2025

Documenso Typescript SDK
Documenso Typescript SDK

Au début de 2024, nous avons décidé de construire une API publique pour fournir aux utilisateurs un moyen d'intégrer Documenso dans leurs plateformes et leurs flux de travail personnalisés.

Lorsque nous avons implémenté la v1 de l'API, nous avons décidé de découpler l'API publique de l'API interne, ce qui nous a donné plus de flexibilité et de contrôle. Vous pouvez en savoir plus sur l'architecture de la v1 dans l'article « Construire l'API publique de Documenso - Le Pourquoi et Le Comment ».

Quelques mois après la sortie, les défis de la maintenance des deux API sont devenus apparents. Chaque changement nécessitait une mise en œuvre dans les deux API, ainsi que des efforts de test en double. La journalisation, le débogage et la maintenance générale sont devenus de plus en plus complexes.

À mesure que ces défis se cumulaient, nous avons constaté que nous négligions progressivement de garder l'API publique à jour avec les dernières modifications.

Repenser l'API et notre direction

Au fil du temps, nous avons commencé à explorer des idées sur la manière d'avancer avec les API.

Nous nous sommes demandé - pourquoi ne pas construire une API unique que notre application et le public peuvent consommer ? C'est quelque chose que nous avons envisagé lors de la construction de la v1, mais nous y avons renoncé en raison du désir d'avoir plus de flexibilité et de contrôle.

Une API centrale nous permettrait :

  • De concentrer nos ressources sur la construction d'une API unique

  • D'envoyer immédiatement des fonctionnalités à l'application et à l'API publique

  • D'améliorer la couverture des tests

  • De gérer la journalisation, le débogage et la maintenance à un seul endroit

Pour ce faire, nous devions décider si nous allions tout miser sur ts-rest ou tRPC comme base de notre API centrale.

Après mûre réflexion, nous avons décidé d'opter pour tRPC car :

  1. Générer une spécification OpenAPI était beaucoup plus facile avec tRPC (bien que moins flexible)

  2. Travailler avec tRPC est beaucoup plus facile pour les développeurs

  3. Notre API entière est déjà dans tRPC, donc nous n'aurions rien à porter

SDK et Meilleure Documentation

Le prochain pas après avoir décidé de construire la v2 de l'API était de fournir une meilleure expérience à nos utilisateurs lors de l'utilisation de l'API.

Pour ce faire, nous avons choisi Speakeasy pour construire des SDK, et Scalar pour une meilleure documentation API avec des exemples interactifs.

Construction de l'API v2

Préparation

Avant de commencer le travail sur l'API v2, nous avions des objectifs que nous souhaitions atteindre pour offrir une meilleure expérience aux développeurs et aux consommateurs d'API

  • Améliorer la sécurité des types

  • Réponses cohérentes

  • Nommage sensé

Pour y parvenir, nous avons refactorisé notre base de données et mis en œuvre ces générateurs Prisma pour fournir les types et schémas requis pour notre base de données :

En combinant ces générateurs, nous pouvons exporter des schémas Zod directement à partir de nos modèles Prisma, ce qui nous permet d'avoir des réponses fortement typées pour l'API.

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(...)
  ...
}

Mise en œuvre

Pour construire une API REST à partir de tRPC, nous avons utilisé un package open source appelé trpc-to-openapi qui nous permet de :

  • Créer un point de terminaison API REST

  • Générer une spécification OpenAPI, que nous utiliserons pour générer des SDK

Actuellement, l'API v2 est encore en bêta, donc la structure et le code sont susceptibles de changer. Notre structure est très similaire à celle des applications tRPC normalement créées :

  1. Routeur - Gère la combinaison de toutes les routes

  2. Route - Gère la demande, la réponse et le point de terminaison

  3. Mise en œuvre - Gère le code réel requis pour l'événement

Routeur

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

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

Route

Chaque route gérera la spécification OpenAPI et les schémas de demande/réponse.

Cela nous permet d'avoir une division claire entre la demande API et la mise en œuvre, ce qui sera discuté ensuite.

// 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();
  });

Mise en œuvre

En découplant la mise en œuvre de la demande réelle, cela nous permet de réutiliser du code entre différents événements. Cela peut être plus lâche et flexible car cela n'a pas besoin de s'adhérer directement à la demande API.

Faire cela nous permet d'étendre et de réutiliser ce morceau de code là où c'est nécessaire, comme lors du rendu côté serveur ou des rappels de middleware.

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

SDK

En utilisant trpc-to-openapi pour générer une spécification OpenAPI, nous pouvons utiliser des services tiers pour générer un SDK pour nous.

Il existe plusieurs façons de générer un SDK basé sur OpenAPI, nous avons examiné les solutions suivantes :

À la fin, nous avons décidé de choisir Speakeasy, car il semblait avoir la meilleure compatibilité et documentation avec notre solution.

Le SDK qui en résulte est entièrement typé et facile à utiliser.

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

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

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

Quelle est la suite ?

L'API v2 est actuellement en bêta, et nous travaillons avec la communauté pour l'améliorer et la préparer pour la version stable. Essayez la v2 et faites-nous savoir si vous avez des retours ou des suggestions.

Ressources :

Si vous souhaitez avoir une discussion plus directe, veuillez rejoindre notre serveur Discord.