openapi-fetch-gen – Generate TypeScript API client from OpenAPI TypeScript interface definitions created by openapi-typescript

moznion / openapi-fetch-gen Generate TypeScript API client from OpenAPI TypeScript interface definitions created by openapi-typescript (https://github.com/openapi-ts/openapi-typescript). openapi-fetch-gen Generate TypeScript API client from OpenAPI TypeScript interface definitions created by openapi-typescript. This tool takes TypeScript interface definitions generated by openapi-typescript and creates a fully typed API client using openapi-fetch. How It Works Parse the TypeScript schema file generated by openapi-typescript Extract all API endpoints, their HTTP methods, and parameter structures from the schema Generate typed wrapper functions for each endpoint Export a fully-typed client that provides A base client instance created with createClient from openapi-fetch Individual typed functions for each API endpoint Type helpers for parameters and responses Installation npm install --save-dev @moznion/openapi-fetch-gen Usage CLI npx openapi-fetch-gen --input ./schema.d.ts --output ./client.ts Options: -V, --version output the version number -i, --input path to input OpenAPI TypeScript definition file -o, --output path to output generated client file (default: "./client.ts") --default-headers header names so that the generated client includes the default HTTP headers across all endpoints… View on GitHub Background/Motivation The goal of openapi-fetch-gen is to automatically generate a fully-typed TypeScript API client from the .d.ts schema generated by openapi-ts/openapi-typescript. openapi-ts/openapi-typescript is a powerful tool for generating TypeScript schema definitions from OpenAPI 3 specifications. The same organization also provides openapi-fetch, a type-safe fetch client library designed to work seamlessly with these generated schemas. Although very useful, one notable hurdle with openapi-fetch is that developers must manually implement client code for each API endpoint. How This Tool Works For instance, suppose you have an OpenAPI 3 YAML definition like the following: schema.yaml (a bit long, but imagine a typical REST API definition) openapi: 3.0.3 info: title: Fictional Library User Management Service API version: 1.0.0 description: | A RESTful API for managing library user records and their loan information. servers: - url: https://api.fictionallibrary.example.com/v1 description: Production server paths: /users/{userId}: parameters: - $ref: '#/components/parameters/userId' - name: Authorization in: header required: true schema: type: string description: Authorization Header - name: Application-Version in: header required: true schema: type: string description: Application version - name: Something-Id in: header required: true schema: type: string description: Identifier of something get: summary: Get user details description: Retrieve detailed information for a specific user. responses: '200': description: User details content: application/json: schema: $ref: '#/components/schemas/User' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' put: summary: Replace user description: Replace a user's entire record. requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UserUpdate' responses: '200': description: Updated user record content: application/json: schema: $ref: '#/components/schemas/User' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' patch: summary: Update user fields description: Partially update a user's information. requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UserPatch' responses: '200': description: Updated user record content: application/json: schema: $ref: '#/components/schemas/User' '400': $ref: '#/components/responses/BadReques

Apr 20, 2025 - 08:08
 0
openapi-fetch-gen – Generate TypeScript API client from OpenAPI TypeScript interface definitions created by openapi-typescript

GitHub logo moznion / openapi-fetch-gen

Generate TypeScript API client from OpenAPI TypeScript interface definitions created by openapi-typescript (https://github.com/openapi-ts/openapi-typescript).

openapi-fetch-gen Run Tests

Generate TypeScript API client from OpenAPI TypeScript interface definitions created by openapi-typescript.

This tool takes TypeScript interface definitions generated by openapi-typescript and creates a fully typed API client using openapi-fetch.

How It Works

  1. Parse the TypeScript schema file generated by openapi-typescript
  2. Extract all API endpoints, their HTTP methods, and parameter structures from the schema
  3. Generate typed wrapper functions for each endpoint
  4. Export a fully-typed client that provides
    • A base client instance created with createClient from openapi-fetch
    • Individual typed functions for each API endpoint
    • Type helpers for parameters and responses

Installation

npm install --save-dev @moznion/openapi-fetch-gen

Usage

CLI

npx openapi-fetch-gen --input ./schema.d.ts --output ./client.ts

Options:

  -V, --version                                         output the version number
  -i, --input                                     path to input OpenAPI TypeScript definition file
  -o, --output                                    path to output generated client file (default: "./client.ts")
  --default-headers   header names so that the generated client includes the default HTTP headers across all endpoints

Background/Motivation

The goal of openapi-fetch-gen is to automatically generate a fully-typed TypeScript API client from the .d.ts schema generated by openapi-ts/openapi-typescript.

openapi-ts/openapi-typescript is a powerful tool for generating TypeScript schema definitions from OpenAPI 3 specifications. The same organization also provides openapi-fetch, a type-safe fetch client library designed to work seamlessly with these generated schemas. Although very useful, one notable hurdle with openapi-fetch is that developers must manually implement client code for each API endpoint.

How This Tool Works

For instance, suppose you have an OpenAPI 3 YAML definition like the following:

schema.yaml (a bit long, but imagine a typical REST API definition)

openapi: 3.0.3
info:
  title: Fictional Library User Management Service API
  version: 1.0.0
  description: |
    A RESTful API for managing library user records and their loan information.
servers:
  - url: https://api.fictionallibrary.example.com/v1
    description: Production server

paths:
  /users/{userId}:
    parameters:
      - $ref: '#/components/parameters/userId'
      - name: Authorization
        in: header
        required: true
        schema:
          type: string
        description: Authorization Header
      - name: Application-Version
        in: header
        required: true
        schema:
          type: string
        description: Application version
      - name: Something-Id
        in: header
        required: true
        schema:
          type: string
        description: Identifier of something
    get:
      summary: Get user details
      description: Retrieve detailed information for a specific user.
      responses:
        '200':
          description: User details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

    put:
      summary: Replace user
      description: Replace a user's entire record.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserUpdate'
      responses:
        '200':
          description: Updated user record
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

    patch:
      summary: Update user fields
      description: Partially update a user's information.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserPatch'
      responses:
        '200':
          description: Updated user record
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

    delete:
      summary: Delete user
      description: Soft-delete a user record.
      responses:
        '204':
          description: User deleted (no content)
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

components:
  parameters:
    userId:
      name: userId
      in: path
      required: true
      description: Unique user identifier (UUID)
      schema:
        type: string
        format: uuid

  responses:
    BadRequest:
      description: Bad request due to invalid input
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: Authentication required or failed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: Insufficient permissions to access resource
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Conflict:
      description: Conflict with current state of the resource
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

  schemas:
    Error:
      type: object
      properties:
        code:
          type: integer
          description: HTTP status code
        message:
          type: string
          description: Error message detailing the cause
      required:
        - code
        - message

    User:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        email:
          type: string
          format: email
        membershipType:
          type: string
          enum: [REGULAR, PREMIUM, STUDENT]
        registeredAt:
          type: string
          format: date-time
        address:
          $ref: '#/components/schemas/Address'
      required:
        - id
        - name
        - email
        - membershipType
        - registeredAt

    Address:
      type: object
      properties:
        postalCode:
          type: string
        street:
          type: string
        city:
          type: string
        country:
          type: string
      required:
        - street
        - city
        - country

    UserCreate:
      type: object
      properties:
        name:
          type: string
        email:
          type: string
          format: email
        membershipType:
          type: string
          enum: [REGULAR, PREMIUM, STUDENT]
        address:
          $ref: '#/components/schemas/Address'
      required:
        - name
        - email
        - membershipType

    UserUpdate:
      allOf:
        - $ref: '#/components/schemas/UserCreate'
        - type: object
          properties:
            id:
              type: string
              format: uuid
          required:
            - id

    UserPatch:
      type: object
      description: Schema for partial updates – include only fields to change
      properties:
        name:
          type: string
        email:
          type: string
          format: email
        membershipType:
          type: string
          enum: [REGULAR, PREMIUM, STUDENT]
        address:
          $ref: '#/components/schemas/Address'

Next, you execute:

openapi-typescript --output schema.d.ts ./schema.yaml

This generates a TypeScript interface definition file named schema.d.ts:

schema.d.ts

/**
 * This file was auto-generated by openapi-typescript.
 * Do not make direct changes to the file.
 */

export interface paths {
    "/users/{userId}": {
        parameters: {
            query?: never;
            header: {
                /** @description Authorization Header */
                Authorization: string;
                /** @description Application version */
                "Application-Version": string;
                /** @description Identifier of something */
                "Something-Id": string;
            };
            path: {
                /** @description Unique user identifier (UUID) */
                userId: components["parameters"]["userId"];
            };
            cookie?: never;
        };
        /**
         * Get user details
         * @description Retrieve detailed information for a specific user.
         */
        get: {
            parameters: {
                query?: never;
                header: {
                    /** @description Authorization Header */
                    Authorization: string;
                    /** @description Application version */
                    "Application-Version": string;
                    /** @description Identifier of something */
                    "Something-Id": string;
                };
                path: {
                    /** @description Unique user identifier (UUID) */
                    userId: components["parameters"]["userId"];
                };
                cookie?: never;
            };
            requestBody?: never;
            responses: {
                /** @description User details */
                200: {
                    headers: {
                        [name: string]: unknown;
                    };
                    content: {
                        "application/json": components["schemas"]["User"];
                    };
                };
                400: components["responses"]["BadRequest"];
                401: components["responses"]["Unauthorized"];
                403: components["responses"]["Forbidden"];
                404: components["responses"]["NotFound"];
            };
        };
        /**
         * Replace user
         * @description Replace a user's entire record.
         */
        put: {
            parameters: {
                query?: never;
                header: {
                    /** @description Authorization Header */
                    Authorization: string;
                    /** @description Application version */
                    "Application-Version": string;
                    /** @description Identifier of something */
                    "Something-Id": string;
                };
                path: {
                    /** @description Unique user identifier (UUID) */
                    userId: components["parameters"]["userId"];
                };
                cookie?: never;
            };
            requestBody: {
                content: {
                    "application/json": components["schemas"]["UserUpdate"];
                };
            };
            responses: {
                /** @description Updated user record */
                200: {
                    headers: {
                        [name: string]: unknown;
                    };
                    content: {
                        "application/json": components["schemas"]["User"];
                    };
                };
                400: components["responses"]["BadRequest"];
                401: components["responses"]["Unauthorized"];
                403: components["responses"]["Forbidden"];
                404: components["responses"]["NotFound"];
            };
        };
        post?: never;
        /**
         * Delete user
         * @description Soft-delete a user record.
         */
        delete: {
            parameters: {
                query?: never;
                header: {
                    /** @description Authorization Header */
                    Authorization: string;
                    /** @description Application version */
                    "Application-Version": string;
                    /** @description Identifier of something */
                    "Something-Id": string;
                };
                path: {
                    /** @description Unique user identifier (UUID) */
                    userId: components["parameters"]["userId"];
                };
                cookie?: never;
            };
            requestBody?: never;
            responses: {
                /** @description User deleted (no content) */
                204: {
                    headers: {
                        [name: string]: unknown;
                    };
                    content?: never;
                };
                400: components["responses"]["BadRequest"];
                401: components["responses"]["Unauthorized"];
                403: components["responses"]["Forbidden"];
                404: components["responses"]["NotFound"];
            };
        };
        options?: never;
        head?: never;
        /**
         * Update user fields
         * @description Partially update a user's information.
         */
        patch: {
            parameters: {
                query?: never;
                header: {
                    /** @description Authorization Header */
                    Authorization: string;
                    /** @description Application version */
                    "Application-Version": string;
                    /** @description Identifier of something */
                    "Something-Id": string;
                };
                path: {
                    /** @description Unique user identifier (UUID) */
                    userId: components["parameters"]["userId"];
                };
                cookie?: never;
            };
            requestBody: {
                content: {
                    "application/json": components["schemas"]["UserPatch"];
                };
            };
            responses: {
                /** @description Updated user record */
                200: {
                    headers: {
                        [name: string]: unknown;
                    };
                    content: {
                        "application/json": components["schemas"]["User"];
                    };
                };
                400: components["responses"]["BadRequest"];
                401: components["responses"]["Unauthorized"];
                403: components["responses"]["Forbidden"];
                404: components["responses"]["NotFound"];
            };
        };
        trace?: never;
    };
}
export type webhooks = Record<string, never>;
export interface components {
    schemas: {
        Error: {
            /** @description HTTP status code */
            code: number;
            /** @description Error message detailing the cause */
            message: string;
        };
        User: {
            /** Format: uuid */
            id: string;
            name: string;
            /** Format: email */
            email: string;
            /** @enum {string} */
            membershipType: "REGULAR" | "PREMIUM" | "STUDENT";
            /** Format: date-time */
            registeredAt: string;
            address?: components["schemas"]["Address"];
        };
        Address: {
            postalCode?: string;
            street: string;
            city: string;
            country: string;
        };
        UserCreate: {
            name: string;
            /** Format: email */
            email: string;
            /** @enum {string} */
            membershipType: "REGULAR" | "PREMIUM" | "STUDENT";
            address?: components["schemas"]["Address"];
        };
        UserUpdate: components["schemas"]["UserCreate"] & {
            /** Format: uuid */
            id: string;
        };
        /** @description Schema for partial updates – include only fields to change */
        UserPatch: {
            name?: string;
            /** Format: email */
            email?: string;
            /** @enum {string} */
            membershipType?: "REGULAR" | "PREMIUM" | "STUDENT";
            address?: components["schemas"]["Address"];
        };
    };
    responses: {
        /** @description Bad request due to invalid input */
        BadRequest: {
            headers: {
                [name: string]: unknown;
            };
            content: {
                "application/json": components["schemas"]["Error"];
            };
        };
        /** @description Authentication required or failed */
        Unauthorized: {
            headers: {
                [name: string]: unknown;
            };
            content: {
                "application/json": components["schemas"]["Error"];
            };
        };
        /** @description Insufficient permissions to access resource */
        Forbidden: {
            headers: {
                [name: string]: unknown;
            };
            content: {
                "application/json": components["schemas"]["Error"];
            };
        };
        /** @description Resource not found */
        NotFound: {
            headers: {
                [name: string]: unknown;
            };
            content: {
                "application/json": components["schemas"]["Error"];
            };
        };
        /** @description Conflict with current state of the resource */
        Conflict: {
            headers: {
                [name: string]: unknown;
            };
            content: {
                "application/json": components["schemas"]["Error"];
            };
        };
    };
    parameters: {
        /** @description Unique user identifier (UUID) */
        userId: string;
    };
    requestBodies: never;
    headers: never;
    pathItems: never;
}
export type $defs = Record<string, never>;
export type operations = Record<string, never>;

Previously, you would have needed to manually implement the API client for each endpoint using openapi-fetch. Now, with openapi-fetch-gen, the process is significantly simplified:

Just execute:

openapi-fetch-gen --input ./schema.d.ts --output ./generated_client.ts

This automatically generates the following fully-typed API client code:

import createClient, { type ClientOptions } from "openapi-fetch";
import type { paths } from "./schema"; // generated by openapi-typescript

export class Client {
  private readonly client;

  constructor(clientOptions: ClientOptions) {
    this.client = createClient<paths>(clientOptions);
  }

  ...

  /**
   * Replace user
   */
  async putUsersUserid(
    params: {
      header: {
        Authorization: string;
        "Application-Version": string;
        "Something-Id": string;
      };
      path: { userId: string };
    },
    body: {
      name: string;
      email: string;
      membershipType: "REGULAR" | "PREMIUM" | "STUDENT";
      address?: {
        postalCode?: string;
        street: string;
        city: string;
        country: string;
      };
    } & { id: string },
  ) {
    return await this.client.PUT("/users/{userId}", {
      params,
      body,
    });
  }
  ...
}

FYI: complete example of generated_client.ts

// THIS FILE IS AUTO-GENERATED BY openapi-fetch-gen.
// DO NOT EDIT THIS FILE MANUALLY.
// See Also: https://github.com/moznion/openapi-fetch-gen
import createClient, { type ClientOptions } from "openapi-fetch";
import type { paths } from "./schema"; // generated by openapi-typescript

export class Client {
  private readonly client;

  constructor(clientOptions: ClientOptions) {
    this.client = createClient<paths>(clientOptions);
  }

  /**
   * Get user details
   */
  async getUsersUserid(params: {
    header: {
      Authorization: string;
      "Application-Version": string;
      "Something-Id": string;
    };
    path: { userId: string };
  }) {
    return await this.client.GET("/users/{userId}", {
      params,
    });
  }

  /**
   * Replace user
   */
  async putUsersUserid(
    params: {
      header: {
        Authorization: string;
        "Application-Version": string;
        "Something-Id": string;
      };
      path: { userId: string };
    },
    body: {
      name: string;
      email: string;
      membershipType: "REGULAR" | "PREMIUM" | "STUDENT";
      address?: {
        postalCode?: string;
        street: string;
        city: string;
        country: string;
      };
    } & { id: string },
  ) {
    return await this.client.PUT("/users/{userId}", {
      params,
      body,
    });
  }

  /**
   * Delete user
   */
  async deleteUsersUserid(params: {
    header: {
      Authorization: string;
      "Application-Version": string;
      "Something-Id": string;
    };
    path: { userId: string };
  }) {
    return await this.client.DELETE("/users/{userId}", {
      params,
    });
  }

  /**
   * Update user fields
   */
  async patchUsersUserid(
    params: {
      header: {
        Authorization: string;
        "Application-Version": string;
        "Something-Id": string;
      };
      path: { userId: string };
    },
    body: {
      name?: string;
      email?: string;
      membershipType?: "REGULAR" | "PREMIUM" | "STUDENT";
      address?: {
        postalCode?: string;
        street: string;
        city: string;
        country: string;
      };
    },
  ) {
    return await this.client.PATCH("/users/{userId}", {
      params,
      body,
    });
  }
}

That's all there is to it! ✨

Wrap-up

In summary, openapi-fetch-gen automates the generation of a TypeScript API client layer directly from the openapi-typescript schema definitions, removing the need for tedious manual implementation.

Additionally, this tool supports a "Default HTTP Headers" feature that simplifies type definitions for client users. The original implementation required explicitly passing headers (e.g., Authorization) in many methods. To simplify this, openapi-fetch-gen uses a tiny type-level trick to handle these headers more gracefully. If you're interested, please refer to the Default HTTP Headers section.