Skip to content

moznion/openapi-fetch-gen

Repository files navigation

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>   path to input OpenAPI TypeScript definition file
  -o, --output <path>  path to output generated client file (default: "./client.ts")
  -h, --help           display help for command

Example

Please refer to the examples.

schema.d.ts is generated from schema.yaml by openapi-typescript, and generated_client.ts is generated by this tool according to the schema.d.ts.

FYI, you can use the generated client as follows:

import { Client } from "./generated_client";

async function doSomething() {
  const client = new Client({ baseUrl: "https://api.example.com" });
  const users = await client.getUsers({
    query: {
      page: 1,
      pageSize: 10,
      membershipType: "PREMIUM",
    },
  });

  for (const user of users.data?.items ?? []) {
    console.log(`User: ${user.name}, Email: ${user.email}`);
  }
}

Default HTTP Headers

Generated clients support a generic type for default HTTP headers.

Example:

export class Client<HT extends Record<string, string>> {
  constructor(clientOptions: ClientOptions, defaultHeaders?: HT) {
    this.client = createClient<paths>(clientOptions);
    this.defaultHeaders = defaultHeaders ?? ({} as HT);
  }
  ...
}

You can create a client instance with default headers like this:

new Client({}, {"Authorization": "Bearer your-token", "Application-Version": "1.0.0"});

With this setup, endpoint methods that require these headers no longer need them to be explicitly passed each time.

For example, given the following schema:

"/users/bulk/{jobId}": {
  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 Bulk import job identifier */
        jobId: string;
      };
      cookie?: never;
    };

This tool generates an endpoint method using a type-level trick like this:

async getUsersBulkJobid(
  params: [
    Exclude<
      // Missed Header Keys for default headers
      keyof {
        Authorization: string;
        "Application-Version": string;
        "Something-Id": string;
      },
      Extract<
        // Provided header keys by default headers' keys
        keyof HT,
        keyof {
          Authorization: string;
          "Application-Version": string;
          "Something-Id": string;
        }
      >
    >,
  ] extends [never]
    ? {
        header?: {
          Authorization: string;
          "Application-Version": string;
          "Something-Id": string;
        };
        path: { jobId: string };
      }
    : {
        header:
          | (Pick<
              // Pick the header keys that are not in the default headers
              {
                Authorization: string;
                "Application-Version": string;
                "Something-Id": string;
              },
              Exclude<
                // Missed Header Keys for default headers
                keyof {
                  Authorization: string;
                  "Application-Version": string;
                  "Something-Id": string;
                },
                Extract<
                  // Provided header keys by default headers' keys
                  keyof HT,
                  keyof {
                    Authorization: string;
                    "Application-Version": string;
                    "Something-Id": string;
                  }
                >
              >
            > &
              Partial<
                // Disallow default headers' keys to be in the header param
                Record<
                  Extract<
                    // Provided header keys by default headers' keys
                    keyof HT,
                    keyof {
                      Authorization: string;
                      "Application-Version": string;
                      "Something-Id": string;
                    }
                  >,
                  never
                >
              >)
          | {
              Authorization: string;
              "Application-Version": string;
              "Something-Id": string;
            };
        path: { jobId: string };
      },
) {
  return await this.client.GET("/users/bulk/{jobId}", {
    params: {
      ...params,
      header: { ...this.defaultHeaders, ...params.header } as {
        Authorization: string;
        "Application-Version": string;
        "Something-Id": string;
      },
    },
  });
}

This signature allows you to:

  • Omit the defaulted headers and only pass additional ones (here, Something-Id):
client.getUsersBulkJobid({header: {"Something-Id": "foobar"}, path: {jobId: "123"}});
  • Override all headers, including the defaults:
client.getUsersBulkJobid({header: {"Authorization": "foo", "Application-Version": "bar", "Something-Id": "foobar"}, path: {jobId: "123"}});

If your default headers already include all required headers for the endpoint (e.g. {"Authorization": "Bearer your-token", "Application-Version": "1.0.0", "Something-Id": "123"} as the second constructor argument), you can omit the header parameter entirely:

client.getUsersBulkJobid({path: {jobId: "123"}});

NOTE:

In this context, the "default HTTP headers" are different from the headers in ClientOptions. The headers in ClientOptions are always sent implicitly, regardless of the header parameters specified in endpoint methods. In contrast, the "default HTTP headers" mechanism is intended to reduce the need to repeatedly specify common header parameters in each endpoint method when their values are already known.

Articles

For developers

How to publish this packages

$ pnpm ship

License

MIT