Skip to content

Commit 0380e9a

Browse files
authored
Add multipart requestBody support, update docs (#1189)
* Add multipart requestBody support, update docs * Drop Node 16 tests
1 parent a022a00 commit 0380e9a

File tree

14 files changed

+262
-205
lines changed

14 files changed

+262
-205
lines changed

.changeset/long-moose-scream.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"openapi-fetch": minor
3+
---
4+
5+
Add multipart/form-data request body support

.changeset/wise-adults-deny.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"openapi-fetch": minor
3+
---
4+
5+
Breaking: openapi-fetch now just takes the first media type it finds rather than preferring JSON. This is because in the case of `multipart/form-data` vs `application/json`, it’s not inherently clear which you’d want. Or if there were multiple JSON-like media types.

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
runs-on: ubuntu-latest
3636
strategy:
3737
matrix:
38-
node-version: [16.x, 18.x, 20.x]
38+
node-version: [18.x, 20.x]
3939
steps:
4040
- uses: actions/checkout@v3
4141
- uses: pnpm/action-setup@v2

docs/public/assets/openapi-schema.png

18.8 KB
Loading

docs/scripts/update-contributors.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ const OPENAPI_TS_CONTRIBUTORS = [
8989
]),
9090
];
9191

92-
export const OPENAPI_FETCH_CONTRIBUTORS = [...new Set(["drwpow", "fergusean", "shinzui", "ezpuzz", "KotoriK", "fletchertyler914", "nholik", "roj1512", "nickcaballero", "hd-o", "kecrily"])];
92+
export const OPENAPI_FETCH_CONTRIBUTORS = [...new Set(["drwpow", "fergusean", "shinzui", "ezpuzz", "KotoriK", "fletchertyler914", "nholik", "roj1512", "nickcaballero", "hd-o", "kecrily", "psychedelicious"])];
9393

9494
async function main() {
9595
const openapiTS = Promise.all(OPENAPI_TS_CONTRIBUTORS.map(async (username) => ({ username, avatar: await fetchAvatar(username) })));

docs/src/content/docs/openapi-fetch/api.md

+50-27
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ description: openapi-fetch API
1111
createClient<paths>(options);
1212
```
1313

14-
| Name | Type | Description |
15-
| :-------------- | :------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16-
| `baseUrl` | `string` | Prefix all fetch URLs with this option (e.g. `"https://myapi.dev/v1/"`). |
17-
| `fetch` | `fetch` | Fetch function used for requests (defaults to `globalThis.fetch`) |
18-
| (Fetch options) | | Any valid fetch option (`headers`, `mode`, `cache`, `signal` …) (<a href="https://developer.mozilla.org/en-US/docs/Web/API/fetch#options" target="_blank" rel="noopener noreferrer">docs</a>) |
14+
| Name | Type | Description |
15+
| :---------------- | :-------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16+
| `baseUrl` | `string` | Prefix all fetch URLs with this option (e.g. `"https://myapi.dev/v1/"`). |
17+
| `fetch` | `fetch` | Fetch function used for requests (defaults to `globalThis.fetch`) |
18+
| `querySerializer` | QuerySerializer | (optional) Serialize query params for all requests (default: `new URLSearchParams()`) |
19+
| `bodySerializer` | BodySerializer | (optional) Serialize request body object for all requests (default: `JSON.stringify()`) |
20+
| (Fetch options) | | Any valid fetch option (`headers`, `mode`, `cache`, `signal` …) (<a href="https://developer.mozilla.org/en-US/docs/Web/API/fetch#options" target="_blank" rel="noopener noreferrer">docs</a>) |
1921

2022
## Fetch options
2123

@@ -25,35 +27,56 @@ The following options apply to all request methods (`.get()`, `.post()`, etc.)
2527
client.get("/my-url", options);
2628
```
2729

28-
| Name | Type | Description |
29-
| :---------------- | :---------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
30-
| `params` | ParamsObject | Provide `path` and `query` params from the OpenAPI schema |
31-
| `params.path` | `{ [name]: value }` | Provide all `path` params (params that are part of the URL) |
32-
| `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> |
33-
| `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) |
34-
| `querySerializer` | QuerySerializer | (optional) Override default param serialization (see [Parameter Serialization](#parameter-serialization)) |
35-
| `parseAs` | `"json"` \| `"text"` \| `"arrayBuffer"` \| `"blob"` \| `"stream"` | Decide how to parse the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Response/body" target="_blank" rel="noopener noreferrer">response body</a>, with `"stream"` skipping processing altogether (default: `"json"`) |
36-
| (Fetch options) | | Any valid fetch option (`headers`, `mode`, `cache`, `signal` …) (<a href="https://developer.mozilla.org/en-US/docs/Web/API/fetch#options" target="_blank" rel="noopener noreferrer">docs</a>) |
30+
| Name | Type | Description |
31+
| :---------------- | :---------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
32+
| `params` | ParamsObject | Provide `path` and `query` params from the OpenAPI schema |
33+
| `params.path` | `{ [name]: value }` | Provide all `path` params (params that are part of the URL) |
34+
| `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> |
35+
| `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) |
36+
| `querySerializer` | QuerySerializer | (optional) Serialize query params for this request only (default: `new URLSearchParams()`) |
37+
| `bodySerializer` | BodySerializer | (optional) Serialize request body for this request only (default: `JSON.stringify()`) |
38+
| `parseAs` | `"json"` \| `"text"` \| `"arrayBuffer"` \| `"blob"` \| `"stream"` | Parse the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Response/body" target="_blank" rel="noopener noreferrer">response body</a>, with `"stream"` skipping processing altogether (default: `"json"`) |
39+
| (Fetch options) | | Any valid fetch option (`headers`, `mode`, `cache`, `signal` …) (<a href="https://developer.mozilla.org/en-US/docs/Web/API/fetch#options" target="_blank" rel="noopener noreferrer">docs</a>) |
3740

38-
### Parameter Serialization
41+
### querySerializer
3942

40-
In the spirit of being lightweight, this library only uses <a href="https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams" target="_blank" rel="noopener noreferrer">URLSearchParams</a> to <a href="https://swagger.io/docs/specification/serialization/" target="_blank" rel="noopener noreferrer">serialize parameters</a>. 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:
43+
This library uses <a href="https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams" target="_blank" rel="noopener noreferrer">URLSearchParams</a> to <a href="https://swagger.io/docs/specification/serialization/" target="_blank" rel="noopener noreferrer">serialize query parameters</a>. 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:
4144

4245
```ts
43-
import createClient from "openapi-fetch";
44-
import { paths } from "./v1"; // generated from openapi-typescript
45-
46-
const { get, post } = createClient<paths>({ baseUrl: "https://myapi.dev/v1/" });
47-
48-
const { data, error } = await get("/blogposts/{post_id}", {
46+
const { data, error } = await get("/search", {
4947
params: {
50-
path: { post_id: "my-post" },
51-
query: { version: 2 },
48+
query: { tags: ["food", "california", "healthy"] },
49+
},
50+
querySerializer(q) {
51+
let s = "";
52+
for (const [k, v] of Object.entries(q)) {
53+
if (Array.isArray(v)) {
54+
s += `${k}[]=${v.join(",")}`;
55+
} else {
56+
s += `${k}=${v}`;
57+
}
58+
}
59+
return s; // ?tags[]=food&tags[]=california&tags[]=healthy
5260
},
53-
querySerializer: (q) => `v=${q.version}`, // ✅ Still typechecked based on the URL!
5461
});
5562
```
5663

57-
Note that this happens **at the request level** so that you still get correct type inference for that URL’s specific query params.
64+
### bodySerializer
65+
66+
Similar to [querySerializer](#querySerializer), bodySerializer works for requestBody. You probably only need this when using `multipart/form-data`:
5867

59-
_Thanks, [@ezpuzz](https://github.com/ezpuzz)!_
68+
```ts
69+
const { data, error } = await put("/submit", {
70+
body: {
71+
name: "",
72+
query: { version: 2 },
73+
},
74+
bodySerializer(body) {
75+
const fd = new FormData();
76+
for (const [k, v] of Object.entries(body)) {
77+
fd.append(k, v);
78+
}
79+
return fd;
80+
},
81+
});
82+
```

docs/src/content/docs/openapi-fetch/index.md

+15-8
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description: Get Started with openapi-fetch
55

66
<img src="/assets/openapi-fetch.svg" alt="openapi-fetch" width="216" height="40" />
77

8-
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.
8+
openapi-fetch applies your OpenAPI types to the native fetch API via TypeScript. Weighs in at **1 kb** and has virtually zero runtime. Works with React, Vue, Svelte, or vanilla JS.
99

1010
| Library | Size (min) |
1111
| :----------------------------- | ---------: |
@@ -19,7 +19,7 @@ The syntax is inspired by popular libraries like react-query or Apollo client, b
1919
import createClient from "openapi-fetch";
2020
import { paths } from "./v1"; // generated from openapi-typescript
2121

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

2424
// Type-checked request
2525
await put("/blogposts", {
@@ -30,8 +30,7 @@ await put("/blogposts", {
3030
});
3131

3232
// Type-checked response
33-
const { data, error } = await get("/blogposts/my-blog-post");
34-
33+
const { data, error } = await get("/blogposts/{post_id}", { params: { path: { post_id: "123" } } });
3534
console.log(data.title); // ❌ 'data' is possibly 'undefined'
3635
console.log(error.message); // ❌ 'error' is possibly 'undefined'
3736
console.log(data?.foo); // ❌ Property 'foo' does not exist on type …
@@ -81,17 +80,17 @@ And run `npm run test:ts` in your CI to catch type errors.
8180
8281
## Usage
8382

84-
Using **openapi-fetch** is as easy as reading your schema! For example, given the following schema:
83+
Using **openapi-fetch** is as easy as reading your schema:
8584

8685
![OpenAPI schema example](/assets/openapi-schema.png)
8786

88-
Here’s how you’d fetch GET `/blogposts/{post_id}` and POST `/blogposts`:
87+
Here’s how you’d fetch GET `/blogposts/{post_id}` and PUT `/blogposts`:
8988

9089
```ts
9190
import createClient from "openapi-fetch";
9291
import { paths } from "./v1";
9392

94-
const { get, post } = createClient<paths>({ baseUrl: "https://myapi.dev/v1/" });
93+
const { get, put } = createClient<paths>({ baseUrl: "https://myapi.dev/v1/" });
9594

9695
const { data, error } = await get("/blogposts/{post_id}", {
9796
params: {
@@ -100,7 +99,7 @@ const { data, error } = await get("/blogposts/{post_id}", {
10099
},
101100
});
102101

103-
const { data, error } = await post("/blogposts", {
102+
const { data, error } = await put("/blogposts", {
104103
body: {
105104
title: "New Post",
106105
body: "<p>New post body</p>",
@@ -134,3 +133,11 @@ All methods return an object with **data**, **error**, and **response**.
134133
- **error** likewise contains that endpoint’s `4xx`/`5xx` response if the server returned either; otherwise it will be `undefined`
135134
- _Note: `default` will also be interpreted as `error`, since its intent is handling unexpected HTTP codes_
136135
- **response** has response info like `status`, `headers`, etc. It is not typechecked.
136+
137+
## Version Support
138+
139+
openapi-fetch implements the [native fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) which is available in all major browsers.
140+
141+
If using in a Node.js environment, version 18 or greater is recommended (newer is better).
142+
143+
TypeScript support is pretty far-reaching as this library doesn’t use any cutting-edge features, but using the latest version of TypeScript is always recommended for accuracy.

0 commit comments

Comments
 (0)