Automatically generate OpenAPI (formerly Swagger) specifications from opinionated Typescript types. Supports OpenAPI v3.1 and v3.0. Requires interfaces/types in a specific format.
- Write once, use many. Typescript provides a fluent way to declare API specifications. With
ts-oas
, you can use the generated specs for documentation, input validation (e.g. with AJV), and serialization — while still reusing the original types in your business logic, unit tests, and more. - Automation first. Simply write a script or use the CLI to regenerate specs accordingly after any type changes.
- Framework-agnostic. Works seamlessly with any server-side framework, unlike some other tools.
- Both Programmatic and Command line support.
- Reference schemas and components. Generate schema references that correspond to their Typescript type references.
- Supports JSDoc annotations. With both pre-defined and custom keywords, metadata can be included in every schema object.
- Schema processor function for any custom post-processing (if JSDoc annotations aren't enough).
- Generate json-schemas separately.
- Typescript 4 and 5 compliant.
npm i ts-oas
Firstly, We need types for each API, compatible with the following format:
type Api = {
path: string;
method: HTTPMethod;
body?: Record<string, any>;
params?: Record<string, any>;
query?: Record<string, any>;
responses: Partial<Record<HttpStatusCode, any>>;
security?: Record<string, string[]>[];
};
We have interfaces.ts
where our API types are present:
import { ApiMapper } from "ts-oas";
// ApiMapper only validates the type structure at compile time,
// it's optional, omit it if you need dependency-free interface files.
export type GetBarAPI = {
path: "/foo/bar/{id}";
method: "GET";
params: {
id: number;
};
query: {
from_date: Date;
};
responses: {
/** @contentType application/json */
"200": Bar;
"404": { success: false };
};
};
/**
* Sample description.
* @summary Add a Bar
*/
export type AddBarAPI = ApiMapper<{
path: "/foo/bar";
method: "POST";
body: Bar;
responses: {
/** No content */
"201": never;
};
}>;
export type Bar = {
/**
* Description for barName.
* @minLength 10
*/
barName: string;
barType: "one" | "two";
};
In script.ts
file:
import { createProgram, TsOAS } from "ts-oas";
import { resolve } from "path";
// create a Typescript program. or any generic ts program can be used.
const tsProgram = createProgram(["interfaces.ts"], { strictNullChecks: true }, resolve());
// initiate the OAS generator.
const tsoas = new TsOAS(tsProgram, { ref: true });
// get the complete OAS. determine type names (Regex/exact name) to be considered for specs.
const specObject = tsoas.getOpenApiSpec([/API$/]); // all types that ends with "API"
// log results:
console.log(JSON.stringify(specObject, null, 4));
// or write into a ts file:
// writeFileSync("./schema.ts", `const spec = ${inspect(specObject, { depth: null })};\n`);
Run the above script.
Expected output
{
"openapi": "3.1.0",
"info": {
"title": "OpenAPI specification",
"version": "1.0.0"
},
"components": {
"schemas": {
"Bar": {
"type": "object",
"properties": {
"barName": {
"description": "Description for barName.",
"minLength": 10,
"type": "string"
},
"barType": {
"enum": ["one", "two"],
"type": "string"
}
},
"required": ["barName", "barType"]
}
}
},
"paths": {
"/foo/bar/{id}": {
"get": {
"operationId": "GetBarAPI",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "number"
}
},
{
"name": "from_date",
"in": "query",
"required": true,
"schema": {
"type": "string",
"format": "date-time"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Bar"
}
}
}
},
"404": {
"description": "",
"content": {
"*/*": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"enum": [false]
}
},
"required": ["success"]
}
}
}
}
}
}
},
"/foo/bar": {
"post": {
"operationId": "AddBarAPI",
"description": "Sample description.",
"summary": "Add a Bar",
"requestBody": {
"content": {
"*/*": {
"schema": {
"$ref": "#/components/schemas/Bar"
}
}
}
},
"responses": {
"201": {
"description": "No content"
}
}
}
}
}
}
Schemas with any format can be generated by:
const schema = tsoas.getSchemas(["Bar"]);
console.log(schema);
Expected output
{
Bar: {
type: 'object',
properties: {
barName: {
description: 'Description for barName.',
minLength: 10,
type: 'string'
},
barType: { enum: [ 'one', 'two' ], type: 'string' }
},
required: [ 'barName', 'barType' ]
}
}
Command line tool is designed to behave just like the programmatic way. Once it has been installed, CLI can be executed using npx ts-oas
, or just ts-oas
if installed globally.
Usage: ts-oas <file-paths> <type-names> [options]
<file-paths> : Comma-separated list of relative .ts file paths which contain
types.
<type-names> : Comma-separated list of type names (Regex/exact name) to be
considered in files.
Options:
-c, --tsconfig-file Path to a JSON tsconfig file. [string]
-p, --options-file Path to a JSON file containing 'ts-oas' Options. Refer to
the documentation. [string]
-s, --spec-file Path to a JSON file containing additional OpenAPI
specifications. [string]
-e, --schema-only Only generates pure schemas from given types.
('spec-file' will be ignored.) [boolean]
-o, --output Path to a JSON file that will be used to write the
output. Will create the file if not existed. [string]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
Examples:
ts-oas ./interfaces/sample.ts myApi,mySecondApi
Keyword | Fields | Examples |
---|---|---|
@default | any | @default foo @default 3 @default ["a", "b", "c"] |
@format | strings | @format email |
@items | arrays | @items.minimum 1 @items.format email @items {"type":"integer", "minimum":0} @default ["a", "b", "c"] |
@$ref | any | @ref http://my-schema.org |
@title | any | @title foo |
@minimum @maximum @exclusiveMinimum @exclusiveMaximum |
numbers | @minimum 10 @maximum 100 |
@minLength @maxLength |
strings | @minLength 10 @maxLength 100 |
@minItems @maxItems |
arrays | @minItems 10 @maxItems 100 |
@minProperties @maxProperties |
objects | @minProperties 10 @maxProperties 100 |
@additionalProperties | objects | @additionalProperties |
@ignore | any | @ignore |
@pattern | strings | @pattern /^[0-9a-z]+$/g |
@example | any | @example foo @example {"abc":true} @example require('./examples.ts').myExampleConst |
@examples | any | @example ["foo", "bar"] @example require('./examples.ts').myExampleArrayConst |
Keyword | Description |
---|---|
@summary | Provides a brief summary of the API endpoint |
@operationId | Sets a unique identifier for the operation |
@tags | Assigns tags to group related operations (comma-separated) |
@deprecated | Marks the operation as deprecated |
@ignore | Excludes the API type from the generated specification |
@body.description | Adds a description for the request body |
@body.contentType | Sets the content-type for the request body |
Example
/**
* Sample description.
* @summary Summary of Endpoint
* @operationId addBar
* @tags foos,bars
* @deprecated
* @ignore
* @body.description Description for body of request.
* @body.contentType application/json
*/
export type AddBarAPI = ApiMapper<{
path: "/foo/bar";
method: "POST";
body: Bar;
responses: {
"201": {};
};
}>;
Keyword | Description |
---|---|
@contentType | Sets the content-type for the response |
Example
...
responses: {
/**
* Description for response 200.
* @contentType application/json
*/
"200": { success: true };
};
default: false
Defines references for schemas based on their type references.
default: false
Provides a title
field in each schema, filled with its corresponding field name or type name.
default: false
Ignores the required
field in all schemas.
default: false
Ignores errors in Typescript files. May introduce wrong schemas.
default: false
Replaces every type name with a unique hash to avoid duplication issues.
default: false
Uses ts-node/register
as a runtime argument, enabling direct execution of TypeScript on Node.js without precompiling.
default: true
Provides nullable: true
for nullable fields; otherwise, set type: "null"
.
default: "*/*"
Sets the default content type for all operations. This can be overridden case-by-case (see the annotations section).
default: "number"
Sets the default schema type for number values, which can be overridden case-by-case (see the annotations section).
A list of custom keywords to consider in annotations.
default: "x-"
The prefix added to all customKeywords
.
default: false
Whether to consider custom operation properties in the root of API types.
If true, avoid using ApiMapper
, as it will override these properties.
A function that runs over each generated schema.
ts-oas
is highly inspired by typescript-json-schema. While using the so-called library, it took lots of workarounds to create compatible OpenAPI v3.0 specs. For example, modifying output schemas enforced us to use schema-walker tools which added lots of overhead in our scripts (Despite of compatible OpenAPI schemas in ts-oas
, we added a schema-processor custom function as an option as well).
Contributions of any kind are welcome!
TODOs
- CLI
- Support for request and response header specs
- More docs and examples
- Complete tests