openapi-ts-json-schema
Version:
Generate TypeScript-first JSON schemas from OpenAPI definitions
261 lines (199 loc) • 14.2 kB
Markdown
# openapi-ts-json-schema
[![Build Status][ci-badge]][ci]
[![Npm version][npm-version-badge]][npm]
[![Coveralls][coveralls-badge]][coveralls]
Generate **TypeScript-first JSON Schemas** (`.ts` modules with `as const`) directly from your OpenAPI definitions — so you can use the same schema for both **runtime validation** and **TypeScript type inference**.
## Why?
Keeping **OpenAPI specs**, **runtime validators**, and **TypeScript types** in sync is hard.
Many teams end up maintaining the same api models in different formats:
- JSON Schema for runtime validation (`Ajv`, `Fastify`, etc.)
- TypeScript types for static checking
`openapi-ts-json-schema` solves this by generating **TypeScript JSON Schemas directly from your OpenAPI definitions**: valid JSON schemas written as **TypeScript modules**, ready for runtime validation and type inference.
These schemas:
- ✅ are 100% JSON Schema–compatible (usable with `Ajv`, `Fastify`, etc.)
- ✅ are TypeScript-native (`as const` objects you can import)
- ✅ can be used for type inference via [json-schema-to-ts](https://github.com/ThomasAribart/json-schema-to-ts)
- ✅ are generated automatically from your OpenAPI spec
In short: **OpenAPI spec becomes the single source of truth** for both runtime validation and TypeScript typing.
## Example
From this OpenAPI definition:
```yaml
components:
schemas:
User:
type: object
properties:
id: { type: string }
name: { type: string }
required: [id, name]
```
You get this TypeScript JSON schema:
```ts
// components/schemas/User.ts
export default {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
},
required: ['id', 'name'],
} as const;
```
Now you can use it for both runtime validation and type inference:
```ts
import Ajv from 'ajv';
import type { FromSchema } from 'json-schema-to-ts';
import userSchema from './components/schemas/User';
const ajv = new Ajv();
const validate = ajv.compile<FromSchema<typeof userSchema>>(userSchema);
const data: unknown = {};
if (validate(data)) {
// data is now typed as { id: string; name: string }
} else {
console.error(validate.errors);
}
```
## Installation
```
npm i openapi-ts-json-schema -D
```
## Usage
```ts
import { openapiToTsJsonSchema } from 'openapi-ts-json-schema';
const { outputPath } = await openapiToTsJsonSchema({
openApiDocument: 'path/to/open-api-specs.yaml',
targets: {
collections: ['components.schemas', 'paths'],
},
});
```
Schemas are generated in a folder mirroring your OpenAPI layout (default: `schemas-autogenerated`).
### CommonJS
Since `openapi-ts-json-schema` is distributed as an ESM-only package, it must be imported using a dynamic `import()` when used from a CommonJS project:
```ts
async function run() {
const { openapiToTsJsonSchema } = await import('openapi-ts-json-schema');
const { outputPath } = await openapiToTsJsonSchema({
openApiDocument: 'path/to/open-api-specs.yaml',
targets: {
collections: ['components.schemas', 'paths'],
},
moduleSystem: 'cjs',
});
}
run();
```
## Options
### Core options
| Property | Type | Description | Default |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| **openApiDocument** _(required)_ | `string` | Path to an OpenAPI document (JSON or YAML). | - |
| **targets** _(required)_ | `{`</br>` collections?: string[];`</br>` single?: string[];`</br>`}` | OpenAPI definition paths to generate JSON Schemas from _(dot notation)_.<br/><br/>`collections`: paths pointing to objects/records of definitions, where each entry will be generated (eg: `["components.schemas"]`).<br/><br/>`single`: paths pointing to individual definitions to generate (eg: `["paths./users/{id}"]`). | - |
| **outputPath** | `string` | Directory where generated schemas will be written. Defaults to `/schemas-autogenerated` in the same directory of `openApiDocument`. | - |
| **refHandling** | `"import" \| "inline" \| "keep"` | `"import"`: generate and import `$ref` schemas.<br/>`"inline"`: inline `$ref` schemas.<br/>`"keep"`: keep `$ref` values. | `"import"` |
| **moduleSystem** | `"cjs" \| "esm"` | Controls how import specifiers are written in generated artifacts. Configure this option based on whether the consuming project is using CommonJS or ECMAScript modules. | `"esm"` |
| **silent** | `boolean` | Don't log user messages. | `false` |
### Advanced options
| Property | Type | Description | Default |
| ----------------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------- | ------- |
| **idMapper** | `(params: { id: string }) => string` | Customize generated schemas `$id`s and `$ref`s values. Useful for enforcing naming conventions. | - |
| **schemaPatcher** | `(params: { schema: JSONSchema }) => void` | Hook called for every generated schema node, allowing programmatic mutation before output. | - |
| **plugins** | `ReturnType<Plugin>[]` | List of plugins to extend or customize the generation process. See [plugins docs](./docs/plugins.md). | - |
### Full configuration example
```ts
import {
generateSchemaWith,
openapiToTsJsonSchema,
} from 'openapi-ts-json-schema';
await openapiToTsJsonSchema({
openApiDocument: './openapi.yaml',
targets: {
collections: ['components.schemas'],
single: ['paths./users/{id}'],
},
outputPath: './generated',
refHandling: 'import',
moduleSystem: 'esm',
silent: false,
idMapper: ({ id }) => id.toUpperCase(),
schemaPatcher: ({ schema }) => {
if (schema.properties && !schema.type) {
schema.type = 'object';
}
},
plugins: [generateSchemaWith$idPlugin()],
});
```
### `refHandling` option
Three strategies for how `$ref`s are resolved:
| `refHandling` option | description |
| -------------------- | ---------------------------------------------------------------------------------------------- |
| `inline` | Inlines `$refs`s, creating self-contained schemas (no imports, but possible redundancy). |
| `import` | Replaces`$ref`s with imports of the target definition |
| `keep` | Leaves `$ref`s untouched — useful if you plan to interpret `$ref`s dynamically or use a plugin |
Circular references are supported:
- `inline`: circular refs are replaced with `{}`
- `import`: resolves the JSON schema but TypeScript recursion halts (`any` type, TS error 7022)
- `keep`: circular refs left unresolved
See [tests](https://github.com/toomuchdesign/openapi-ts-json-schema/blob/master/test/circularReference.test.ts) for details.
## Return values
Along with generated schema files, `openapi-ts-json-schema` returns metadata:
```ts
{
// The path where the schemas are generated
outputPath: string;
metaData: {
// Meta data of the generated schemas
schemas: Map<
// Schema internal id. Eg: "/components/schemas/MySchema"
string,
{
id: string;
// Internal unique schema identifier. Eg `"/components/schemas/MySchema"`
$id: string;
// JSON schema Compound Schema Document `$id`. Eg: `"/components/schemas/MySchema"`
uniqueName: string;
// Unique JavaScript identifier used as import name. Eg: `"componentsSchemasMySchema"`
openApiDefinition: OpenApiObject;
// Original dereferenced openAPI definition
originalSchema: JSONSchema | string;
// Original dereferenced JSON schema
isRef: boolean;
// True if schemas is used as a `$ref`
shouldBeGenerated: boolean;
// Text content of schema file
fileContent?: string;
absoluteDirName: string;
// Absolute path pointing to schema folder (posix or win32). Eg: `"Users/username/output/path/components/schemas"`
absolutePath: string;
// Absolute path pointing to schema file (posix or win32). Eg: `"Users/username/output/path/components/schemas/MySchema.ts"`
absoluteImportPath: string;
// Absolute import path (posix or win32, without extension). Eg: `"Users/username/output/path/components/schemas/MySchema"`
}
>;
}
}
```
## Plugins
Extend `openapi-ts-json-schema` with custom generators. Currently available plugins:
- `generateSchemaWith$idPlugin`
- `fastifyIntegrationPlugin`
See [plugins documentation 📖](./docs/plugins.md).
## How it works
Given an OpenAPI definition file, `openapi-ts-json-schema`:
- Resolves and dereferences $refs (using [@apidevtools/json-schema-ref-parser](https://github.com/APIDevTools/json-schema-ref-parser))
- Converts OpenAPI objects to JSON Schema (via [@openapi-contrib/openapi-schema-to-json-schema](https://github.com/APIDevTools/json-schema-ref-parser) & [`openapi-jsonschema-parameters`](https://www.npmjs.com/package/openapi-jsonschema-parameters))
- Generates `.ts` files exporting each schema as `as const`
- Mirrors the original OpenAPI structure in the generated folder
- Supports plugins (e.g. for Fastify integration)
Take a look at the [Developer's notes](./docs/developer-notes.md) for a few more in-depth explanations.
## Todo
- Improve external `#ref`s handling (currently being inlined and duplicated)
- Find a way to merge multiple different OpenApi definitions consistently
- Consider implementing an option to inline circular `$ref`s with a configurable nesting level
[ci-badge]: https://github.com/toomuchdesign/openapi-ts-json-schema/actions/workflows/ci.yml/badge.svg
[ci]: https://github.com/toomuchdesign/openapi-ts-json-schema/actions/workflows/ci.yml
[coveralls-badge]: https://coveralls.io/repos/github/toomuchdesign/openapi-ts-json-schema/badge.svg?branch=master
[coveralls]: https://coveralls.io/github/toomuchdesign/openapi-ts-json-schema?branch=master
[npm]: https://www.npmjs.com/package/openapi-ts-json-schema
[npm-version-badge]: https://img.shields.io/npm/v/openapi-ts-json-schema.svg