Platform
Open Source
Jan 31, 2025
At the beginning of 2024, we decided to build a public API to provide users a way to integrate Documenso into their platforms, and custom workflows.
When we implemented v1 of the API, we decided to decouple the public API from the internal one, which provided us more flexibility and control. You can read more on the v1 architecture in the “Building the Documenso Public API - The Why and How” article.
Within months of release, the challenges of maintaining both APIs became apparent. Every change required implementation across both APIs, along with duplicate testing efforts. Logging, debugging, and general maintenance became increasingly complex.
As these challenges compounded, we found ourselves gradually neglecting to keep the public API up to date with the latest changes.
Rethinking the API, and our direction
As time went on we began exploring ideas on how to move forward with the APIs.
We asked ourselves - why not build a singular API that both our application and the public can consume? It’s something we considered when building v1, but decided against it due to the desire for more flexibility and control.
A core API would allow us to:
Focus our resources on building a single API
Immediately ship features to both the app and public API
Improve test coverage
Handle logging, debugging and maintenance in one location
To do that, we had to decide whether to go all in on ts-rest or tRPC as the backbone of our core API.
After careful consideration, we decided to go with tRPC
because:
Generating an OpenAPI spec was a lot easier using tRPC (although less flexible)
Working with
tRPC
is a lot easier for developersOur whole API is already in
tRPC
, so we wouldn't need to port anything
SDKs and Better Documentation
The next step after deciding to build the v2 of the API was to provide a better experience for our users when using the API.
To do that, we chose Speakeasy to build SDKs, and Scalar for a better API documentation with interactive examples.
Building v2 API
Preparation
Prior to starting the work on v2 API, we had some goals we wanted to achieve to provide a better experience for developers and API consumers
Improve type safety
Consistent responses
Sensible naming
To achieve this, we refactored our database and implemented these Prisma generators to provide the required types and schemas for our database:
Combining these generators allows us to export Zod schemas directly from our Prisma models, which in turn enables us to have strongly-typed responses for the API.
Implementation
To build a REST API from tRPC
, we utilised an open source package called trpc-to-openapi which allows us to:
Create a REST API endpoint
Generate an OpenAPI specification, which we will use to generate SDKs
Currently the v2 API is still in beta, so the structure and code will be prone to changes. Our structure is very similar to how tRPC
apps are normally created:
Router - Handles combining all the routes
Route - Handles the request, response and endpoint
Implementation - Handles the actual code required for the event
Router
Route
Each route will manage the OpenAPI specification and the request/response schemas.
This allows us to have a clear divide between the API request and implementation, which will be discussed next.
Implementation
By decoupling the implementation from the actual request, it allows us to reuse code between different events. It can be more loose and flexible since it does not need to directly adhere to the API request.
Doing this allows us to extend and reuse this piece of code where needed, such as during server-side render or middleware callbacks.
SDK
By using trpc-to-openapi
to generate an OpenAPI specification, we can use 3rd party services to generate an SDK for us.
There are multiple ways to generate an SDK based on OpenAPI, we investigated the following solutions:
In the end, we decided to go with Speakeasy, since it seemed to have the best compatibility and documentation with our solution.
The resulting SDK is fully type-safe and easy to use.
What's next?
The v2 API is currently in beta, and we're working with the community to make it better and get it ready for the stable release. Give v2 a go and let us know if you have any feedback or suggestions.
Resources:
If you want to have a more direct discussion, please join our Discord server.