Openapi To Fetch

OpenAPI & Swagger to Native Fetch TypeScript Client Generator

Generate zero-dependency native fetch TypeScript API methods with query string serialization, headers, and response types.

6 operations • 4 models
Presets:
Architecture:
Response Format:
YAMLBase: https://petstore3.swagger.io/api/v3
OpenAPI / Swagger Specification (YAML or JSON)YAML
/**
 * Zero-Dependency Native Fetch TypeScript Client
 * API: Swagger Petstore (v1.0.0)
 * Base URL: https://petstore3.swagger.io/api/v3
 * Standard Web Fetch API compatible with Browsers, Node.js 18+, Bun, Deno, and Next.js
 */

/**
 * Generated TypeScript Interfaces & Models
 * API: Swagger Petstore (v1.0.0)
 * Spec: OpenAPI 3.0.3
 * Generated automatically by ToolsAA Native Fetch Generator
 */

Category {
  id?: number | undefined;
  name?: string | undefined;
}

Tag {
  id?: number | undefined;
  name?: string | undefined;
}

Pet {
  id: number;
  name: string;
  category?: Category | undefined;
  photoUrls?: string[] | undefined;
  tags?: Tag[] | undefined;
  status?: 'available' | 'pending' | 'sold' | undefined;
}

ApiResponse {
  code?: number | undefined;
  type?: string | undefined;
  message?: string | undefined;
}

// ============================================================================
// Operation Parameter Interfaces
// ============================================================================

ListPetsQueryParams {
  /** Status values that need to be considered for filter */
  status?: 'available' | 'pending' | 'sold' | undefined;
  /** How many items to return at one time (max 100) */
  limit?: number | undefined;
}

GetPetByIdPathParams {
  /** The id of the pet to retrieve */
  petId: number;
}

DeletePetPathParams {
  /** Pet id to delete */
  petId: number;
}

DeletePetHeaders {
  api_key?: string | undefined;
}

UploadFilePathParams {
  /** ID of pet to update */
  petId: number;
}

UploadFileQueryParams {
  additionalMetadata?: string | undefined;
}


// ============================================================================
// Runtime ApiError & Client Infrastructure
// ============================================================================

export class ApiError extends Error {
  readonly status: number;
  readonly statusText: string;
  readonly data: unknown;
  readonly headers: Headers;

  constructor(status: number, statusText: string, data: unknown, headers: Headers) {
    super(`HTTP ${status} ${statusText}`);
    this.name = 'ApiError';
    this.status = status;
    this.statusText = statusText;
    this.data = data;
    this.headers = headers;
  }
}

FetchClientConfig {
  baseUrl?: string;
  headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
  customFetch?: typeof fetch;
  timeoutMs?: number;
  onRequest?: (url: string, init: RequestInit) => void | Promise<void>;
  onResponse?: (response: Response) => void | Promise<void>;
}

RequestOptions extends Omit<RequestInit, 'body' | 'headers'> {
  headers?: Record<string, string>;
  query?: Record<string, any>;
  timeoutMs?: number;
}

ApiResponseEnvelope<T> {
  data: T;
  status: number;
  headers: Headers;
}

function serializeQuery(query?: Record<string, any>): string {
  if (!query) return '';
  const params = new URLSearchParams();
  for (const [key, value] of Object.entries(query)) {
    if (value === undefined || value === null) continue;
    if (Array.isArray(value)) {
      value.forEach((v) => params.append(key, String(v)));
    } else {
      params.append(key, String(value));
    }
  }
  const str = params.toString();
  return str ? `?${str}` : '';
}

async function executeFetch<T>(
  config: FetchClientConfig,
  method: string,
  path: string,
  options?: RequestOptions & { body?: any }
): Promise<T> {
  const baseUrl = (config.baseUrl || 'https://petstore3.swagger.io/api/v3').replace(/\/+$/, '');
  const queryString = serializeQuery(options?.query);
  const fullUrl = `${baseUrl}${path.startsWith('/') ? path : '/' + path}${queryString}`;

  let dynamicHeaders: Record<string, string> = {};
  if (typeof config.headers === 'function') {
    dynamicHeaders = await config.headers();
  } else if (config.headers) {
    dynamicHeaders = config.headers;
  }

  const headers = new Headers({
    ...dynamicHeaders,
    ...options?.headers,
  });

  let requestBody: any = undefined;
  if (options?.body !== undefined) {
    if (typeof FormData !== 'undefined' && options.body instanceof FormData) {
      requestBody = options.body;
      // Browser sets multipart boundary automatically, remove conflicting content-type
      headers.delete('Content-Type');
    } else if (typeof Blob !== 'undefined' && options.body instanceof Blob) {
      requestBody = options.body;
    } else if (typeof options.body === 'object') {
      if (!headers.has('Content-Type')) {
        headers.set('Content-Type', 'application/json');
      }
      requestBody = JSON.stringify(options.body);
    } else {
      requestBody = String(options.body);
    }
  }

  const timeoutMs = options?.timeoutMs ?? config.timeoutMs ?? 15000;
  let signal = options?.signal;
  let timeoutId: any = null;

  if (!signal && typeof AbortController !== 'undefined') {
    const controller = new AbortController();
    timeoutId = setTimeout(() => controller.abort(new Error('Request Timeout')), timeoutMs);
    signal = controller.signal;
  }

  const requestInit: RequestInit = {
    method,
    headers,
    body: requestBody,
    signal,
    ...options,
  };

  if (config.onRequest) {
    await config.onRequest(fullUrl, requestInit);
  }

  const fetchFn = config.customFetch || fetch;

  try {
    const response = await fetchFn(fullUrl, requestInit);
    if (config.onResponse) {
      await config.onResponse(response);
    }

    let responseData: any = null;
    if (response.status !== 204) {
      const contentType = response.headers.get('content-type') || '';
      if (contentType.includes('application/json')) {
        responseData = await response.json();
      } else {
        responseData = await response.text();
      }
    }

    if (!response.ok) {
      throw new ApiError(response.status, response.statusText, responseData, response.headers);
    }

    return responseData as T;
  } finally {
    if (timeoutId) clearTimeout(timeoutId);
  }
}

// ============================================================================
// Flagship ApiClient Class
// ============================================================================

export class ApiClient {
  private config: FetchClientConfig;

  constructor(config: FetchClientConfig = {}) {
    this.config = config;
  }

  public setBaseUrl(url: string): void {
    this.config.baseUrl = url;
  }

  public setHeaders(headers: Record<string, string>): void {
    this.config.headers = headers;
  }

  public setBearerToken(token: string): void {
    this.config.headers = { ...((typeof this.config.headers === 'object' ? this.config.headers : {})), Authorization: `Bearer ${token}` };
  }

  public setApiKey(key: string, headerName: string = 'X-API-Key'): void {
    this.config.headers = { ...((typeof this.config.headers === 'object' ? this.config.headers : {})), [headerName]: key };
  }

  /**
   * List all pets
   * @description Returns all pets from the store with optional status and tag filtering.
   * @route GET /pets
   * @param query URL search query parameters
   * @param options Additional RequestInit options
   * @throws {ApiError}
   */
  async listPets(query?: ListPetsQueryParams, options?: RequestOptions): Promise<Pet[]> {
    const path = '/pets';
    return executeFetch<Pet[]>(this.config, 'GET', path, { ...options, query });
  }

  /**
   * Create a pet
   * @description Adds a new pet to the catalog.
   * @route POST /pets
   * @param body Request payload (application/json)
   * @param options Additional RequestInit options
   * @throws {ApiError}
   */
  async createPet(body: Pet, options?: RequestOptions): Promise<Pet> {
    const path = '/pets';
    return executeFetch<Pet>(this.config, 'POST', path, { ...options, body });
  }

  /**
   * Info for a specific pet
   * @route GET /pets/{petId}
   * @param pathParams URL path interpolation parameters
   * @param options Additional RequestInit options
   * @throws {ApiError}
   */
  async getPetById(pathParams: GetPetByIdPathParams, options?: RequestOptions): Promise<Pet> {
    const path = `/pets/${encodeURIComponent(String(pathParams['petId']))}`;
    return executeFetch<Pet>(this.config, 'GET', path, { ...options });
  }

  /**
   * Deletes a pet
   * @route DELETE /pets/{petId}
   * @param pathParams URL path interpolation parameters
   * @param options Additional RequestInit options
   * @throws {ApiError}
   */
  async deletePet(pathParams: DeletePetPathParams, options?: RequestOptions): Promise<void> {
    const path = `/pets/${encodeURIComponent(String(pathParams['petId']))}`;
    return executeFetch<void>(this.config, 'DELETE', path, { ...options });
  }

  /**
   * Uploads an image
   * @route POST /pets/{petId}/uploadImage
   * @param pathParams URL path interpolation parameters
   * @param body Request payload (multipart/form-data)
   * @param query URL search query parameters
   * @param options Additional RequestInit options
   * @throws {ApiError}
   */
  async uploadFile(pathParams: UploadFilePathParams, body?: {
   /** File to upload @format binary */
  file?: Blob | File | undefined;
}, query?: UploadFileQueryParams, options?: RequestOptions): Promise<ApiResponse> {
    const path = `/pets/${encodeURIComponent(String(pathParams['petId']))}/uploadImage`;
    return executeFetch<ApiResponse>(this.config, 'POST', path, { ...options, query, body });
  }

  /**
   * Returns pet inventories by status
   * @route GET /store/inventory
   * @param options Additional RequestInit options
   * @throws {ApiError}
   */
  async getInventory(options?: RequestOptions): Promise<Record<string, number>> {
    const path = '/store/inventory';
    return executeFetch<Record<string, number>>(this.config, 'GET', path, { ...options });
  }

}

api = new ApiClient();
export default ApiClient;
6
API Operations
4
TypeScript Models
9.5 KB
Client File Size

100% Client-Side Privacy

Zero server uploads or analytics telemetry. Your OpenAPI specifications and proprietary API contracts are parsed strictly within local browser memory.

Zero-Dependency Native Fetch

Generates pure standard Web Fetch API code compatible with modern browsers, Node.js 18+, Bun, Deno, and Next.js Server Components without Axios or external SDK bloat.

Full Type-Safe Serialization

Automatic URL path interpolation, URLSearchParams query string serialization, custom headers, multipart FormData, and typed error handling.

Technical Reference & Architecture Guide

OpenAPI & Swagger to Native Fetch TypeScript Client Generator — Reference Guide

Generate zero-dependency native fetch TypeScript API methods with query string serialization, headers, and response types.

Key Parameters & Controls

architecture'class' | 'functional'

Determines client generation architecture: OOP client class with shared configuration or tree-shakeable standalone functions.

Default: 'class'
unwrapResponse'direct' | 'envelope'

Specifies return payload structure: unwrapped response body (Promise<T>) or full envelope ({ data, status, headers }).

Default: 'direct'
includeJsdocboolean

Injects OpenAPI summary, description, and parameter annotations into generated TypeScript methods as JSDoc comments.

Default: true
strictNullChecksboolean

Enforces strict optional properties and explicit nullable unions (T | null) aligned with TypeScript strict mode.

Default: true
includeTimeoutboolean

Adds built-in AbortController signal handling with configurable timeout in milliseconds for robust network resilience.

Default: true
includeMockStubsboolean

Generates realistic mock fixtures and stubbed client implementations for offline unit tests and UI prototyping.

Default: false

Pro Tips & Best Practices

Select 'Functional SDK' architecture

Select 'Functional SDK' architecture in micro-frontends or serverless edge handlers. Modern bundlers like Rollup, Vite, and Webpack will tree-shake unused API operations, trimming runtime bundle size.

Seamless Next.js App Router Integration

Pass custom RequestInit options such as next: { revalidate: 60, tags: ['users'] } or cache: 'no-store' directly into generated fetch calls to utilize Next.js incremental static regeneration and data cache.

Type-Safe Error Discrimination

When using 'Full Response' envelope mode, wrap invocations in standard try/catch blocks or inspect response status codes directly without needing Axios interceptors or third-party error wrappers.

Common Gotchas & Troubleshooting

Swagger 2.0 schema references ($ref) fail to resolve in definitions

The parser automatically normalizes legacy Swagger 2.0 #/definitions/ pointers and OpenAPI 3.x #/components/schemas/ targets into clean, PascalCase TypeScript interfaces.

Array query parameters formatted with duplicate keys or bracket notations

Configure standard URLSearchParams serialization or append explode formatting based on your backend framework expectations (e.g., Express, FastAPI, or Spring Boot).

CORS or Cookie credentials omitted in browser environments

Provide { credentials: 'include' } in the global client configuration constructor or override per-request headers to transmit session cookies and authorization tokens.

Frequently Asked Questions

Why generate native fetch clients instead of using Axios or heavy SDKs?▼

Native fetch is a built-in web standard supported across all modern browsers, Node.js 18+, Bun, Deno, and Edge runtimes. Eliminating Axios or heavyweight generator runtimes saves 15-40KB in bundle size and eliminates external dependency vulnerabilities.

How are URL path parameters and query strings serialized?▼

Path parameters (e.g. /users/{id}) are safely interpolated with encodeURIComponent, while query parameters are parsed and serialized via native URLSearchParams with automatic undefined and null filtering.

Are my proprietary API specifications or internal endpoints transmitted to any server?▼

No. The entire OpenAPI parsing, schema AST traversal, and TypeScript code generation runs 100% locally in your browser memory via Web Workers and WebAssembly. Zero spec data leaves your machine.

Can I generate React Hooks or TanStack Query fetchers from this tool?▼

Yes. Switch to the 'React Hooks' tab to export ready-to-use custom hooks or query functions compatible with TanStack Query (React Query) and SWR.

How does the generated client handle 4xx and 5xx HTTP response statuses?▼

By default, fetch does not reject on HTTP error statuses. The generated client automatically verifies response.ok, parsing JSON error payloads into a typed ApiError with status code and headers before throwing.

Related Tools & Utilities

All Tools →