npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@kecrily/openapi-fetch

v0.3.0

Published

Ultra-fast fetching for TypeScript generated automatically from your OpenAPI schema. Weighs in at 1 kb and has virtually zero runtime. Works with React, Vue, Svelte, or vanilla JS.

Downloads

11

Readme

openapi-fetch is an ultra-fast fetch client for TypeScript using your OpenAPI schema. Weighs in at 1 kb and has virtually zero runtime. Works with React, Vue, Svelte, or vanilla JS.

| Library | Size (min) | | :----------------------------- | ---------: | | openapi-fetch | 1 kB | | openapi-typescript-fetch | 4 kB | | openapi-typescript-codegen | 345 kB |

The syntax is inspired by popular libraries like react-query or Apollo client, but without all the bells and whistles and in a 1 kb package.

import createClient from "openapi-fetch";
import { paths } from "./v1"; // (generated from openapi-typescript)

const { get, post } = createClient<paths>({ baseUrl: "https://myapi.dev/v1/" });

// Type-checked request
await post("/create-post", {
  body: {
    title: "My New Post",
    // ❌ Property 'publish_date' is missing in type …
  },
});

// Type-checked response
const { data, error } = await get("/blogposts/my-blog-post");

console.log(data.title); // ❌ 'data' is possibly 'undefined'
console.log(error.message); // ❌ 'error' is possibly 'undefined'
console.log(data?.foo); // ❌ Property 'foo' does not exist on type …

Notice there are no generics, and no manual typing. Your endpoint’s exact request & response was inferred automatically off the URL. This makes a big difference in the type safety of your endpoints! This eliminates all of the following:

  • ✅ No typos in URLs or params
  • ✅ All parameters, request bodies, and responses are type-checked and 100% match your schema
  • ✅ No manual typing of your API
  • ✅ Eliminates any types that hide bugs
  • ✅ Also eliminates as type overrides that can also hide bugs
  • ✅ All of this in a 1 kB client package 🎉

🔧 Setup

Install this library along with openapi-typescript:

npm i openapi-fetch
npm i -D openapi-typescript

Next, generate TypeScript types from your OpenAPI schema using openapi-typescript:

npx openapi-typescript ./path/to/api/v1.yaml -o ./src/lib/api/v1.d.ts

⚠️ Be sure to validate your schemas! openapi-typescript will err on invalid schemas.

Lastly, be sure to run typechecking in your project. This can be done by adding tsc --noEmit to your npm scripts like so:

{
  "scripts": {
    "test:ts": "tsc --noEmit"
  }
}

And run npm run test:ts in your CI to catch type errors.

Tip

Always use tsc --noEmit to check for type errors! Your build tools (Vite, esbuild, webpack, etc.) won’t typecheck as accurately as the TypeScript compiler itself.

🏓 Usage

Using openapi-fetch is as easy as reading your schema! For example, given the following schema:

OpenAPI schema example

Here’s how you’d fetch GET /blogpost/{post_id} and POST /blogposts:

import createClient from "openapi-fetch";
import { paths } from "./v1";

const { get, post } = createClient<paths>({ baseUrl: "https://myapi.dev/v1/" });

const { data, error } = await get("/blogposts/{post_id}", {
  params: {
    path: { post_id: "my-post" },
    query: { version: 2 },
  },
});

const { data, error } = await post("/blogposts", {
  body: {
    title: "New Post",
    body: "<p>New post body</p>",
    publish_date: new Date("2023-03-01T12:00:00Z").getTime(),
  },
});

Pathname

The pathname of get(), put(), post(), etc. must match your schema literally. Note in the example, the URL is /blogposts/{post_id}. This library will replace all path params for you (so they can be typechecked)

Tip

openapi-fetch infers types from the URL. Prefer static string values over dynamic runtime values, e.g.:

  • ✅ "/blogposts/{post_id}"
  • ❌ [...pathParts].join("/") + "{post_id}"

Request

The get() request shown needed the params object that groups parameters by type (path or query). If a required param is missing, or the wrong type, a type error will be thrown.

The post() request required a body object that provided all necessary requestBody data.

Response

All methods return an object with data, error, and response.

  • data will contain that endpoint’s 2xx response if the server returned 2xx; otherwise it will be undefined
  • error likewise contains that endpoint’s 4xx/5xx response if the server returned either; otherwise it will be undefined
    • Note: default will also be interpreted as error, since its intent is handling unexpected HTTP codes
  • response has response info like status, headers, etc. It is not typechecked.

API

Create Client

createClient accepts the following options, which set the default settings for all subsequent fetch calls.

createClient<paths>(options);

| Name | Type | Description | | :-------------- | :------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | baseUrl | string | Prefix all fetch URLs with this option (e.g. "https://myapi.dev/v1/"). | | fetch | fetch | Fetch function used for requests (defaults to globalThis.fetch) | | (Fetch options) | | Any valid fetch option (headers, mode, cache, signal …) (docs) |

Fetch options

import { paths } from "./v1";

const { get, put, post, del, options, head, patch, trace } = createClient<paths>({ baseUrl: "https://myapi.dev/v1/" });

const { data, error, response } = await get("/my-url", options);

| Name | Type | Description | | :---------------- | :-----------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | params | ParamsObject | Provide path and query params from the OpenAPI schema | | params.path | { [name]: value } | Provide all path params (params that are part of the URL) | | params.query | { [name]: value } | Provide all query params (params that are part of the <a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/searchParams" target="_blank" rel="noopener noreferrer">searchParams</a> | | body |{ [name]:value } | The <a href="https://spec.openapis.org/oas/latest.html#request-body-object" target="_blank" rel="noopener noreferrer">requestBody</a> data, if needed (PUT/POST/PATCH/DEL only) | |querySerializer | QuerySerializer | (optional) Override default param serialization (see [Parameter Serialization](#parameter-serialization)) | | (Fetch options) | | Any valid fetch option (headers, mode, cache, signal` …) (docs) |

🔀 Parameter Serialization

In the spirit of being lightweight, this library only uses URLSearchParams to serialize parameters. So for complex query param types (e.g. arrays) you’ll need to provide your own querySerializer() method that transforms query params into a URL-safe string:

import createClient from "openapi-fetch";
import { paths } from "./v1"; // generated from openapi-typescript

const { get, post } = createClient<paths>({ baseUrl: "https://myapi.dev/v1/" });

const { data, error } = await get("/post/{post_id}", {
  params: {
    path: { post_id: "my-post" },
    query: { version: 2 },
  },
  querySerializer: (q) => `v=${q.version}`, // ✅ Still typechecked based on the URL!
});

Note that this happens at the request level so that you still get correct type inference for that URL’s specific query params.

Thanks, @ezpuzz!

Examples

🔒 Handling Auth

Authentication often requires some reactivity dependent on a token. Since this library is so low-level, there are myriad ways to handle it:

Nano Stores

Here’s how it can be handled using nanostores, a tiny (334 b), universal signals store:

// src/lib/api/index.ts
import { atom, computed } from "nanostores";
import createClient from "openapi-fetch";
import { paths } from "./v1";

export const authToken = atom<string | undefined>();
someAuthMethod().then((newToken) => authToken.set(newToken));

export const client = computed(authToken, (currentToken) =>
  createClient<paths>({
    headers: currentToken ? { Authorization: `Bearer ${currentToken}` } : {},
    baseUrl: "https://myapi.dev/v1/",
  })
);

// src/some-other-file.ts
import { client } from "./lib/api";

const { get, post } = client.get();

get("/some-authenticated-url", {
  /* … */
});

Vanilla JS Proxies

You can also use proxies which are now supported in all modern browsers:

// src/lib/api/index.ts
import createClient from "openapi-fetch";
import { paths } from "./v1";

let authToken: string | undefined = undefined;
someAuthMethod().then((newToken) => (authToken = newToken));

const baseClient = createClient<paths>({ baseUrl: "https://myapi.dev/v1/" });
export default new Proxy(baseClient, {
  get(_, key: keyof typeof baseClient) {
    const newClient = createClient<paths>({
      headers: authToken ? { Authorization: `Bearer ${authToken}` } : {},
      baseUrl: "https://myapi.dev/v1/",
    });
    return newClient[key];
  },
});

// src/some-other-file.ts
import client from "./lib/api";

client.get("/some-authenticated-url", {
  /* … */
});

🎯 Project Goals

  1. Infer types automatically from OpenAPI schemas without generics (or, only the absolute minimum needed)
  2. Respect the native fetch() API while reducing boilerplate (such as await res.json())
  3. Be as small and light as possible