Skip to content

Commit 168e424

Browse files
brolnickijmrlubos
authored andcommitted
docs(ofetch): init
1 parent a1807fe commit 168e424

File tree

3 files changed

+346
-0
lines changed

3 files changed

+346
-0
lines changed

docs/.vitepress/config/en.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ export default defineConfig({
103103
link: '/openapi-ts/clients/nuxt',
104104
text: 'Nuxt',
105105
},
106+
{
107+
link: '/openapi-ts/clients/ofetch',
108+
text: 'ofetch',
109+
},
106110
{
107111
link: '/openapi-ts/clients/effect',
108112
text: 'Effect <span data-soon>soon</span>',

docs/openapi-ts/clients.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Hey API natively supports the following clients.
3030
- [Axios](/openapi-ts/clients/axios)
3131
- [Next.js](/openapi-ts/clients/next-js)
3232
- [Nuxt](/openapi-ts/clients/nuxt)
33+
- [ofetch](/openapi-ts/clients/ofetch)
3334
- [Effect](/openapi-ts/clients/effect) <span data-soon>Soon</span>
3435
- [Legacy](/openapi-ts/clients/legacy)
3536

docs/openapi-ts/clients/ofetch.md

Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
---
2+
title: ofetch Client
3+
description: Generate a type-safe ofetch client from OpenAPI with the ofetch client for openapi-ts. Fully compatible with validators, transformers, and all core features.
4+
---
5+
6+
<script setup lang="ts">
7+
import { embedProject } from '../../embed'
8+
</script>
9+
10+
# ofetch
11+
12+
### About
13+
14+
[ofetch](https://github.com/unjs/ofetch) is a lightweight wrapper around the Fetch API that adds useful defaults and features such as automatic response parsing, request/response hooks, and Node.js support.
15+
16+
The ofetch client for Hey API generates a type-safe client from your OpenAPI spec, fully compatible with validators, transformers, and all core features.
17+
18+
## Features
19+
20+
- seamless integration with `@hey-api/openapi-ts` ecosystem
21+
- type-safe response data and errors
22+
- response data validation and transformation
23+
- access to the original request and response
24+
- granular request and response customization options
25+
- minimal learning curve thanks to extending the underlying technology
26+
- support bundling inside the generated output
27+
28+
## Installation
29+
30+
In your [configuration](/openapi-ts/get-started), add `@hey-api/client-ofetch` to your plugins and you'll be ready to generate client artifacts. :tada:
31+
32+
::: code-group
33+
34+
```js [config]
35+
export default {
36+
input: 'hey-api/backend', // sign up at app.heyapi.dev
37+
output: 'src/client',
38+
plugins: ['@hey-api/client-ofetch'], // [!code ++]
39+
};
40+
```
41+
42+
```sh [cli]
43+
npx @hey-api/openapi-ts \
44+
-i hey-api/backend \
45+
-o src/client \
46+
-c @hey-api/client-ofetch # [!code ++]
47+
```
48+
49+
:::
50+
51+
## Configuration
52+
53+
The ofetch client is built as a thin wrapper on top of ofetch, extending its functionality to work with Hey API. If you're already familiar with ofetch, configuring your client will feel like working directly with ofetch.
54+
55+
When we installed the client above, it created a [`client.gen.ts`](/openapi-ts/output#client) file. You will most likely want to configure the exported `client` instance. There are two ways to do that.
56+
57+
### `setConfig()`
58+
59+
This is the simpler approach. You can call the `setConfig()` method at the beginning of your application or anytime you need to update the client configuration. You can pass any ofetch configuration option to `setConfig()` (including ofetch hooks), and even your own [ofetch](#custom-ofetch) instance.
60+
61+
```js
62+
import { client } from 'client/client.gen';
63+
64+
client.setConfig({
65+
baseUrl: 'https://example.com',
66+
});
67+
```
68+
69+
The disadvantage of this approach is that your code may call the `client` instance before it's configured for the first time. Depending on your use case, you might need to use the second approach.
70+
71+
### Runtime API
72+
73+
Since `client.gen.ts` is a generated file, we can't directly modify it. Instead, we can tell our configuration to use a custom file implementing the Runtime API. We do that by specifying the `runtimeConfigPath` option.
74+
75+
```js
76+
export default {
77+
input: 'hey-api/backend', // sign up at app.heyapi.dev
78+
output: 'src/client',
79+
plugins: [
80+
{
81+
name: '@hey-api/client-ofetch',
82+
runtimeConfigPath: './src/hey-api.ts', // [!code ++]
83+
},
84+
],
85+
};
86+
```
87+
88+
In our custom file, we need to export a `createClientConfig()` method. This function is a simple wrapper allowing us to override configuration values.
89+
90+
::: code-group
91+
92+
```ts [hey-api.ts]
93+
import type { CreateClientConfig } from './client/client.gen';
94+
95+
export const createClientConfig: CreateClientConfig = (config) => ({
96+
...config,
97+
baseUrl: 'https://example.com',
98+
});
99+
```
100+
101+
:::
102+
103+
With this approach, `client.gen.ts` will call `createClientConfig()` before initializing the `client` instance. If needed, you can still use `setConfig()` to update the client configuration later.
104+
105+
### `createClient()`
106+
107+
You can also create your own client instance. You can use it to manually send requests or point it to a different domain.
108+
109+
```js
110+
import { createClient } from './client/client';
111+
112+
const myClient = createClient({
113+
baseUrl: 'https://example.com',
114+
});
115+
```
116+
117+
You can also pass this instance to any SDK function through the `client` option. This will override the default instance from `client.gen.ts`.
118+
119+
```js
120+
const response = await getFoo({
121+
client: myClient,
122+
});
123+
```
124+
125+
### SDKs
126+
127+
Alternatively, you can pass the client configuration options to each SDK function. This is useful if you don't want to create a client instance for one-off use cases.
128+
129+
```js
130+
const response = await getFoo({
131+
baseUrl: 'https://example.com', // <-- override default configuration
132+
});
133+
```
134+
135+
## Interceptors
136+
137+
Interceptors (middleware) can be used to modify requests before they're sent or responses before they're returned to your application.
138+
139+
The ofetch client supports two complementary options:
140+
141+
- Built-in interceptors exposed via `client.interceptors` (request/response/error) — same API across Hey API clients.
142+
- Native ofetch hooks passed through config: `onRequest`, `onRequestError`, `onResponse`, `onResponseError`.
143+
144+
Below is an example request interceptor using the built-in API.
145+
146+
::: code-group
147+
148+
```js [use]
149+
import { client } from 'client/client.gen';
150+
// Supports async functions
151+
async function myInterceptor(request) {
152+
// do something
153+
return request;
154+
}
155+
interceptorId = client.interceptors.request.use(myInterceptor);
156+
```
157+
158+
```js [eject]
159+
import { client } from 'client/client.gen';
160+
161+
// eject interceptor by interceptor id
162+
client.interceptors.request.eject(interceptorId);
163+
164+
// eject interceptor by reference to interceptor function
165+
client.interceptors.request.eject(myInterceptor);
166+
```
167+
168+
```js [update]
169+
import { client } from 'client/client.gen';
170+
171+
async function myNewInterceptor(request) {
172+
// do something
173+
return request;
174+
}
175+
// update interceptor by interceptor id
176+
client.interceptors.request.update(interceptorId, myNewInterceptor);
177+
178+
// update interceptor by reference to interceptor function
179+
client.interceptors.request.update(myInterceptor, myNewInterceptor);
180+
```
181+
182+
:::
183+
184+
and an example response interceptor
185+
186+
::: code-group
187+
188+
```js [use]
189+
import { client } from 'client/client.gen';
190+
async function myInterceptor(response) {
191+
// do something
192+
return response;
193+
}
194+
// Supports async functions
195+
interceptorId = client.interceptors.response.use(myInterceptor);
196+
```
197+
198+
```js [eject]
199+
import { client } from 'client/client.gen';
200+
201+
// eject interceptor by interceptor id
202+
client.interceptors.response.eject(interceptorId);
203+
204+
// eject interceptor by reference to interceptor function
205+
client.interceptors.response.eject(myInterceptor);
206+
```
207+
208+
```js [update]
209+
import { client } from 'client/client.gen';
210+
211+
async function myNewInterceptor(response) {
212+
// do something
213+
return response;
214+
}
215+
// update interceptor by interceptor id
216+
client.interceptors.response.update(interceptorId, myNewInterceptor);
217+
218+
// update interceptor by reference to interceptor function
219+
client.interceptors.response.update(myInterceptor, myNewInterceptor);
220+
```
221+
222+
:::
223+
224+
You can also use native ofetch hooks by passing them in config:
225+
226+
```js
227+
import { client } from 'client/client.gen';
228+
229+
client.setConfig({
230+
onRequest: ({ options }) => {
231+
// mutate ofetch options (headers, query, etc.)
232+
},
233+
onResponse: ({ response }) => {
234+
// inspect/transform the raw Response
235+
},
236+
onRequestError: (ctx) => {
237+
// handle request errors
238+
},
239+
onResponseError: (ctx) => {
240+
// handle response errors
241+
},
242+
});
243+
```
244+
245+
::: tip
246+
To eject, you must provide the id or reference of the interceptor passed to `use()`, the id is the value returned by `use()` and `update()`.
247+
:::
248+
249+
## Auth
250+
251+
The SDKs include auth mechanisms for every endpoint. You will want to configure the `auth` field to pass the right token for each request. The `auth` field can be a string or a function returning a string representing the token. The returned value will be attached only to requests that require auth.
252+
253+
```js
254+
import { client } from 'client/client.gen';
255+
256+
client.setConfig({
257+
auth: () => '<my_token>', // [!code ++]
258+
baseUrl: 'https://example.com',
259+
});
260+
```
261+
262+
If you're not using SDKs or generating auth, using interceptors is a common approach to configuring auth for each request.
263+
264+
```js
265+
import { client } from 'client/client.gen';
266+
267+
client.interceptors.request.use((request, options) => {
268+
request.headers.set('Authorization', 'Bearer <my_token>'); // [!code ++]
269+
return request;
270+
});
271+
```
272+
273+
or with ofetch hooks:
274+
275+
```js
276+
import { client } from 'client/client.gen';
277+
278+
client.setConfig({
279+
onRequest: ({ options }) => {
280+
options.headers.set('Authorization', 'Bearer <my_token>'); // [!code ++]
281+
},
282+
});
283+
```
284+
285+
## Build URL
286+
287+
If you need to access the compiled URL, you can use the `buildUrl()` method. It's loosely typed by default to accept almost any value; in practice, you will want to pass a type hint.
288+
289+
```ts
290+
type FooData = {
291+
path: {
292+
fooId: number;
293+
};
294+
query?: {
295+
bar?: string;
296+
};
297+
url: '/foo/{fooId}';
298+
};
299+
300+
const url = client.buildUrl<FooData>({
301+
path: {
302+
fooId: 1,
303+
},
304+
query: {
305+
bar: 'baz',
306+
},
307+
url: '/foo/{fooId}',
308+
});
309+
console.log(url); // prints '/foo/1?bar=baz'
310+
```
311+
312+
## Custom `ofetch`
313+
314+
You can provide a custom ofetch instance. This is useful if you need to extend ofetch with extra functionality (hooks, retry behavior, etc.) or replace it altogether.
315+
316+
```js
317+
import { ofetch } from 'ofetch';
318+
import { client } from 'client/client.gen';
319+
320+
const $ofetch = ofetch.create({
321+
onRequest: ({ options }) => {
322+
// customize request
323+
},
324+
onResponse: ({ response }) => {
325+
// customize response
326+
},
327+
});
328+
329+
client.setConfig({
330+
ofetch: $ofetch,
331+
});
332+
```
333+
334+
You can use any of the approaches mentioned in [Configuration](#configuration), depending on how granular you want your custom instance to be.
335+
336+
## API
337+
338+
You can view the complete list of options in the [UserConfig](https://github.com/hey-api/openapi-ts/blob/main/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/types.d.ts) interface.
339+
340+
<!--@include: ../../partials/examples.md-->
341+
<!--@include: ../../partials/sponsors.md-->

0 commit comments

Comments
 (0)