Skip to content

Commit 2dc380e

Browse files
committed
feat: add Next.js client
1 parent c928e80 commit 2dc380e

File tree

60 files changed

+2175
-471
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+2175
-471
lines changed

.changeset/dry-eggs-begin.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@hey-api/client-axios': patch
3+
'@hey-api/client-fetch': patch
4+
'@hey-api/client-next': patch
5+
'@hey-api/client-nuxt': patch
6+
'@hey-api/openapi-ts': patch
7+
---
8+
9+
fix: update keywords in package.json

.changeset/light-jokes-grin.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@hey-api/client-next': minor
3+
---
4+
5+
feat: initial release

.changeset/spotty-suns-build.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@hey-api/openapi-ts': patch
3+
---
4+
5+
fix: add Next.js client

docs/.vitepress/config/en.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export default defineConfig({
7373
},
7474
{
7575
link: '/openapi-ts/clients/next-js',
76-
text: 'Next.js <span data-soon>soon</span>',
76+
text: 'Next.js',
7777
},
7878
{
7979
link: '/openapi-ts/clients/nuxt',

docs/openapi-ts/clients.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Hey API natively supports the following clients.
2727

2828
- [Fetch API](/openapi-ts/clients/fetch)
2929
- [Axios](/openapi-ts/clients/axios)
30-
- [Next.js](/openapi-ts/clients/next-js) <span data-soon>Soon</span>
30+
- [Next.js](/openapi-ts/clients/next-js)
3131
- [Nuxt](/openapi-ts/clients/nuxt)
3232
- [Legacy](/openapi-ts/clients/legacy)
3333

docs/openapi-ts/clients/next-js.md

Lines changed: 256 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,266 @@ title: Next.js client
33
description: Next.js client for Hey API. Compatible with all our features.
44
---
55

6-
# Next.js <span data-soon>soon</span>
6+
# Next.js
77

88
::: warning
9-
This feature isn't in development yet. Help us prioritize it by voting on [GitHub](https://github.com/hey-api/openapi-ts/issues/1515).
9+
Next.js client is currently in beta. The interface might change before it becomes stable. We encourage you to leave feedback on [GitHub](https://github.com/hey-api/openapi-ts/issues).
1010
:::
1111

1212
[Next.js](https://nextjs.org/) is the React framework for the web. Used by some of the world's largest companies, Next.js enables you to create high-quality web applications with the power of React components.
1313

14+
<!-- <button class="buttonLink" @click="(event) => embedProject('hey-api-client-next-example')(event)">
15+
Live demo
16+
</button> -->
17+
18+
## Installation
19+
20+
Start by adding `@hey-api/client-next` to your dependencies.
21+
22+
::: code-group
23+
24+
```sh [npm]
25+
npm install @hey-api/client-next
26+
```
27+
28+
```sh [pnpm]
29+
pnpm add @hey-api/client-next
30+
```
31+
32+
```sh [yarn]
33+
yarn add @hey-api/client-next
34+
```
35+
36+
```sh [bun]
37+
bun add @hey-api/client-next
38+
```
39+
40+
:::
41+
42+
In your [configuration](/openapi-ts/get-started), add `@hey-api/client-next` to your plugins and you'll be ready to generate client artifacts. :tada:
43+
44+
::: code-group
45+
46+
```js [config]
47+
export default {
48+
input: 'path/to/openapi.json',
49+
output: 'src/client',
50+
plugins: ['@hey-api/client-next'], // [!code ++]
51+
};
52+
```
53+
54+
```sh [cli]
55+
npx @hey-api/openapi-ts \
56+
-i path/to/openapi.json \
57+
-o src/client \
58+
-c @hey-api/client-next # [!code ++]
59+
```
60+
61+
:::
62+
63+
## Configuration
64+
65+
The Next.js client is built as a thin wrapper on top of [fetch](https://nextjs.org/docs/app/api-reference/functions/fetch), extending its functionality to work with Hey API. If you're already familiar with Fetch, configuring your client will feel like working directly with Fetch API.
66+
67+
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.
68+
69+
### `setConfig()`
70+
71+
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 Fetch API configuration option to `setConfig()`, and even your own Fetch implementation.
72+
73+
```js
74+
import { client } from 'client/client.gen';
75+
76+
client.setConfig({
77+
baseUrl: 'https://example.com',
78+
});
79+
```
80+
81+
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.
82+
83+
### Runtime API
84+
85+
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.
86+
87+
```js
88+
export default {
89+
input: 'path/to/openapi.json',
90+
output: 'src/client',
91+
plugins: [
92+
{
93+
name: '@hey-api/client-next',
94+
runtimeConfigPath: './src/hey-api.ts', // [!code ++]
95+
},
96+
],
97+
};
98+
```
99+
100+
In our custom file, we need to export a `createClientConfig()` method. This function is a simple wrapper allowing us to override configuration values.
101+
102+
::: code-group
103+
104+
```ts [hey-api.ts]
105+
import type { CreateClientConfig } from '@hey-api/client-next';
106+
107+
export const createClientConfig: CreateClientConfig = (config) => ({
108+
...config,
109+
baseUrl: 'https://example.com',
110+
});
111+
```
112+
113+
:::
114+
115+
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.
116+
117+
### `createClient()`
118+
119+
You can also create your own client instance. You can use it to manually send requests or point it to a different domain.
120+
121+
```js
122+
import { createClient } from '@hey-api/client-next';
123+
124+
const myClient = createClient({
125+
baseUrl: 'https://example.com',
126+
});
127+
```
128+
129+
You can also pass this instance to any SDK function through the `client` option. This will override the default instance from `client.gen.ts`.
130+
131+
```js
132+
const response = await getFoo({
133+
client: myClient,
134+
});
135+
```
136+
137+
### SDKs
138+
139+
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.
140+
141+
```js
142+
const response = await getFoo({
143+
baseUrl: 'https://example.com', // <-- override default configuration
144+
});
145+
```
146+
147+
## Interceptors
148+
149+
Interceptors (middleware) can be used to modify requests before they're sent or responses before they're returned to your application. They can be added with `use` and removed with `eject`. Fetch API does not have the interceptor functionality, so we implement our own. Below is an example request interceptor
150+
151+
::: code-group
152+
153+
```js [use]
154+
import { client } from 'client/client.gen';
155+
156+
// Supports async functions
157+
client.interceptors.request.use(async (options) => {
158+
// do something
159+
});
160+
```
161+
162+
```js [eject]
163+
import { client } from 'client/client.gen';
164+
165+
client.interceptors.request.eject((options) => {
166+
// do something
167+
});
168+
```
169+
170+
:::
171+
172+
and an example response interceptor
173+
174+
::: code-group
175+
176+
```js [use]
177+
import { client } from 'client/client.gen';
178+
179+
client.interceptors.response.use((response) => {
180+
// do something
181+
return response;
182+
});
183+
```
184+
185+
```js [eject]
186+
import { client } from 'client/client.gen';
187+
188+
client.interceptors.response.eject((response) => {
189+
// do something
190+
return response;
191+
});
192+
```
193+
194+
:::
195+
196+
::: tip
197+
To eject, you must provide a reference to the function that was passed to `use()`.
198+
:::
199+
200+
## Auth
201+
202+
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.
203+
204+
```js
205+
import { client } from 'client/client.gen';
206+
207+
client.setConfig({
208+
auth: () => '<my_token>', // [!code ++]
209+
baseUrl: 'https://example.com',
210+
});
211+
```
212+
213+
If you're not using SDKs or generating auth, using interceptors is a common approach to configuring auth for each request.
214+
215+
```js
216+
import { client } from 'client/client.gen';
217+
218+
client.interceptors.request.use((options) => {
219+
options.headers.set('Authorization', 'Bearer <my_token>'); // [!code ++]
220+
});
221+
```
222+
223+
## Build URL
224+
225+
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.
226+
227+
```ts
228+
type FooData = {
229+
path: {
230+
fooId: number;
231+
};
232+
query?: {
233+
bar?: string;
234+
};
235+
url: '/foo/{fooId}';
236+
};
237+
238+
const url = client.buildUrl<FooData>({
239+
path: {
240+
fooId: 1,
241+
},
242+
query: {
243+
bar: 'baz',
244+
},
245+
url: '/foo/{fooId}',
246+
});
247+
console.log(url); // prints '/foo/1?bar=baz'
248+
```
249+
250+
## Bundling
251+
252+
Sometimes, you may not want to declare client packages as a dependency. This scenario is common if you're using Hey API to generate output that is repackaged and published for other consumers under your own brand. For such cases, our clients support bundling through the `client.bundle` configuration option.
253+
254+
```js
255+
export default {
256+
input: 'path/to/openapi.json',
257+
output: 'src/client',
258+
plugins: [
259+
{
260+
bundle: true, // [!code ++]
261+
name: '@hey-api/client-next',
262+
},
263+
],
264+
};
265+
```
266+
267+
<!--@include: ../../examples.md-->
14268
<!--@include: ../../sponsors.md-->

docs/openapi-ts/clients/nuxt.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ Nuxt client is currently in beta. The interface might change before it becomes s
1111

1212
[Nuxt](https://nuxt.com/) is an open source framework that makes web development intuitive and powerful.
1313

14+
<!-- <button class="buttonLink" @click="(event) => embedProject('hey-api-client-fetch-example')(event)">
15+
Live demo
16+
</button> -->
17+
1418
## Installation
1519

1620
Start by adding `@hey-api/client-nuxt` to your dependencies.

examples/openapi-ts-next/app/layout.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@ import './globals.css';
33
import type { Metadata } from 'next';
44
import { Geist, Geist_Mono } from 'next/font/google';
55

6+
import { client } from '@/src/client/client.gen';
7+
8+
client.interceptors.request.use((options) => {
9+
console.log(options);
10+
});
11+
12+
client.interceptors.response.use((response, options) => {
13+
console.log(response, options);
14+
return response;
15+
});
16+
617
const geistSans = Geist({
718
subsets: ['latin'],
819
variable: '--font-geist-sans',

examples/openapi-ts-next/app/page.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
'use client';
22

33
import Image from 'next/image';
4+
import Link from 'next/link';
45
import { useEffect, useState } from 'react';
56

6-
import { getPetById } from './client/sdk.gen';
7-
import type { Pet } from './client/types.gen';
7+
import { getPetById } from '@/src/client/sdk.gen';
8+
import type { Pet } from '@/src/client/types.gen';
89

910
export default function Home() {
1011
const [pet, setPet] = useState<Pet>();
@@ -13,6 +14,11 @@ export default function Home() {
1314
useEffect(() => {
1415
const fetchPet = async () => {
1516
const { data } = await getPetById({
17+
cache: 'force-cache',
18+
next: {
19+
revalidate: 10,
20+
tags: ['pet'],
21+
},
1622
path: {
1723
petId,
1824
},
@@ -64,14 +70,12 @@ export default function Home() {
6470
/>
6571
Random pet
6672
</button>
67-
<a
73+
<Link
6874
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
69-
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
70-
target="_blank"
71-
rel="noopener noreferrer"
75+
href="/pet/8"
7276
>
73-
Read our docs
74-
</a>
77+
Server component
78+
</Link>
7579
</div>
7680
</main>
7781
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">

0 commit comments

Comments
 (0)