Développement
API
8 mars 2024
Cet article couvre le processus de construction de l'API publique pour Documenso. Il commence par expliquer pourquoi l'API était nécessaire pour une entreprise de signature de documents numériques en premier lieu. Ensuite, il plongera dans les étapes que nous avons suivies pour la construire. Enfin, il présentera les exigences que nous devions respecter et les contraintes dans lesquelles nous devions travailler.
Pourquoi l'API publique
Nous avons décidé de construire l'API publique pour ouvrir une nouvelle façon d'interagir avec Documenso. Bien que l'application web fasse bien le travail, il existe des cas d'utilisation où cela ne suffit pas. Dans ces cas, les utilisateurs peuvent souhaiter interagir avec la plateforme de manière programmatique. En général, c'est pour intégrer Documenso avec d'autres applications.
Avec la nouvelle API publique, cela est maintenant possible. Vous pouvez intégrer les fonctionnalités de Documenso au sein d'autres applications pour automatiser des tâches, créer des solutions personnalisées et construire des flux de travail personnalisés, pour n'en nommer que quelques-uns.
L'API fournit 12 points de terminaison au moment d'écrire cet article :
- (GET)
/api/v1/documents
- récupérer tous les documents- (POST)
/api/v1/documents
- télécharger un nouveau document et obtenir une URL présignée- (GET)
/api/v1/documents/{id}
- récupérer un document spécifique- (DELETE)
/api/v1/documents/{id}
- supprimer un document spécifique- (POST)
/api/v1/templates/{templateId}/create-document
- créer un nouveau document à partir d'un modèle existant- (POST)
/api/v1/documents/{id}/send
- envoyer un document pour signature- (POST)
/api/v1/documents/{id}/recipients
- créer un destinataire de document- (PATCH)
/api/v1/documents/{id}/recipients/{recipientId}
- mettre à jour les détails d'un destinataire de document- (DELETE)
/api/v1/documents/{id}/recipients/{recipientId}
- supprimer un destinataire spécifique d'un document- (POST)
/api/v1/documents/{id}/fields
- créer un champ pour un document- (PATCH)
/api/v1/documents/{id}/fields
- mettre à jour les détails d'un champ de document- (DELETE)
/api/v1/documents/{id}/fields
- supprimer un champ d'un document
Consultez la documentation de l'API.
De plus, cela nous permet également d'améliorer la plateforme en apportant d'autres intégrations à Documenso, comme Zapier.
En conclusion, la nouvelle API publique étend les capacités de Documenso, offre plus de flexibilité aux utilisateurs et ouvre un monde de possibilités plus large.
Choisir la bonne approche et la bonne technologie
Une fois que nous avons décidé de construire l'API, nous avons dû choisir l'approche et les technologies à utiliser. Il y avait 2 options :
Construire une application supplémentaire
Lancer l'API dans le code de base existant
1. Construire une application supplémentaire
Cela signifierait créer un nouveau code de base et construire l'API depuis zéro. Avoir une application séparée pour l'API entraînerait des avantages tels que :
réponses à faible latence
soutenir des téléchargements de champs plus volumineux
séparation entre les applications (Documenso et l'API)
personnalisabilité et flexibilité
tests et débogage plus faciles
Cette approche a des avantages significatifs. Cependant, un inconvénient majeur est qu'elle nécessite des ressources supplémentaires.
Nous devrions passer beaucoup de temps juste sur les éléments de base, comme la construction et la configuration du serveur de base. Après cela, nous passerions du temps à mettre en œuvre les points de terminaison et l'autorisation, entre autres. Lorsque la construction est terminée, il y aura une autre application à déployer et à gérer. Tout cela mettrait à l'épreuve nos ressources déjà limitées.
Ainsi, nous nous sommes demandé s'il existait un autre moyen de le faire sans sacrifier la qualité de l'API et l'expérience des développeurs.
2. Lancer l'API dans le code de base existant
L'autre option était de lancer l'API dans le code de base existant. Plutôt que d'écrire tout depuis zéro, nous pourrions utiliser la plupart de notre code existant.
Puisque nous utilisons tRPC pour notre API interne (backend), nous avons recherché des solutions qui fonctionnent bien avec tRPC. Nous avons réduit les choix à :
Les deux technologies vous permettent de construire des API publiques. La technologie trpc-openapi
vous permet de transformer facilement les procédures tRPC en points de terminaison REST. C'est plutôt comme un plugin pour tRPC.
D'autre part, `ts-rest` est plus une solution autonome. ts-rest
vous permet de créer un contrat pour l'API, qui peut être utilisé à la fois sur le client et le serveur. Vous pouvez consommer et mettre en œuvre le contrat dans votre application, garantissant ainsi une sécurité de type de bout en bout et un client similaire à RPC.
Vous pouvez voir une comparaison entre trpc-openapi et ts-rest ici.
Ainsi, la principale différence entre les 2 est que trpc-openapi
est comme un plugin qui étend les capacités de tRPC, tandis que ts-rest
fournit les outils pour construire une API autonome.
Notre choix
Après avoir analysé et comparé les 2 options, nous avons décidé d'opter pour ts-rest
en raison de ses avantages. Voici un paragraphe de la documentation de ts-rest
qui touche juste :
tRPC dispose de nombreux plugins pour résoudre ce problème en mappant l'implémentation de l'API à une API de type REST, cependant, ces approches sont souvent un peu maladroites et réduisent la sécurité du système dans son ensemble, ts-rest effectue ce travail lourd dans les implémentations client et serveur plutôt que d'exiger une seconde couche d'abstraction et que les points de terminaison de l'API soient définis.
Exigences de l'API
Nous avons défini les exigences suivantes pour l'API :
L'API doit utiliser un versionnement basé sur le chemin (par exemple, `/v1`)
Le système doit utiliser des jetons porteur pour l'authentification de l'API
Le jeton API doit être une chaîne aléatoire de 32 à 40 caractères
Le système doit hacher le jeton et stocker la valeur hachée
Le système doit n'afficher le jeton API que lorsqu'il est créé
L'API doit avoir une documentation générée automatiquement comme Swagger
Les utilisateurs doivent pouvoir créer une clé API
Les utilisateurs doivent pouvoir choisir un nom de jeton
Les utilisateurs doivent pouvoir choisir une date d'expiration pour le jeton
L'utilisateur doit pouvoir choisir entre 7 jours, 1 mois, 3 mois, 6 mois, 12 mois, jamais
Le système doit afficher tous les jetons de l'utilisateur sur la page des paramètres
Le système doit afficher le nom du jeton, la date de création, la date d'expiration et un bouton de suppression
Les utilisateurs doivent pouvoir supprimer une clé API
Les utilisateurs doivent pouvoir récupérer tous les documents de leur compte
Les utilisateurs doivent pouvoir télécharger un nouveau document
Les utilisateurs doivent recevoir une URL présignée S3 après un téléchargement réussi
Les utilisateurs doivent pouvoir récupérer un document spécifique de leur compte par son identifiant
Les utilisateurs doivent pouvoir supprimer un document spécifique de leur compte par son identifiant
Les utilisateurs doivent pouvoir créer un nouveau document à partir d'un modèle de document existant
Les utilisateurs doivent pouvoir envoyer un document pour signature à un ou plusieurs destinataires
Les utilisateurs doivent pouvoir créer un destinataire pour un document
Les utilisateurs doivent pouvoir mettre à jour les détails d'un destinataire
Les utilisateurs doivent pouvoir supprimer un destinataire d'un document
Les utilisateurs doivent pouvoir créer un champ (par exemple, signature, e-mail, nom, date) pour un document
Les utilisateurs doivent pouvoir mettre à jour un champ pour un document
Les utilisateurs doivent pouvoir supprimer un champ d'un document
Contraintes
Nous avons également été confrontés aux contraintes suivantes lors du développement de l'API :
1. Ressources
Des ressources limitées étaient l'une des principales contraintes. Nous sommes une nouvelle startup avec une équipe relativement petite. Construire et maintenir une application supplémentaire mettrait à l'épreuve nos ressources limitées.
2. Technologie stack
Une autre contrainte était la technologie stack. Notre stack technologique inclut TypeScript, Prisma, et tRPC, entre autres. Nous utilisons également Vercel pour l'hébergement.
En conséquence, nous voulions utiliser des technologies avec lesquelles nous sommes à l'aise. Cela nous a permis de tirer parti de nos connaissances existantes et a assuré la cohérence à travers nos applications.
Utiliser des technologies familières signifiait également que nous pouvions développer l'API plus rapidement, car nous n'avions pas à passer du temps à apprendre de nouvelles technologies. Nous pouvions également tirer parti du code et des outils existants utilisés dans notre application principale.
Il convient de mentionner que ce n'est pas une décision permanente. Nous sommes ouverts à déplacer l'API vers un autre code de base/stack technologique quand cela a du sens (par exemple, l'API est fortement utilisée et nécessite de meilleures performances).
3. Téléchargements de fichiers
En raison de notre architecture actuelle, nous supportons les téléchargements de fichiers d'une taille maximale de 50 Mo. Pour contourner cela, nous avons créé une étape supplémentaire pour télécharger des documents.
Les utilisateurs effectuent une requête POST à l'endpoint /api/v1/documents
et l'API répond avec une URL présignée S3. Les utilisateurs effectuent ensuite une deuxième requête à l'URL présignée avec leur document.
Comment nous avons construit l'API
Notre base de code est un monorepo, donc nous avons créé un nouveau package API dans le répertoire packages
. Il contient à la fois l'implémentation de l'API et sa documentation. Les deux principaux blocs de l'implémentation consistent en le contrat API et le code pour les points de terminaison de l'API.
En quelques mots, le contrat API définit la structure de l'API, le format des requêtes et des réponses, comment authentifier les appels d'API, les points de terminaison disponibles et leurs verbes HTTP associés. Vous pouvez explorer le contrat API sur GitHub.
Ensuite, il y a la partie implémentation, qui est le code réel pour chaque point de terminaison défini dans le contrat API. L'implémentation est là où le contrat API prend vie et devient fonctionnel.
Prenons le point de terminaison /api/v1/documents
comme exemple.
Le contrat API spécifie les éléments suivants pour getDocuments
:
la méthode HTTP de requête autorisée est GET, donc essayer de faire une requête POST, par exemple, entraîne une erreur
le chemin est
/api/v1/documents
les paramètres de requête que l'utilisateur peut passer avec la requête
dans ce cas -
page
etperPage
les réponses autorisées et leur schéma
200
renvoie un objet contenant un tableau de tous les documents et un champtotalPages
, qui est explicite401
renvoie un objet avec un message tel que "Non autorisé"404
renvoie un objet avec un message tel que "Non trouvé"
L'implémentation de ce point de terminaison doit correspondre complètement au contrat ; sinon, ts-rest
se plaignera, et votre API pourrait ne pas fonctionner comme prévu.
La fonction getDocuments
du fichier implementation.ts
s'exécute lorsque l'utilisateur accède au point de terminaison.
Il y a aussi un middleware, authenticatedMiddleware
, qui gère l'authentification des requêtes API. Il garantit que le jeton API existe et que le jeton utilisé dispose des privilèges appropriés pour la ressource qu'il accède.
C'est ainsi que les autres points de terminaison fonctionnent également. Le code diffère, mais les principes sont les mêmes. Vous pouvez explorer l'implémentation de l'API et le code du middleware sur GitHub.
Documentation
Pour la documentation, nous avons décidé d'utiliser Swagger UI, qui génère automatiquement la documentation à partir de la spécification OpenAPI.
La spécification OpenAPI décrit une API contenant les points de terminaison disponibles et leurs méthodes de requêtes HTTP, méthodes d'authentification, etc. Son objectif est d'aider à la fois les machines et les humains à comprendre l'API sans avoir à regarder le code.
La spécification OpenAPI de Documenso est en direct ici.
Heureusement, ts-rest
rend la génération de la spécification OpenAPI transparente.
Ensuite, Swagger UI prend la spécification OpenAPI comme prop et génère la documentation. Le code ci-dessous montre le composant responsable de la génération de la documentation.
Enfin, nous créons un point de terminaison API pour afficher la documentation Swagger. Le code ci-dessous importe dynamiquement le composant OpenApiDocsPage
et l'affiche.
Vous pouvez accéder et jouer avec la documentation à documenso.com/api/v1/openapi. Vous devriez voir une page comme celle montrée dans la capture d'écran ci-dessous.
Cet article montre comment générer une documentation Swagger pour une API Next.js.
Ainsi, c'est ainsi que nous avons construit la première itération de l'API publique après avoir pris en compte toutes les contraintes et les besoins actuels. La demande de tirage GitHub pour l'API est publiquement disponible sur GitHub. Vous pouvez le parcourir à votre rythme.
Conclusion
L'architecture et l'approche actuelles fonctionnent bien pour notre stade et nos besoins actuels. Cependant, à mesure que nous continuons à croître et à évoluer, notre architecture et notre approche devront probablement s'adapter. Nous surveillons régulièrement l'utilisation de l'API et ses performances et recueillons les retours des utilisateurs. Cela nous permet de trouver des domaines d'amélioration, de comprendre les besoins de nos utilisateurs et de prendre des décisions éclairées sur les prochaines étapes.