TypeAPI

An OpenAPI alternative to describe REST APIs for type-safe code generation.

Specification Editor Generator

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 the in and name 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 optionally scopes

{
    "security": {
        "type": "httpBearer",
    },
    "operations": {
        "getMessage": { ... }
    },
    "definitions": {
        "Hello_World": { ... }
    }
}
part of the Apioo-Project