OpenAPI, formerly known as Swagger Specification, has long been the standard for specifying APIs. At OpenMeter, we've used it from the project's inception to define our APIs and generate API docs, clients, types, validators, and server-side route handlers. However, as OpenMeter expanded, managing API specifications across our growing number of products became challenging.
To improve API management, we recently adopted TypeSpec,
a new Microsoft project designed to describe APIs and generate schemas and code.
TypeSpec feels similar to TypeScript and can output OpenAPI schemas, offering
significant power for managing APIs. This article shares our experience and
learnings from adopting
TypeSpec for both OpenMeter OSS and OpenMeter Cloud.
What We Love About TypeSpec
Below are some highlights from our journey. While OpenAPI supports many of these features, TypeSpec makes them easier or more natural to implement.
Looks Like TypeScript
The JavaScript ecosystem has benefited from TypeScript's rich type system, with features like templates, interfaces, and spreads simplifying type descriptions. Bringing these concepts to API specifications makes a lot of sense, and that's precisely how TypeSpec feels. Given that both TypeSpec and TypeScript originated from Microsoft, this similarity isn't surprising.
Here's an example showcasing the flexibility TypeSpec provides:
Feels Like Code
TypeSpec feels less like writing configuration and more like writing code. IDE support, linters, and compilation make maintaining the API spec significantly easier. With imports, packages, and namespaces, you can organize the spec and extend it with external components, much like in application code.
OpenAPI Output and Emitters
We wanted to retain OpenAPI as the source for our various generators (API docs, clients, types, validators, and server-side route handlers). Fortunately, TypeSpec offers an OpenAPI emitter, which makes generating OpenAPI YAML specifications straightforward, preserving our existing workflows.
The TypeSpec team is developing additional emitters to generate SDKs and server routes directly, though these are still in progress.
Our current OpenAPI-based generators include:
- Go types and server route handlers: oapi-codegen
- TypeScript types: openapi-typescript
- Documentation: Fumadocs
Organizing Spec Into Packages
Organizing OpenAPI files is notoriously challenging. TypeSpec's namespaces, packages, and imports simplify managing complex API specifications. The best part? You can install packages from NPM and extend your specifications, enabling reusable code and expanding TypeSpec's functionality. We expect API standards for pagination and error handling to become more standardized across the industry as developers can easily share and install them as npm packages.
Co-locating Operations and Types
If you've worked with OpenAPI, you likely know the frustration of scrolling between path, parameter, and schema definitions. TypeSpec makes it much easier to co-locate types and operations.
Self-Contained Properties
OpenAPI's object-level required property can be cumbersome, especially for objects with numerous properties. In TypeSpec, properties are required by default unless suffixed with a question mark, similar to TypeScript. You can also set visibility directly within the property definition.
I know that the conversations about whether properties should be required or optional for APIs go deep, especially in GraphQL circles, to allow graceful failover for servers, but let's not get carried away.
Increased Consistency With Templates
TypeSpec supports templates, which function like generics and allow for
consistent type reuse across APIs. This has been incredibly helpful, reducing
the need for multiple model variants for pagination and error handling, which
can be error-prone. For instance, we can use Paginated<Customer>
to keep the
API definitions concise.
And here's the pagination template used above:
70% Less Code to Maintain
As a combination of packaging, templates, and reusability, the TypeSpec definition of our API is much smaller than the OpenAPI representation. Our OpenAPI specification is almost 600k characters long, while our TypeSpec files are around a combined 200k characters. That's a significant difference and much less code to write and maintain. For a startup where speed is crucial, everything counts.
What's Next?
Our primary goal with TypeSpec is to improve the consistency and maintainability of OpenMeter APIs and SDKs. The first step was achieving feature parity with our existing OpenAPI specification while retaining compatibility. Moving forward, we plan to unify patterns across APIs and models, increasingly relying on TypeSpec emitters to generate types and SDKs. Expect significant improvements in the OpenMeter API and SDK experience in the coming months. Stay tuned.
You can find our TypeSpec files in the OpenMeter GitHub repository.