Spoosh
Type Adapters

OpenAPI

Generate type-safe TypeScript clients from OpenAPI specs, or export your schemas as OpenAPI documentation

The @spoosh/openapi package provides bidirectional conversion between OpenAPI 3.0/3.1 specifications and TypeScript Spoosh schemas.

Features

  • Import: Generate TypeScript Spoosh schemas from OpenAPI 3.0/3.1 specs (JSON & YAML)
  • Export: Generate OpenAPI 3.0 or 3.1 specs from TypeScript Spoosh schemas
  • Type-safe: Automatic detection of endpoint types, query params, request bodies, and responses
  • Error Types: Extract error types from 4xx/5xx responses with automatic union types
  • JSDoc Preservation: Convert OpenAPI descriptions to TypeScript comments
  • File Upload Detection: Automatic conversion of binary formats to File type

Installation

npm install @spoosh/openapi

Import: OpenAPI to Spoosh

Generate TypeScript Spoosh schemas from existing OpenAPI specifications. Perfect for integrating with third-party APIs or migrating from OpenAPI-based tools.

CLI Usage

# Import from JSON
npx spoosh-openapi import openapi.json -o ./src/schema.ts --include-imports

# Import from YAML
npx spoosh-openapi import openapi.yaml -o ./src/schema.ts --include-imports

# Custom options
npx spoosh-openapi import \
  ./docs/openapi.json \
  --output ./src/api-schema.ts \
  --type-name MyApiSchema \
  --include-imports \
  --jsdoc

Input: OpenAPI Spec

openapi.json
{
  "openapi": "3.0.0",
  "paths": {
    "/posts": {
      "get": {
        "description": "Retrieve all posts",
        "parameters": [
          { "name": "userId", "in": "query", "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/Post" }
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CreatePostBody" }
            }
          }
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Post" }
              }
            }
          }
        }
      }
    },
    "/posts/{postId}": {
      "get": {
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Post" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Post": {
        "type": "object",
        "required": ["id", "title"],
        "properties": {
          "id": { "type": "integer", "description": "Unique identifier" },
          "title": { "type": "string" },
          "desc": { "type": "string" }
        }
      },
      "CreatePostBody": {
        "type": "object",
        "required": ["title"],
        "properties": {
          "title": { "type": "string" },
          "desc": { "type": "string" }
        }
      }
    }
  }
}

Generated Output

src/schema.ts
/** Unique identifier */
type Post = {
  /** Unique identifier */
  id: number;
  title: string;
  desc?: string;
};

type CreatePostBody = {
  title: string;
  desc?: string;
};

type ApiSchema = {
  posts: {
    /** Retrieve all posts */
    GET: {
      data: Post[];
      query: { userId?: number };
      error: { error?: string };
    };
    POST: { data: Post; body: CreatePostBody };
  };
  "posts/:postId": {
    GET: { data: Post };
  };
};

Import CLI Options

OptionAliasRequiredDefaultDescription
<input>-Yes-Path to OpenAPI spec (JSON or YAML)
--output-oYes-Output TypeScript file path
--type-name-tNoApiSchemaSchema type name
--include-imports-NofalseInclude Spoosh type imports
--jsdoc-NofalseInclude JSDoc comments from OpenAPI descriptions and summaries

Type Detection

The import feature generates endpoint types with appropriate fields:

OpenAPI PatternSpoosh TypeExample
Query parameters\{ data: TData; query: TQuery \}GET with ?page=1&limit=10
multipart/form-data request body\{ data: TData; body: TBody \}POST with file upload
application/x-www-form-urlencoded request\{ data: TData; body: TBody \}POST with form data
application/json request body\{ data: TData; body: TBody \}POST/PUT with JSON
No response body (204)voidDELETE operation
Simple response onlyTDataSimple GET returning data

Path Conversion

OpenAPI paths are converted to flat Spoosh structure with path-based keys:

/posts              -> { posts: { GET: ... } }
/posts/{postId}     -> { "posts/:postId": { GET: ... } }
/posts/{id}/comments -> { "posts/:id/comments": { GET: ... } }

Dynamic segments ({paramName}) are converted to colon-prefixed params (:paramName) in flat path keys.

Error Type Extraction

Error types are automatically extracted from 4xx and 5xx response schemas. Responses with only descriptions (no schemas) are ignored.

{
  "responses": {
    "200": {
      "content": {
        "application/json": { "schema": { ... } }
      }
    },
    "400": {
      "description": "Bad request"
    },
    "500": {
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "system_message": { "type": "string" }
            }
          }
        }
      }
    }
  }
}

Generated:

// Only 500 has a schema, so only that is included
POST: { data: Data; body: Body; error: { system_message?: string } };

// If no error responses have schemas, no error field is added
GET: Data;

Multiple error schemas are automatically unioned:

// Both 400 and 500 have schemas
POST: {
  data: Data;
  body: Body;
  error: { error?: string } | { system_message?: string };
};

File Upload Detection

Binary formats are automatically converted to File type:

{
  "requestBody": {
    "content": {
      "multipart/form-data": {
        "schema": {
          "type": "object",
          "properties": {
            "file": { "type": "string", "format": "binary" },
            "title": { "type": "string" }
          }
        }
      }
    }
  }
}

Generated:

POST: {
  data: Response;
  body: {
    file: File;
    title: string;
  }
}

Export: Spoosh to OpenAPI

Generate OpenAPI specifications from your TypeScript Spoosh schema for documentation or API contract sharing.

CLI Usage

# Basic usage
npx spoosh-openapi export -s ./src/schema.ts -o openapi.json

# With custom options
npx spoosh-openapi export \
  --schema ./src/api-schema.ts \
  --type MyApiSchema \
  --output ./docs/openapi.json \
  --title "My API" \
  --version "2.0.0" \
  --base-url "https://api.example.com"

Schema File

src/schema.ts
interface User {
  id: number;
  name: string;
  email: string;
}

interface CreateUserBody {
  name: string;
  email: string;
}

export type ApiSchema = {
  users: {
    GET: { data: User[]; query: { page?: number; limit?: number } };
    POST: { data: User; body: CreateUserBody };
  };
  "users/:id": {
    GET: { data: User };
    PUT: { data: User; body: Partial<CreateUserBody> };
    DELETE: void;
  };
  health: {
    GET: { status: string };
  };
};

Generated Output

{
  "openapi": "3.0.0",
  "info": {
    "title": "My API",
    "version": "2.0.0"
  },
  "servers": [{ "url": "https://api.example.com" }],
  "paths": {
    "/users": {
      "get": {
        "parameters": [
          { "name": "page", "in": "query", "schema": { "type": "number" } },
          { "name": "limit", "in": "query", "schema": { "type": "number" } }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/User" }
                }
              }
            }
          }
        }
      }
    }
  }
}

Export CLI Options

OptionAliasRequiredDefaultDescription
--schema-sYes-Path to TypeScript file containing schema
--type-tNoApiSchemaName of the schema type to use
--output-oNostdoutOutput file path
--title-No-API title for OpenAPI info
--version-No1.0.0API version for OpenAPI info
--base-url-No-Base URL for servers array
--openapi-version-No3.1.0OpenAPI spec version (3.0.0 or 3.1.0)

Programmatic Usage

Import API

import {
  importOpenAPISpec,
  loadOpenAPISpec,
  generateSpooshSchema,
} from "@spoosh/openapi";

// High-level API (load + generate)
const tsCode = importOpenAPISpec("./openapi.json", {
  typeName: "ApiSchema",
  includeImports: true,
  jsdoc: true,
});

// Or use low-level APIs for more control
const spec = loadOpenAPISpec("./openapi.json");
const schema = generateSpooshSchema(spec, {
  typeName: "ApiSchema",
  includeImports: true,
});

console.log(schema);

Export API

import { parseSchema, generateOpenAPISpec } from "@spoosh/openapi";

const { endpoints, schemas } = parseSchema("./src/schema.ts", "ApiSchema");

const spec = generateOpenAPISpec(endpoints, schemas, {
  title: "My API",
  version: "1.0.0",
  baseUrl: "https://api.example.com",
});

console.log(JSON.stringify(spec, null, 2));

Use Cases

1. Import Existing APIs

Generate type-safe Spoosh clients from existing API documentation:

# Import from public API
npx spoosh-openapi import https://api.example.com/openapi.json -o ./src/api-schema.ts --include-imports

# Import from local file
npx spoosh-openapi import ./docs/openapi.json -o ./src/schema.ts --include-imports --jsdoc

2. Migration from OpenAPI

Migrate from OpenAPI-based tools to Spoosh while preserving types:

# Convert OpenAPI to Spoosh
npx spoosh-openapi import old-api.json -o ./src/schema.ts --include-imports

# Use with Spoosh client
import { Spoosh } from "@spoosh/core";
import type { ApiSchema } from "./schema";

const client = new Spoosh<ApiSchema, Error>("https://api.example.com");

3. Generate Documentation

Export your Spoosh schema to OpenAPI for use with documentation tools like Swagger UI or Redoc:

npx spoosh-openapi export -s ./src/schema.ts -o ./docs/openapi.json --title "My API" --version "1.0.0"

4. Contract Testing

Ensure your implementation matches your OpenAPI contract:

import { parseSchema, generateOpenAPISpec } from "@spoosh/openapi";
import fs from "fs";

const { endpoints, schemas } = parseSchema("./src/schema.ts", "ApiSchema");
const generated = generateOpenAPISpec(endpoints, schemas);
const contract = JSON.parse(fs.readFileSync("./openapi.json", "utf-8"));

// Compare and validate
expect(generated.paths).toEqual(contract.paths);

On this page