Skip to content

Commit 1590c88

Browse files
committed
Middleware 2nd pass
1 parent 8eee83d commit 1590c88

File tree

16 files changed

+610
-349
lines changed

16 files changed

+610
-349
lines changed

.changeset/moody-bottles-tie.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"openapi-fetch": patch
3+
---
4+
5+
Support arrays in headers

docs/.vitepress/config.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,12 @@ export default defineConfig({
4444
{
4545
text: "openapi-fetch",
4646
items: [
47-
{ text: "Introduction", link: "/openapi-fetch/" },
47+
{ text: "Getting Started", link: "/openapi-fetch/" },
48+
{
49+
text: "Middleware & Auth",
50+
link: "/openapi-fetch/middleware-auth",
51+
},
52+
{ text: "Testing", link: "/openapi-fetch/testing" },
4853
{ text: "Examples", link: "/openapi-fetch/examples" },
4954
{ text: "API", link: "/openapi-fetch/api" },
5055
{ text: "About", link: "/openapi-fetch/about" },
@@ -68,7 +73,12 @@ export default defineConfig({
6873
{
6974
text: "openapi-fetch",
7075
items: [
71-
{ text: "Introduction", link: "/openapi-fetch/" },
76+
{ text: "Getting Started", link: "/openapi-fetch/" },
77+
{
78+
text: "Middleware & Auth",
79+
link: "/openapi-fetch/middleware-auth",
80+
},
81+
{ text: "Testing", link: "/openapi-fetch/testing" },
7282
{ text: "Examples", link: "/openapi-fetch/examples" },
7383
{ text: "API", link: "/openapi-fetch/api" },
7484
{ text: "About", link: "/openapi-fetch/about" },

docs/.vitepress/theme/style.css

+22-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,18 @@
66
/**
77
* Fonts
88
*/
9+
@font-face {
10+
font-family: "Inter";
11+
font-style: normal;
12+
font-display: swap;
13+
src: url("/assets/InterVariable.woff2") format("woff2");
14+
}
915

1016
@font-face {
11-
font-family: "Geist";
12-
src: url("/assets/GeistVariableVF.woff2") format("woff2");
17+
font-family: "Inter";
18+
font-style: italic;
19+
font-display: swap;
20+
src: url("/assets/InterVariable-Italic.woff2") format("woff2");
1321
}
1422

1523
@font-face {
@@ -18,6 +26,10 @@
1826
}
1927

2028
:root {
29+
--vp-font-family-base: "Chinese Quotes", "Inter", ui-sans-serif, system-ui,
30+
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
31+
Helvetica, Arial, "Noto Sans", sans-serif, "Apple Color Emoji",
32+
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
2133
--vp-font-family-mono: ui-monospace, "Geist Mono", SFMono-Regular, "SF Mono",
2234
Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
2335
}
@@ -89,6 +101,14 @@
89101
--vp-c-danger-soft: var(--vp-c-red-soft);
90102
}
91103

104+
/**
105+
* Base styles
106+
* -------------------------------------------------------------------------- */
107+
pre,
108+
code {
109+
font-variant-ligatures: none;
110+
}
111+
92112
/**
93113
* Component: Button
94114
* -------------------------------------------------------------------------- */

docs/openapi-fetch/api.md

+43-70
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ client.get("/my-url", options);
3737
| `bodySerializer` | BodySerializer | (optional) Provide a [bodySerializer](#bodyserializer) |
3838
| `parseAs` | `"json"` \| `"text"` \| `"arrayBuffer"` \| `"blob"` \| `"stream"` | (optional) Parse the response using [a built-in instance method](https://developer.mozilla.org/en-US/docs/Web/API/Response#instance_methods) (default: `"json"`). `"stream"` skips parsing altogether and returns the raw stream. |
3939
| `fetch` | `fetch` | Fetch instance used for requests (default: fetch from `createClient`) |
40-
| `middleware` | `Middleware[]` | [See docs](#middleware) |
40+
| `middleware` | `Middleware[]` | [See docs](/openapi-fetch/middleware-auth) |
4141
| (Fetch options) | | Any valid fetch option (`headers`, `mode`, `cache`, `signal`, …) ([docs](https://developer.mozilla.org/en-US/docs/Web/API/fetch#options)) |
4242

4343
### querySerializer
@@ -87,97 +87,70 @@ const { data, error } = await PUT("/submit", {
8787

8888
## Middleware
8989

90-
As of `0.9.0` this library supports lightweight middleware. Middleware allows you to modify either the request, response, or both for all fetches.
91-
92-
You can declare middleware as an array of functions on [createClient](#create-client). Each middleware function will be **called twice**—once for the request, then again for the response. On request, they’ll be called in array order. On response, they’ll be called in reverse-array order. That way the first middleware gets the first “dibs” on request, and the final control over responses.
93-
94-
Within your middleware function, you’ll either need to check for `req` (request) or `res` (response) to handle each pass appropriately:
90+
Middleware is an object with `onRequest()` and `onResponse()` callbacks that can observe and modify requests and responses.
9591

9692
```ts
97-
createClient({
98-
middleware: [
99-
async function myMiddleware({
100-
req, // request (undefined for responses)
101-
res, // response (undefined for requests)
102-
options, // all options passed to openapi-fetch
103-
}) {
104-
if (req) {
105-
return new Request(req.url, {
106-
...req,
107-
headers: { ...req.headers, foo: "bar" },
108-
});
109-
} else if (res) {
110-
return new Response({
111-
...res,
112-
status: 200,
113-
});
114-
}
93+
function myMiddleware(): Middleware {
94+
return {
95+
async onRequest(req, options) {
96+
// set "foo" header
97+
req.headers.set("foo", "bar");
98+
return req;
11599
},
116-
],
100+
async onResponse(res, options) {
101+
const { body, ...resOptions } = res;
102+
// change status of response
103+
return new Response(body, { ...resOptions, status: 200 });
104+
},
105+
};
106+
}
107+
108+
createClient({
109+
middleware: [myMiddleware()],
117110
});
118111
```
119112

120-
### Request pass
121-
122-
The request pass of each middleware provides `req` that’s a standard [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) instance, but has 2 additional properties:
123-
124-
| Name | Type | Description |
125-
| :----------- | :------: | :--------------------------------------------------------------- |
126-
| `schemaPath` | `string` | The OpenAPI pathname called (e.g. `/projects/{project_id}`) |
127-
| `params` | `Object` | The [params](#fetch-options) fetch option provided by the client |
128-
129-
### Response pass
113+
::: tip
130114

131-
The response pass returns a standard [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) instance with no modifications.
115+
Middleware can be a simple object. But wrapping in a function like the examples show lets you optionally create multiple instances of the same logic to handle different scenarios if needed.
132116

133-
### Skipping middleware
117+
:::
134118

135-
If you want to skip the middleware under certain conditions, just `return` as early as possible:
119+
### onRequest
136120

137121
```ts
138-
async function myMiddleware({ req }) {
139-
if (req.schemaPath !== "/projects/{project_id}") {
140-
return;
141-
}
142-
122+
onRequest(req, options) {
143123
//
144124
}
145125
```
146126

147-
This will leave the request/response unmodified, and pass things off to the next middleware handler (if any). There’s no internal callback or observer library needed.
127+
`onRequest()` takes 2 params:
148128

149-
### Handling statefulness
129+
| Name | Type | Description |
130+
| :-------- | :-----------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
131+
| `req` | `MiddlewareRequest` | A standard [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) with `schemaPath` (OpenAPI pathname) and `params` ([params](/openapi-fetch/api#fetch-options) object) |
132+
| `options` | `MergedOptiosn` | Combination of [createClient](/openapi-fetch/api#create-client) options + [fetch overrides](/openapi-fetch/api#fetch-options) |
150133

151-
When using middleware, it’s important to remember 2 things:
134+
And it expects either:
152135

153-
- **Create new instances** when modifying (e.g. `new Response()`)
154-
- **Clone bodies** before accessing (e.g. `res.clone().json()`)
136+
- **If modifying the request:** A [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request)
137+
- **If not modifying:** `undefined` (void)
155138

156-
This is to account for the fact responses are [stateful](https://developer.mozilla.org/en-US/docs/Web/API/Response/bodyUsed), and if the stream is consumed in middleware [the client will throw an error](https://developer.mozilla.org/en-US/docs/Web/API/Response/clone).
139+
### onResponse
157140

158-
<!-- prettier-ignore -->
159141
```ts
160-
async function myMiddleware({ req, res }) {
161-
// Example 1: modifying request
162-
if (req) {
163-
res.headers.foo = "bar"; // [!code --]
164-
return new Request(req.url, { // [!code ++]
165-
...req, // [!code ++]
166-
headers: { ...req.headers, foo: "bar" }, // [!code ++]
167-
}); // [!code ++]
168-
}
169-
170-
// Example 2: accessing response
171-
if (res) {
172-
const data = await res.json(); // [!code --]
173-
const data = await res.clone().json(); // [!code ++]
174-
}
142+
onResponse(res, options) {
143+
//
175144
}
176145
```
177146

178-
### Other notes
147+
`onResponse()` also takes 2 params:
148+
| Name | Type | Description |
149+
| :-------- | :-----------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
150+
| `req` | `MiddlewareRequest` | A standard [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response). |
151+
| `options` | `MergedOptiosn` | Combination of [createClient](/openapi-fetch/api#create-client) options + [fetch overrides](/openapi-fetch/api#fetch-options) |
152+
153+
And it expects either:
179154

180-
- `querySerializer()` runs _before_ middleware
181-
- This is to save middleware from having to do annoying URL formatting. But remember middleware can access `req.params`
182-
- `bodySerializer()` runs _after_ middleware
183-
- There is some overlap with `bodySerializer()` and middleware. Probably best to use one or the other; not both together.
155+
- **If modifying the response:** A [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response)
156+
- **If not modifying:** `undefined` (void)

docs/openapi-fetch/examples.md

+5-107
Original file line numberDiff line numberDiff line change
@@ -4,130 +4,28 @@ title: openapi-fetch Examples
44

55
# Examples
66

7-
## Authentication
7+
Example code of using openapi-fetch with other frameworks and libraries.
88

9-
Authentication often requires some reactivity dependent on a token. Since this library is so low-level, there are myriad ways to handle it:
10-
11-
### Nano Stores
12-
13-
Here’s how it can be handled using [Nano Stores](https://github.com/nanostores/nanostores), a tiny (334 b), universal signals store:
14-
15-
```ts
16-
// src/lib/api/index.ts
17-
import { atom, computed } from "nanostores";
18-
import createClient from "openapi-fetch";
19-
import { paths } from "./v1";
20-
21-
export const authToken = atom<string | undefined>();
22-
someAuthMethod().then((newToken) => authToken.set(newToken));
23-
24-
export const client = computed(authToken, (currentToken) =>
25-
createClient<paths>({
26-
headers: currentToken ? { Authorization: `Bearer ${currentToken}` } : {},
27-
baseUrl: "https://myapi.dev/v1/",
28-
}),
29-
);
30-
```
31-
32-
```ts
33-
// src/some-other-file.ts
34-
import { client } from "./lib/api";
35-
36-
const { GET, POST } = client.get();
37-
38-
GET("/some-authenticated-url", {
39-
/**/
40-
});
41-
```
42-
43-
### Vanilla JS Proxies
44-
45-
You can also use [proxies](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) which are now supported in all modern browsers:
46-
47-
```ts
48-
// src/lib/api/index.ts
49-
import createClient from "openapi-fetch";
50-
import { paths } from "./v1";
51-
52-
let authToken: string | undefined = undefined;
53-
someAuthMethod().then((newToken) => (authToken = newToken));
54-
55-
const baseClient = createClient<paths>({ baseUrl: "https://myapi.dev/v1/" });
56-
export default new Proxy(baseClient, {
57-
get(_, key: keyof typeof baseClient) {
58-
const newClient = createClient<paths>({
59-
headers: authToken ? { Authorization: `Bearer ${authToken}` } : {},
60-
baseUrl: "https://myapi.dev/v1/",
61-
});
62-
return newClient[key];
63-
},
64-
});
65-
```
66-
67-
```ts
68-
// src/some-other-file.ts
69-
import client from "./lib/api";
70-
71-
client.GET("/some-authenticated-url", {
72-
/**/
73-
});
74-
```
75-
76-
### Vanilla JS getter
77-
78-
You can also use a [getter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get):
79-
80-
```ts
81-
// src/lib/api/index.ts
82-
import createClient from "openapi-fetch";
83-
import { paths } from "./v1";
84-
85-
let authToken: string | undefined = undefined;
86-
someAuthMethod().then((newToken) => (authToken = newToken));
87-
88-
export default createClient<paths>({
89-
baseUrl: "https://myapi.dev/v1/",
90-
headers: {
91-
get Authorization() {
92-
return authToken ? `Bearer ${authToken}` : undefined;
93-
},
94-
},
95-
});
96-
```
97-
98-
```ts
99-
// src/some-other-file.ts
100-
import client from "./lib/api";
101-
102-
client.GET("/some-authenticated-url", {
103-
/**/
104-
});
105-
```
106-
107-
## Frameworks
108-
109-
openapi-fetch is simple vanilla JS that can be used in any project. But sometimes the implementation in a framework may come with some prior art that helps you get the most out of your usage.
110-
111-
### React + React Query
9+
## React + React Query
11210

11311
[React Query](https://tanstack.com/query/latest) is a perfect wrapper for openapi-fetch in React. At only 13 kB, it provides clientside caching and request deduping across async React components without too much client weight in return. And its type inference preserves openapi-fetch types perfectly with minimal setup.
11412

11513
[View a code example in GitHub](https://github.com/drwpow/openapi-typescript/tree/main/packages/openapi-fetch/examples/react-query)
11614

117-
### Next.js
15+
## Next.js
11816

11917
[Next.js](https://nextjs.org/) is the most popular SSR framework for React. While [React Query](#react--react-query) is recommended for all clientside fetching with openapi-fetch (not SWR), this example shows how to take advantage of Next.js’s [server-side fetching](https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#fetching-data-on-the-server-with-fetch) with built-in caching.
12018

12119
[View a code example in GitHub](https://github.com/drwpow/openapi-typescript/tree/main/packages/openapi-fetch/examples/nextjs)
12220

123-
### Svelte / SvelteKit
21+
## Svelte / SvelteKit
12422

12523
[SvelteKit](https://kit.svelte.dev)’s automatic type inference can easily pick up openapi-fetch’s types in both clientside fetching and [Page Data](https://kit.svelte.dev/docs/load#page-data) fetching. And it doesn’t need any additional libraries to work. SvelteKit also advises to use their [custom fetch](https://kit.svelte.dev/docs/load#making-fetch-requests) in load functions. This can be achieved with [fetch options](/openapi-fetch/api#fetch-options).
12624

12725
_Note: if you’re using Svelte without SvelteKit, the root example in `src/routes/+page.svelte` doesn’t use any SvelteKit features and is generally-applicable to any setup._
12826

12927
[View a code example in GitHub](https://github.com/drwpow/openapi-typescript/tree/main/packages/openapi-fetch/examples/sveltekit)
13028

131-
### Vue
29+
## Vue
13230

13331
TODO

docs/openapi-fetch/index.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ And run `npm run test:ts` in your CI to catch type errors.
9494
Use `tsc --noEmit` to check for type errors rather than relying on your linter or your build command. Nothing will typecheck as accurately as the TypeScript compiler itself.
9595
:::
9696

97-
## Usage
97+
## Basic Usage
9898

9999
The best part about using openapi-fetch over oldschool codegen is no documentation needed. openapi-fetch encourages using your existing OpenAPI documentation rather than trying to find what function to import, or what parameters that function wants:
100100

@@ -128,7 +128,7 @@ const { data, error } = await PUT("/blogposts", {
128128

129129
### Pathname
130130

131-
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)
131+
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 quickly replace all `path` params for you (so they can be typechecked).
132132

133133
::: tip
134134

0 commit comments

Comments
 (0)