Skip to content

Commit fd44bd2

Browse files
committed
Middleware 2nd pass
1 parent b174dd6 commit fd44bd2

File tree

14 files changed

+596
-354
lines changed

14 files changed

+596
-354
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

+8-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
/**
77
* Fonts
88
*/
9-
109
@font-face {
1110
font-family: "Inter";
1211
font-style: normal;
@@ -114,6 +113,14 @@ code {
114113
font-variant-ligatures: none;
115114
}
116115

116+
/**
117+
* Base styles
118+
* -------------------------------------------------------------------------- */
119+
pre,
120+
code {
121+
font-variant-ligatures: none;
122+
}
123+
117124
/**
118125
* Component: Button
119126
* -------------------------------------------------------------------------- */

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
@@ -154,97 +154,70 @@ openapi-fetch supports path serialization as [outlined in the 3.1 spec](https://
154154

155155
## Middleware
156156

157-
As of `0.9.0` this library supports lightweight middleware. Middleware allows you to modify either the request, response, or both for all fetches.
158-
159-
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.
160-
161-
Within your middleware function, you’ll either need to check for `req` (request) or `res` (response) to handle each pass appropriately:
157+
Middleware is an object with `onRequest()` and `onResponse()` callbacks that can observe and modify requests and responses.
162158

163159
```ts
164-
createClient({
165-
middleware: [
166-
async function myMiddleware({
167-
req, // request (undefined for responses)
168-
res, // response (undefined for requests)
169-
options, // all options passed to openapi-fetch
170-
}) {
171-
if (req) {
172-
return new Request(req.url, {
173-
...req,
174-
headers: { ...req.headers, foo: "bar" },
175-
});
176-
} else if (res) {
177-
return new Response({
178-
...res,
179-
status: 200,
180-
});
181-
}
160+
function myMiddleware(): Middleware {
161+
return {
162+
async onRequest(req, options) {
163+
// set "foo" header
164+
req.headers.set("foo", "bar");
165+
return req;
182166
},
183-
],
167+
async onResponse(res, options) {
168+
const { body, ...resOptions } = res;
169+
// change status of response
170+
return new Response(body, { ...resOptions, status: 200 });
171+
},
172+
};
173+
}
174+
175+
createClient({
176+
middleware: [myMiddleware()],
184177
});
185178
```
186179

187-
### Request pass
188-
189-
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:
190-
191-
| Name | Type | Description |
192-
| :----------- | :------: | :--------------------------------------------------------------- |
193-
| `schemaPath` | `string` | The OpenAPI pathname called (e.g. `/projects/{project_id}`) |
194-
| `params` | `Object` | The [params](#fetch-options) fetch option provided by the client |
195-
196-
### Response pass
180+
::: tip
197181

198-
The response pass returns a standard [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) instance with no modifications.
182+
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.
199183

200-
### Skipping middleware
184+
:::
201185

202-
If you want to skip the middleware under certain conditions, just `return` as early as possible:
186+
### onRequest
203187

204188
```ts
205-
async function myMiddleware({ req }) {
206-
if (req.schemaPath !== "/projects/{project_id}") {
207-
return;
208-
}
209-
189+
onRequest(req, options) {
210190
//
211191
}
212192
```
213193

214-
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.
194+
`onRequest()` takes 2 params:
215195

216-
### Handling statefulness
196+
| Name | Type | Description |
197+
| :-------- | :-----------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
198+
| `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) |
199+
| `options` | `MergedOptiosn` | Combination of [createClient](/openapi-fetch/api#create-client) options + [fetch overrides](/openapi-fetch/api#fetch-options) |
217200

218-
When using middleware, it’s important to remember 2 things:
201+
And it expects either:
219202

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

223-
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).
206+
### onResponse
224207

225-
<!-- prettier-ignore -->
226208
```ts
227-
async function myMiddleware({ req, res }) {
228-
// Example 1: modifying request
229-
if (req) {
230-
res.headers.foo = "bar"; // [!code --]
231-
return new Request(req.url, { // [!code ++]
232-
...req, // [!code ++]
233-
headers: { ...req.headers, foo: "bar" }, // [!code ++]
234-
}); // [!code ++]
235-
}
236-
237-
// Example 2: accessing response
238-
if (res) {
239-
const data = await res.json(); // [!code --]
240-
const data = await res.clone().json(); // [!code ++]
241-
}
209+
onResponse(res, options) {
210+
//
242211
}
243212
```
244213

245-
### Other notes
214+
`onResponse()` also takes 2 params:
215+
| Name | Type | Description |
216+
| :-------- | :-----------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
217+
| `req` | `MiddlewareRequest` | A standard [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response). |
218+
| `options` | `MergedOptiosn` | Combination of [createClient](/openapi-fetch/api#create-client) options + [fetch overrides](/openapi-fetch/api#fetch-options) |
219+
220+
And it expects either:
246221

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

docs/openapi-fetch/examples.md

+5-107
Original file line numberDiff line numberDiff line change
@@ -4,131 +4,29 @@ 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 type { paths } from "./api/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 type { paths } from "./api/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 type { paths } from "./api/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
There isn’t an example app in Vue yet. Are you using it in Vue? Please [open a PR to add it!](https://github.com/drwpow/openapi-typescript/pulls)
13432

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)