mrkeyoor.com_
Sat 08 Aug 17:40 UTC
npmWeb Frontendupdated 08 Aug 2026

openapi-fetch

openapi-fetch is a small Fetch API client whose TypeScript types come from an OpenAPI schema. After openapi-typescript generates a `paths` type, calls such as `client.GET('/users/{id}')` infer the allowed path, path and query parameters, body, success data, and documented error data. Runtime code stays close to fetch and works with browser, server, and framework-provided fetch implementations. It generates types, not endpoint-specific runtime methods or validators.

Verdict

One of the best lightweight clients for teams that already treat OpenAPI as tested source code. If the schema is aspirational or you need runtime validation, the attractive types can hide rather than remove integration risk.

API stability3/5The method-plus-schema-path call shape is clear and current 0.17.0 keeps native fetch objects at the boundary, which limits proprietary surface area. It is still a pre-1.0 package inside an actively developed monorepo, and newer features such as middleware, path-based proxies, serializers, and result inference give maintainers room for breaking refinements.
Docs5/5The official site separates setup, API, examples, middleware and authentication, and testing. It documents serializer defaults, stateful Request and Response bodies, middleware ordering, early responses, onError limits, path-based proxy cost, framework-provided fetch, CI typechecking, and noUncheckedIndexedAccess with concrete TypeScript examples.
Maintenance5/5npm serves version 0.17.0, the shared repository was pushed in August 2026, and it is not archived. GitHub reports 280 open issues and pull requests across the broader openapi-typescript monorepo, which also contains current package changelogs, documentation, tests, benchmarks, and examples for Next.js, SvelteKit, and Vue.
Ecosystem5/5openapi-fetch recorded 7,057,602 npm downloads in the fetched week, while the openapi-typescript repository has 8,290 stars. It shares types and documentation with the popular generator, works with any standard fetch implementation, and has examples or companion packages for React Query, SWR, Next.js, SvelteKit, Vue, and Nuxt.

Use it if

  • Your API has an accurate OpenAPI 3 schema and you want request and response types without a large generated SDK
  • You want endpoint paths, parameters, bodies, success data, and documented errors inferred from one generic client
  • You need a thin fetch-based runtime that works in React, Vue, Svelte, Node, or framework server loaders
  • You want middleware for auth, telemetry, caching, response policy, or fetch error translation without leaving native Request and Response types
Skip it if

Setup reality

Install `openapi-fetch` at runtime and `openapi-typescript` plus `typescript` as development tools. Then generate declarations, for example `npx openapi-typescript ./openapi.yaml -o ./src/api/schema.d.ts`, import the generated `paths` type, and create the client with it. That generation step belongs in a repeatable script and CI should run `tsc --noEmit`; a transpiler or linter alone may never report contract drift. The project strongly recommends `noUncheckedIndexedAccess`, which can expose unrelated unchecked lookups when enabled in an established codebase. The schema is now a build input, so decide whether to commit generated declarations or regenerate from a pinned artifact, and fail CI when output changes unexpectedly. Remote schemas may need credentials and make builds depend on network availability; a checked-in schema snapshot is more reproducible. At runtime the package expects a Fetch API implementation, defaulting to `globalThis.fetch`; framework loaders should pass their provided fetch when cookies, interception, or request context matters. Configure a `baseUrl`, but remember browser CORS and server absolute-URL rules still apply. HTTP failures do not throw by default: destructure `data`, `error`, and `response`, or register middleware that enforces throwing. Middleware order matters, with request hooks in registration order and response hooks in reverse. Request and Response bodies are stateful; middleware that reads them must clone first. Multipart bodies need a custom `bodySerializer`, and the browser must set the boundary, so do not force the JSON content type. Query arrays and objects use documented OpenAPI serialization defaults that may not match a legacy backend. Most importantly, types are only as current as the schema and provide no runtime validation of an unexpected server response.

Patterns

Generate the paths type from a local schemagenerate-types

npx openapi-typescript ./openapi.yaml -o ./src/api/schema.d.ts
npx tsc --noEmit

Pin the schema source and run the TypeScript compiler in CI. Generated types do not update until this command runs again.

Create a typed clientcreate-client

import createClient from 'openapi-fetch';
import type {paths} from './schema';

export const client = createClient<paths>({
  baseUrl: 'https://api.example.com/v1/',
});

The paths import must come from the generated declaration. Keep the base URL environment-specific without changing the schema path keys.

GET with typed path and query parametersget-path-params

const {data, error, response} = await client.GET('/users/{user_id}', {
  params: {
    path: {user_id: '42'},
    query: {include: 'teams'},
  },
});

if (error) console.error(response.status, error);
else console.log(data);

The literal path must match the schema key exactly. Success and error shapes are inferred from the documented responses.

POST a schema-checked bodypost-json

const result = await client.POST('/users', {
  body: {name: 'Ada', email: 'ada@example.com'},
});

if (result.error) throw new Error('create failed');
console.log(result.data.id);

The body is checked at compile time and serialized as JSON by default. It is not validated again at runtime.

Make non-2xx responses throwthrow-http-errors

import type {Middleware} from 'openapi-fetch';

const throwOnError: Middleware = {
  onResponse({response}) {
    if (!response.ok) {
      throw new Error(`${response.url}: ${response.status} ${response.statusText}`);
    }
  },
};
client.use(throwOnError);

onError handles exceptions from fetch, not 4xx or 5xx responses. Response middleware is the place to impose throwing semantics.

Attach a fresh bearer tokenattach-auth

const authMiddleware: Middleware = {
  onRequest({request}) {
    const token = tokenStore.current();
    if (token) request.headers.set('Authorization', `Bearer ${token}`);
    return request;
  },
};

client.use(authMiddleware);

Module-level token state is unsafe for multi-user server processes. Read authentication from request-scoped context there.

Cancel a typed requestabort-request

const controller = new AbortController();
const pending = client.GET('/reports/{id}', {
  params: {path: {id: 'monthly'}},
  signal: controller.signal,
});

controller.abort();
await pending;

An abort is a fetch exception, not typed HTTP error data. Handle DOMException or translate it in onError middleware.

Serialize a multipart requestupload-form-data

await client.POST('/uploads', {
  body: {file, caption: 'invoice'},
  bodySerializer(body) {
    const form = new FormData();
    form.append('file', body.file);
    form.append('caption', body.caption);
    return form;
  },
});

When the serializer returns FormData, openapi-fetch omits Content-Type so the browser can add the multipart boundary.

Match an API's array query formatcustomize-query-arrays

const client = createClient<paths>({
  baseUrl: 'https://api.example.com/',
  querySerializer: {
    array: {style: 'pipeDelimited', explode: false},
    object: {style: 'deepObject', explode: true},
  },
});

The default for arrays is exploded form style. Set this only when the OpenAPI server contract expects another serialization.

Read a response as a streamstream-response

const {data: stream, error} = await client.GET('/exports/{id}', {
  params: {path: {id: 'latest'}},
  parseAs: 'stream',
});

if (error) throw new Error('export failed');
const reader = stream.getReader();

parseAs stream skips response parsing. The generated success type may need schema metadata that accurately describes the binary response.

Pass a framework-scoped fetch implementationuse-framework-fetch

export function apiFor(fetch: typeof globalThis.fetch) {
  return createClient<paths>({
    baseUrl: 'https://api.example.com/',
    fetch,
  });
}

Framework fetch wrappers can carry cookies, caching, tracing, or test interception that globalThis.fetch would bypass.

Inspect the generated Request in a unit testtest-request

const mockFetch = vi.fn(async () => Response.json({id: '42'}));
const testClient = createClient<paths>({
  baseUrl: 'https://api.example.com/',
  fetch: mockFetch,
});

await testClient.POST('/users', {body: {name: 'Ada'}});
const request = mockFetch.mock.calls[0][0] as Request;
expect(await request.json()).toEqual({name: 'Ada'});

A Request body is single-use. Clone it first if middleware or multiple assertions also need to consume it.

Alternatives

PackageRegistryPick it when
openapi-client-axiosnpmYou want OpenAPI operation lookup on top of Axios and prefer its interceptors and response model
openapi-typescript-fetchnpmYou prefer generated operation functions and a smaller older fetch-oriented API
axiosnpmYour API lacks a trustworthy OpenAPI schema and manual request types plus mature interceptors are a better fit