openapi-typescript-fetch

A typed fetch client for openapi-typescript that turns your generated OpenAPI types into a fully type-safe API client.

Library
npm
v2.2.1
257stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
36/100Needs Attention
Development Activity0
Maintenance20
Community48
Maturity56
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
70/100Good
Architecture78
Code Quality80
Innovation75
Learning Curve45

openapi-typescript-fetch pairs with openapi-typescript’s generated types to give you a fully typed fetch client without any code-generation step of its own. You build a Fetcher for your generated paths type, then create per-operation functions with .path(...).method(...).create() — each call is typed end-to-end, with path parameters, query parameters, request bodies, and response payloads all inferred directly from your OpenAPI or Swagger schema.

Error handling is typed too: a failed request throws a per-operation Error subclass whose getActualType() returns a discriminated union over the status codes your schema declares, so you can branch on error.status and get the exact response shape for that code, including a default catch-all. A lightweight middleware chain lets you wrap every request (logging, auth headers, retries) without touching the generated call sites, and the library wraps only the native Fetch API with zero runtime dependencies.

What You Get

  • A Fetcher.for<paths>() factory that builds one typed client per fetch operation, with argument and response types inferred straight from your generated OpenAPI paths.
  • Discriminated-union error types via OpErrorType/getActualType(), so a 400 and a 500 response can carry different, individually typed payloads.
  • A middleware chain (fetcher.use(...)) for cross-cutting concerns like logging, auth headers, or retries, applied to every generated call without touching call sites.
  • Utility types (OpArgType, OpReturnType, OpErrorType, FetchArgType, etc.) for referencing the inferred types of any operation elsewhere in your codebase.
  • A runtime-configurable baseUrl (static string or function), useful for switching environments or multi-tenant base URLs without rebuilding the client.

Common Use Cases

  • Building a typed frontend or backend client against a REST API you control the OpenAPI schema for, catching parameter/response mismatches at compile time.
  • Consuming a third-party OpenAPI/Swagger spec (e.g. a Petstore- or Stripe-style API) and getting full autocomplete without hand-writing request/response types.
  • Adding auth headers or request logging across every API call in an app via middleware, instead of duplicating that logic at every request site.
  • Handling per-status error responses distinctly in UI code, e.g. showing field-level validation errors on a 400 versus a generic message on a 500.

Under The Hood

Architecture The library is built around a builder pattern — Fetcher.for<Paths>() returns an object whose .path(path).method(method).create() chain closes over per-instance state (baseUrl, defaultInit, a middlewares array) rather than using classes. Request execution flows through wrapMiddlewares, a recursive continuation-passing composition (Koa/Express-style onion middleware) wrapping a base fetchJson function that normalizes the Fetch Response into an ApiResponse and throws a typed ApiError on non-ok status. createFetch wraps each generated function to catch ApiError and rethrow it as an operation-specific Error subclass (defined dynamically inside a closure) carrying getActualType() for status-based narrowing. There is no separate transport, cache, or plugin layer beyond this middleware chain — nearly the entire runtime surface lives in fetcher.ts, with index.ts as a thin re-export, types.ts as pure type-level logic with zero runtime code, and utils.ts a single helper. The design is a flat, single-module structure appropriately scoped to a narrow purpose, though it has no dependency injection or pluggable transport beyond middleware.

Tech Stack TypeScript targeting ESNext with zero runtime dependencies (the dependencies field is absent from package.json entirely). The build uses esbuild (build.js) to emit dual CJS (dist/index.cjs, Node platform) and ESM (dist/index.js, neutral platform) bundles, with declaration files generated separately via npm-dts. Tests run on Jest with ts-jest under a jsdom environment, using Mock Service Worker (msw) to intercept fetch calls and the whatwg-fetch polyfill to supply fetch in jsdom. Linting is ESLint plus typescript-eslint and eslint-config-prettier; Prettier is configured for single quotes, no semicolons, and trailing commas. No CI workflow file is present in the repository, though a Codecov badge in the README implies coverage reporting was wired up via an external service at some point.

Code Quality Tests are substantive rather than superficial: fetch.test.ts exercises runtime behavior across GET/POST/PUT/PATCH/DELETE with mocked responses, header merging, and discriminated error narrowing, while infer.test.ts is a type-only test file asserting compile-time inference correctness. A hand-written OpenAPI-shaped fixture (test/paths.ts) is used across the runtime tests, and real-world specs (Petstore, Stripe v2 and v3) sit under test/examples as type-inference regression fixtures. Error handling is explicit and typed throughout — ApiError correctly resets its prototype chain when extending a built-in, and failures are converted to operation-specific subclasses rather than swallowed. tsconfig.json enables strict and noUncheckedIndexedAccess, a stricter-than-default combination, and ESLint/Prettier enforce consistent style. The one visible gap is the absence of a checked-in CI workflow file in this clone.

API Design The central ergonomic idea is that a single generic type parameter — the openapi-typescript-generated paths type — drives the entire fetch surface via Fetcher.for<paths>(), and every function built with .path(p).method(m).create() gets fully inferred argument and return types with zero manual annotation and no separate code-generation step of its own. The discriminated-union error type (getActualType() narrowing on status) is a distinctive piece of DX: many typed-fetch libraries leave error responses as any, whereas this one preserves per-status-code response shapes through to the catch block. Getting started takes only a couple of lines (Fetcher.for<paths>(), .configure({baseUrl}), then one .create() per endpoint used), and the README documents utility types (OpArgType, FetchReturnType, etc.) for referencing inferred types elsewhere in an app. The middleware signature (url, init, next) => Promise<ApiResponse> mirrors familiar Koa/Express-style middleware, keeping the learning curve low for anyone who has used those frameworks.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search