Specification
Table of Contents
Introduction
This document describes the TypeAPI specification. The TypeAPI specification defines a JSON format to describe REST APIs for type-safe code generation.
Goals
- Provide a format to generate clean and ready to use code
- Provide a simple and stable specification
- Optimized for static typed and object-oriented programming languages
Non-Goals
- Describe every possible REST API structure and JSON payload
- Providing complex JSON validation capabilities
Reasoning
We believe that the API world needs a specification which can be used to automatically generate solid type-safe client and server code. The OpenAPI swagger-codegen project exists for a long time to implement such a code generator for the OpenAPI specification, but it has turned out, that the OpenAPI specification and JSON Schema makes it difficult for code generators to generate solid type-safe code. The problems are at the specification level, this means a code generator which is based on OpenAPI needs to somehow solve these inherited problems, by either restricting the specification or by providing a custom format.
With TypeAPI we want to provide an alternativ specification to solve these problems. TypeAPI is basically a stricter version of OpenAPI/JSON Schema and it is easy possible to generate an OpenAPI specification based on a TypeAPI specification but not vice versa. We also see already many commercial projects like Fern, Liblab or Stainless to solve these problems, but we believe that it would be much better to solve this at the specification level.
Vision
We see that the world is connected through APIs but integrating external APIs is still a complex problem. We want to move the API ecosystem into a direction where it is no longer needed to implement a client SDK for your API, you only need to describe the API through a TypeAPI specification and everything else can be generated automatically. In the future we also want to extend the TypeAPI specification and code generator to describe GraphQL or RPC APIs so that we have a single client which can talk to various protocols. This means the generated client is always stable, but it is possible to change the underlying technology i.e. if you want to switch from REST to RPC.
On the server-side we also want to generate great server-stubs so that it is easy possible to switch the underlying server technology. The code generator automatically generates all controller and model classes for the target server technology i.e. Spring or Symfony and then you only need to implement the actual business logic.
At TypeAPI we heavily support the code-first approach, we think it should be possible to generate an API specification directly from your code without the need to add many additional annotations. In the future we want to provide tools to automatically generate a TypeAPI specification directly from various frameworks without the need to manually build the specification. We see many APIs which are not in sync with the specification and we believe that code-first is the correct approach to prevent this, so that the specification is always in sync with the actual implementation. While theoretical the design-first approach would be great we have seen in the past that there is basically no way to prevent API drift at scale and keep the API in sync with the actual implementation.
Operations
Every TypeAPI has a Root definition. The
Root must contain at least the operations
and definitions
keyword i.e.:
{
"operations": {
"getMessage": { ... },
},
"definitions": {
"TypeA": { ... },
"TypeB": { ... }
}
}
The operations
keyword contains a map containing Operation
objects. The key represents the identifier of this operation, through the dot notation i.e. user.getMessage
you can group your
operations into logical units.
{
"operations": {
"getMessage": {
"description": "Returns a hello world message",
"method": "GET",
"path": "/hello/world",
"return": {
"schema": {
"type": "reference",
"target": "Hello_World"
}
}
}
},
"definitions": {
"Hello_World": {
"type": "struct",
"properties": {
"message": {
"type": "string"
}
}
}
}
}
Return
Every operation can define a return type. In the above example the operation simply returns a Hello_World
object.
Arguments
Through the arguments
keywords you can map values from the HTTP request to specific method arguments. In
the following example we have an argument status
which maps to a query parameter and an argument
payload
which contains the request payload.
{
"operations": {
"insertMessage": {
"description": "Inserts and returns a hello world message",
"method": "POST",
"path": "/hello/world",
"arguments": {
"status": {
"in": "query",
"schema": {
"type": "integer"
}
},
"payload": {
"in": "body",
"schema": {
"type": "reference",
"target": "Hello_World"
}
}
},
"return": {
"schema": {
"type": "reference",
"target": "Hello_World"
}
}
}
},
"definitions": {
"Hello_World": {
"type": "struct",
"properties": {
"message": {
"type": "string"
}
}
}
}
}
This would map to the following HTTP request.
POST https://api.acme.com/hello/world?status=2
Content-Type: application/json
{
"message": "Hello"
}
Throws
Besides the return type an operation can return multiple exceptional states in case an error occurred. Every
exceptional state is then mapped to a specific status code i.e. 404
or 500
. The generated
client SDK will throw a fitting exception containing the JSON payload in case the server returns such an error
response code. The client will either return the success response or throw an exception. This greatly simplifies error
handling at your client code.
{
"operations": {
"getMessage": {
"description": "Returns a hello world message",
"method": "POST",
"path": "/hello/world",
"return": {
"schema": {
"type": "reference",
"target": "Hello_World"
}
},
"throws": [{
"code": 404,
"schema": {
"type": "reference",
"target": "Error"
}
}, {
"code": 500,
"schema": {
"type": "reference",
"target": "Error"
}
}]
}
},
"definitions": {
"Hello_World": {
"type": "struct",
"properties": {
"message": {
"type": "string"
}
}
},
"Error": {
"type": "struct",
"properties": {
"message": {
"type": "string"
}
}
}
}
}
Definitions
The definitions
keyword maps to the TypeSchema
specification and represents a map containing Struct,
Map or Reference
types. Those types are then used to describe incoming and outgoing JSON payloads.
Security
The security
keyword describes the authorization mechanism of the API, the following types are supported:
-
apiKey
Describes an arbitrary HTTP header containing an access token i.e.
X-Api-Key
which can be specified with thein
andname
keyword. -
httpBasic
Describes an
Authorization
header using the Basic type. See RFC7617, base64-encoded credentials. -
httpBearer
Describes an
Authorization
header using the Bearer type. See RFC6750, bearer tokens to access OAuth 2.0-protected resources. -
oauth2
Describes an OAuth2 endpoint. The client will automatically request an access token using the
client_credentials
authorization grant on usage. The following keywords can be used:tokenUrl
,authorizationUrl
and optionallyscopes
{
"security": {
"type": "httpBearer",
},
"operations": {
"getMessage": { ... }
},
"definitions": {
"Hello_World": { ... }
}
}