ts-oas
TypeScript icon, indicating that this package has built-in type declarations

0.5.0 • Public • Published

Typescript OpenAPI Spec Generator

NPM version GitHub License NPM Unpacked Size

Automatically generate OpenAPI (formerly Swagger) specifications from Typescript types. Supports OpenAPI v3.1 and v3.0. Requires interfaces/types in a specific format.

Benefits

  • Write once, use many. Typescript provides a fluent way to declare API specifications. With ts-oas, you can use the generated specs not only for documentation but also for input validation (eg. with AJV), serialization, maintaining business logic or test codes (with generics), and more.
  • Automation first. Simply write a script to regenerate specs accordingly after any type changes.
  • Headless. Works seamlessly with any server-side framework, unlike some other tools.

Features

  • 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 schemas separately.
  • Typescript 4 and 5 compliant.

Install

npm i ts-oas

Getting Started

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[]>[];
};

A quick example

We have interfaces.ts where our API types are present:

import { ApiMapper } from "ts-oas"; // Recommended to use ApiMapper to help to keep the format valid.

export type GetBarAPI = ApiMapper<{
    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 TypescriptOAS, { createProgram } 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 TypescriptOAS(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"
                    }
                }
            }
        }
    }
}

Get schemas separately

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' ]
  }
}

CLI

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

Documentations

JSDoc annotations

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

Special keywords for root of API types

@summary @operationId @tags @ignore @body.description @body.contentType
Example
/**
 * Sample description.
 * @summary Summary of Endpoint
 * @operationId addBar
 * @tags foos,bars
 * @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": {};
    };
}>;

Special keywords for response items

@contentType
Example
    ...
    responses: {
        /**
        * Description for response 200.
        * @contentType application/json
        */
        "200": { success: true };
    };

Options

ref

default: false

Defines references for schemas based on their type references.

titles

default: false

Provides a title field in each schema, filled with its corresponding field name or type name.

ignoreRequired

default: false

Ignores the required field in all schemas.

ignoreErrors

default: false

Ignores errors in Typescript files. May introduce wrong schemas.

uniqueNames

default: false

Replaces every type name with a unique hash to avoid duplication issues.

tsNodeRegister

default: false

Uses ts-node/register as a runtime argument, enabling direct execution of TypeScript on Node.js without precompiling.

nullableKeyword

default: true

Provides nullable: true for nullable fields; otherwise, set type: "null".

defaultContentType

default: "*/*"

Sets the default content type for all operations. This can be overridden case-by-case (see the annotations section).

defaultNumberType

default: "number"

Sets the default schema type for number values, which can be overridden case-by-case (see the annotations section).

customKeywords

A list of custom keywords to consider in annotations.

customKeywordPrefix

default: "x-"

The prefix added to all customKeywords.

schemaProcessor

A function that runs over each generated schema.

Inspirations

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).

Contributing

Contributions of any kind are welcome!

TODOs
  • [x] CLI
  • [ ] Support for request and response header specs
  • [ ] More docs and examples
  • [ ] Complete tests

Dependencies (3)

Dev Dependencies (10)

Package Sidebar

Install

npm i ts-oas

Weekly Downloads

60

Version

0.5.0

License

MIT

Unpacked Size

205 kB

Total Files

38

Last publish

Collaborators

  • alireza-mfd