Platform

Código Abierto

Construyendo Documenso SDK y API V2

Construyendo Documenso SDK y API V2

31 ene 2025

Documenso Typescript SDK
Documenso Typescript SDK

Al comienzo de 2024, decidimos construir una API pública para proporcionar a los usuarios una forma de integrar Documenso en sus plataformas y flujos de trabajo personalizados.

Cuando implementamos la v1 de la API, decidimos desacoplar la API pública de la interna, lo que nos brindó más flexibilidad y control. Puedes leer más sobre la arquitectura de la v1 en el artículo “Construyendo la API Pública de Documenso - El Porqué y El Cómo”.

En cuestión de meses tras el lanzamiento, los desafíos de mantener ambas APIs se hicieron evidentes. Cada cambio requería implementación en ambas APIs, junto con esfuerzos de pruebas duplicados. El registro, la depuración y el mantenimiento general se volvieron cada vez más complejos.

Conforme se acumulaban estos desafíos, nos encontramos descuidando gradualmente la actualización de la API pública con los últimos cambios.

Repensando la API y nuestra dirección

A medida que pasaba el tiempo, comenzamos a explorar ideas sobre cómo avanzar con las APIs.

Nos preguntamos: ¿por qué no construir una API singular que tanto nuestra aplicación como el público puedan consumir? Es algo que consideramos al construir la v1, pero decidimos no hacerlo debido al deseo de más flexibilidad y control.

Una API central nos permitiría:

  • Concentrar nuestros recursos en construir una sola API

  • Enviar características de inmediato a la aplicación y la API pública

  • Mejorar la cobertura de pruebas

  • Manejar el registro, la depuración y el mantenimiento en un solo lugar

Para hacer eso, tuvimos que decidir si ir a fondo en ts-rest o tRPC como base de nuestra API central.

Después de una cuidadosa consideración, decidimos optar por tRPC porque:

  1. Generar una especificación OpenAPI fue mucho más fácil usando tRPC (aunque menos flexible)

  2. Trabajar con tRPC es mucho más fácil para los desarrolladores

  3. Nuestra API completa ya está en tRPC, por lo que no necesitaríamos portar nada

SDKs y Mejor Documentación

El siguiente paso después de decidir construir la v2 de la API fue proporcionar una mejor experiencia para nuestros usuarios al utilizar la API.

Para ello, elegimos Speakeasy para construir SDKs y Scalar para una mejor documentación de la API con ejemplos interactivos.

Construyendo la API v2

Preparación

Antes de comenzar el trabajo en la API v2, teníamos algunos objetivos que queríamos lograr para proporcionar una mejor experiencia a los desarrolladores y consumidores de la API

  • Mejorar la seguridad de tipos

  • Respuestas consistentes

  • Nombres sensatos

Para lograr esto, refactorizamos nuestra base de datos e implementamos estos generadores de Prisma para proporcionar los tipos y esquemas requeridos para nuestra base de datos:

Combinar estos generadores nos permite exportar esquemas Zod directamente desde nuestros modelos de Prisma, lo que a su vez nos permite tener respuestas fuertemente tipadas para la 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(...)
  ...
}

Implementación

Para construir una API REST desde tRPC, utilizamos un paquete de código abierto llamado trpc-to-openapi que nos permite:

  • Crear un endpoint de API REST

  • Generar una especificación OpenAPI, que utilizaremos para generar SDKs

Actualmente, la API v2 sigue en beta, por lo que la estructura y el código estarán sujetos a cambios. Nuestra estructura es muy similar a cómo se crean normalmente las aplicaciones tRPC:

  1. Router - Se encarga de combinar todas las rutas

  2. Ruta - Maneja la solicitud, la respuesta y el endpoint

  3. Implementación - Maneja el código real requerido para el evento

Router

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

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

Ruta

Cada ruta gestionará la especificación OpenAPI y los esquemas de solicitud/respuesta.

Esto nos permite tener una clara división entre la solicitud de la API y la implementación, que se discutirá a continuación.

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

Implementación

Al desacoplar la implementación de la solicitud real, nos permite reutilizar el código entre diferentes eventos. Puede ser más laxo y flexible ya que no necesita adherirse directamente a la solicitud de la API.

Hacer esto nos permite extender y reutilizar este fragmento de código donde sea necesario, como durante la representación del lado del servidor o las devoluciones de llamada de middleware.

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

SDK

Al usar trpc-to-openapi para generar una especificación OpenAPI, podemos usar servicios de terceros para generar un SDK para nosotros.

Hay múltiples formas de generar un SDK basado en OpenAPI, investigamos las siguientes soluciones:

Al final, decidimos optar por Speakeasy, ya que parecía tener la mejor compatibilidad y documentación con nuestra solución.

El SDK resultante es completamente seguro en tipos y fácil de usar.

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

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

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

¿Qué sigue?

La API v2 está actualmente en beta, y estamos trabajando con la comunidad para mejorarla y prepararla para el lanzamiento estable. Prueba la v2 y háznos saber si tienes comentarios o sugerencias.

Recursos:

Si deseas tener una discusión más directa, únete a nuestro servidor de Discord.

Documenso

© 2024 Documenso, Inc. Todos los derechos reservados.

Documenso

© 2024 Documenso, Inc. Todos los derechos reservados.

Documenso

© 2024 Documenso, Inc. Todos los derechos reservados.