Desarrollo
API
8 mar 2024
Este artículo cubre el proceso de construcción de la API pública para Documenso. Comienza explicando por qué se necesitaba la API para una empresa de firma de documentos digitales en primer lugar. Luego, se adentrará en los pasos que tomamos para construirla. Por último, presentará los requisitos que debíamos cumplir y las limitaciones con las que tuvimos que trabajar.
Por qué la API pública
Decidimos construir la API pública para abrir una nueva forma de interactuar con Documenso. Si bien la aplicación web hace bien su trabajo, hay casos de uso en los que no es suficiente. En esos casos, los usuarios pueden querer interactuar con la plataforma de manera programática. Normalmente, eso es para integrar Documenso con otras aplicaciones.
Con la nueva API pública eso ya es posible. Puedes integrar las funcionalidades de Documenso dentro de otras aplicaciones para automatizar tareas, crear soluciones personalizadas y construir flujos de trabajo personalizados, por nombrar solo algunos.
La API proporciona 12 puntos finales en el momento de escribir este artículo:
- (GET)
/api/v1/documents
- recuperar todos los documentos- (POST)
/api/v1/documents
- subir un nuevo documento y obtener una URL prefirmada- (GET)
/api/v1/documents/{id}
- obtener un documento específico- (DELETE)
/api/v1/documents/{id}
- eliminar un documento específico- (POST)
/api/v1/templates/{templateId}/create-document
- crear un nuevo documento a partir de una plantilla existente- (POST)
/api/v1/documents/{id}/send
- enviar un documento para firma- (POST)
/api/v1/documents/{id}/recipients
- crear un destinatario de documento- (PATCH)
/api/v1/documents/{id}/recipients/{recipientId}
- actualizar los detalles de un destinatario de documento- (DELETE)
/api/v1/documents/{id}/recipients/{recipientId}
- eliminar un destinatario específico de un documento- (POST)
/api/v1/documents/{id}/fields
- crear un campo para un documento- (PATCH)
/api/v1/documents/{id}/fields
- actualizar los detalles de un campo de documento- (DELETE)
/api/v1/documents/{id}/fields
- eliminar un campo de un documento
Consulta la documentación de la API.
Además, también nos permite mejorar la plataforma al traer otras integraciones a Documenso, como Zapier.
En conclusión, la nueva API pública amplía las capacidades de Documenso, proporciona más flexibilidad para los usuarios y abre un mundo más amplio de posibilidades.
Elegir el enfoque y la tecnología correctos
Una vez que decidimos construir la API, tuvimos que elegir el enfoque y las tecnologías a utilizar. Había 2 opciones:
Construir una aplicación adicional
Lanzar la API en la base de código existente
1. Construir una aplicación adicional
Eso significaría crear una nueva base de código y construir la API desde cero. Tener una aplicación separada para la API resultaría en beneficios como:
respuestas de menor latencia
soportar cargas de campo más grandes
separación entre las aplicaciones (Documenso y la API)
personalización y flexibilidad
pruebas y depuración más fáciles
Este enfoque tiene beneficios significativos. Sin embargo, una gran desventaja es que requiere recursos adicionales.
Tendríamos que gastar mucho tiempo solo en lo básico, como construir y configurar el servidor básico. Después de eso, gastaríamos tiempo implementando los puntos finales y la autorización, entre otras cosas. Cuando la construcción esté lista, habrá otra aplicación que implementar y gestionar. Todo esto estiraría nuestros ya limitados recursos.
Entonces, nos preguntamos si había otra manera de hacerlo sin sacrificar la calidad de la API y la experiencia del desarrollador.
2. Lanzar la API en la base de código existente
La otra opción era lanzar la API en la base de código existente. En lugar de escribir todo desde cero, podríamos utilizar la mayor parte de nuestro código existente.
Como estamos utilizando tRPC para nuestra API interna (backend), buscamos soluciones que funcionen bien con tRPC. Redujimos las opciones a:
Ambas tecnologías te permiten construir APIs públicas. La tecnología trpc-openapi
te permite convertir fácilmente procedimientos de tRPC en puntos finales REST. Es más como un complemento para tRPC.
Por otro lado, `ts-rest` es más una solución autónoma. ts-rest
te permite crear un contrato para la API, que puede ser utilizado tanto en el cliente como en el servidor. Puedes consumir e implementar el contrato en tu aplicación, proporcionando así seguridad de tipo de extremo a extremo y un cliente similar a RPC.
Puedes ver una comparativa entre trpc-openapi y ts-rest aquí.
Entonces, la principal diferencia entre los 2 es que trpc-openapi
es como un complemento que extiende las capacidades de tRPC, mientras que ts-rest
proporciona las herramientas para construir una API autónoma.
Nuestra elección
Después de analizar y comparar las 2 opciones, decidimos optar por ts-rest
debido a sus beneficios. Aquí hay un párrafo de la documentación de ts-rest
que da en el clavo:
tRPC tiene muchos complementos para resolver este problema al mapear la implementación de la API a una API similar a REST, sin embargo, estos enfoques son a menudo un poco incómodos y reducen la seguridad del sistema en general, ts-rest realiza este trabajo pesado en las implementaciones del cliente y del servidor en lugar de requerir una segunda capa de abstracción y puntos finales de API que deben definirse.
Requisitos de la API
Definimos los siguientes requisitos para la API:
La API debe utilizar versionado basado en rutas (por ejemplo, `/v1`)
El sistema debe usar tokens portadores para la autenticación de la API
El token de la API debe ser una cadena aleatoria de 32 a 40 caracteres
El sistema debe hashear el token y almacenar el valor hasheado
El sistema solo debe mostrar el token de API cuando se crea
La API debe tener documentación autogenerada como Swagger
Los usuarios deben poder crear una clave de API
Los usuarios deben poder elegir un nombre para el token
Los usuarios deben poder elegir una fecha de expiración para el token
El usuario debe poder elegir entre 7 días, 1 mes, 3 meses, 6 meses, 12 meses, nunca
El sistema debe mostrar todos los tokens del usuario en la página de configuración
El sistema debe mostrar el nombre del token, la fecha de creación, la fecha de expiración y un botón de eliminar
Los usuarios deben poder eliminar una clave de API
Los usuarios deben poder recuperar todos los documentos de su cuenta
Los usuarios deben poder subir un nuevo documento
Los usuarios deben recibir una URL prefirmada de S3 después de una carga exitosa
Los usuarios deben poder recuperar un documento específico de su cuenta por su id
Los usuarios deben poder eliminar un documento específico de su cuenta por su id
Los usuarios deben poder crear un nuevo documento a partir de una plantilla de documento existente
Los usuarios deben poder enviar un documento para firma a 1 o más destinatarios
Los usuarios deben poder crear un destinatario para un documento
Los usuarios deben poder actualizar los detalles de un destinatario
Los usuarios deben poder eliminar un destinatario de un documento
Los usuarios deben poder crear un campo (por ejemplo, firma, correo electrónico, nombre, fecha) para un documento
Los usuarios deben poder actualizar un campo para un documento
Los usuarios deben poder eliminar un campo de un documento
Limitaciones
También enfrentamos las siguientes limitaciones al desarrollar la API:
1. Recursos
Los recursos limitados fueron una de las principales limitaciones. Somos una nueva startup con un equipo relativamente pequeño. Construir y mantener una aplicación adicional agotaría nuestros recursos limitados.
2. Pila tecnológica
Otra limitación fue la pila tecnológica. Nuestra pila tecnológica incluye TypeScript, Prisma y tRPC, entre otros. También usamos Vercel para la hospedaje.
Como resultado, queríamos usar tecnologías con las que nos sintiéramos cómodos. Esto nos permitió aprovechar nuestro conocimiento existente y aseguró consistencia en nuestras aplicaciones.
El uso de tecnologías familiares también significó que podríamos desarrollar la API más rápido, ya que no tendríamos que pasar tiempo aprendiendo nuevas tecnologías. También podríamos aprovechar el código y las herramientas existentes utilizadas en nuestra aplicación principal.
Es importante mencionar que esta no es una decisión permanente. Estamos abiertos a mover la API a otra base de código/pila tecnológica cuando tenga sentido (por ejemplo, la API se utiliza ampliamente y necesita un mejor rendimiento).
3. Cargas de archivos
Debido a nuestra arquitectura actual, soportamos cargas de archivos con un tamaño máximo de 50 MB. Para sortear esto, creamos un paso adicional para cargar documentos.
Los usuarios hacen una solicitud POST al punto final /api/v1/documents
y la API responde con una URL prefirmada de S3. Los usuarios luego hacen una segunda solicitud a la URL prefirmada con su documento.
Cómo construimos la API
Nuestra base de código es un monorepo, por lo que creamos un nuevo paquete API en el directorio packages
. Contiene tanto la implementación de la API como su documentación. Los 2 bloques principales de la implementación consisten en el contrato de API y el código para los puntos finales de la API.
En pocas palabras, el contrato de API define la estructura de la API, el formato de las solicitudes y respuestas, cómo autenticar las llamadas a la API, los puntos finales disponibles y sus verbos HTTP asociados. Puedes explorar el contrato de API en GitHub.
Luego, está la parte de implementación, que es el código real para cada punto final definido en el contrato de API. La implementación es donde el contrato de API cobra vida y se hace funcional.
Tomemos el punto final /api/v1/documents
como ejemplo.
El contrato de API especifica las siguientes cosas para getDocuments
:
el método de solicitud HTTP permitido es GET, por lo que intentar hacer una solicitud POST, por ejemplo, resulta en un error
la ruta es
/api/v1/documents
los parámetros de consulta que el usuario puede pasar con la solicitud
en este caso -
page
yperPage
las respuestas permitidas y su esquema
200
devuelve un objeto que contiene un array de todos los documentos y un campototalPages
, que es autoexplicativo401
devuelve un objeto con un mensaje como "No autorizado"404
devuelve un objeto con un mensaje como "No encontrado"
La implementación de este punto final necesita coincidir completamente con el contrato; de lo contrario, ts-rest
se quejará, y tu API podría no funcionar como se espera.
La función getDocuments
del archivo implementation.ts
se ejecuta cuando el usuario accede al punto final.
También hay un middleware, authenticatedMiddleware
, que maneja la autenticación para las solicitudes de API. Asegura que el token de API exista y que el token utilizado tenga los privilegios apropiados para el recurso al que accede.
Así es como funcionan los otros puntos finales también. El código difiere, pero los principios son los mismos. Puedes explorar la implementación de la API y el código del middleware en GitHub.
Documentación
Para la documentación, decidimos usar Swagger UI, que genera automáticamente la documentación a partir de la especificación de OpenAPI.
La especificación de OpenAPI describe una API que contiene los puntos finales disponibles y sus métodos de solicitud HTTP, métodos de autenticación, etc. Su propósito es ayudar tanto a máquinas como a humanos a entender la API sin tener que mirar el código.
La especificación de OpenAPI de Documenso está disponible aquí.
Afortunadamente, ts-rest
lo hace fácil para generar la especificación de OpenAPI.
Luego, Swagger UI toma la especificación de OpenAPI como una prop y genera la documentación. El código a continuación muestra el componente responsable de generar la documentación.
Por último, creamos un punto final de API para mostrar la documentación de Swagger. El código a continuación importa dinámicamente el componente OpenApiDocsPage
y lo muestra.
Puedes acceder y experimentar con la documentación en documenso.com/api/v1/openapi. Deberías ver una página como la que se muestra en la captura de pantalla a continuación.
Este artículo muestra cómo generar documentación Swagger para una API de Next.js.
Así que, así es como dimos forma a la primera iteración de la API pública después de tener en cuenta todas las limitaciones y las necesidades actuales. La solicitud de extracción de GitHub para la API está disponible públicamente en GitHub. Puedes revisarla a tu propio ritmo.
Conclusión
La arquitectura y el enfoque actuales funcionan bien para nuestra etapa y necesidades actuales. Sin embargo, a medida que continuamos creciendo y evolucionando, nuestra arquitectura y enfoque probablemente necesitarán adaptarse. Monitoreamos el uso y el rendimiento de la API regularmente y recopilamos comentarios de los usuarios. Esto nos permite encontrar áreas de mejora, comprender las necesidades de nuestros usuarios y tomar decisiones informadas sobre los próximos pasos.