Skip to content

Commit 7098e09

Browse files
committed
refactor: Apply new fetcher
1 parent 42daebc commit 7098e09

File tree

28 files changed

+334
-821
lines changed

28 files changed

+334
-821
lines changed
Lines changed: 46 additions & 178 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,52 @@
11
---
22
title: Fetcher
3-
description: Fetch data from the server.
3+
description: Fetch data with type-safe API
44
---
55

6-
## Usage
6+
Our fetcher has RPC (Remote Procedure Call) style API. It allows you to call any API endpoint with type-safe API.
77

88
<Callout type="warn" emoji="⚠️" title="Server-side only">
99
The `fetcher()` function is only server-side. You cannot use it on the
1010
client-side.
1111
</Callout>
1212

13-
import { Step, Steps } from 'fumadocs-ui/components/steps';
14-
15-
<Steps>
16-
<Step>
17-
18-
### Initial client
13+
## Usage
1914

20-
`fetcher()` is a function to create client. Pass generic type with the response type and pass the `plugin` and `module` as arguments.
15+
To use the `fetcher()` function, you need to import it from the `vitnode/lib/fetcher` module. You also need to import the module you want to use.
2116

22-
As an example, we will pass `UsersTypes` as a generic type.
17+
More about modules can be found in the [Modules](/docs/dev/modules) section.
2318

2419
```ts
2520
import { fetcher } from 'vitnode/lib/fetcher';
26-
import { UsersTypes } from 'vitnode/api/modules/users/users.module';
21+
import { usersModule } from 'vitnode/api/modules/users/users.module';
2722
```
2823

29-
```tsx
30-
const client = await fetcher<UsersTypes>({
31-
plugin: 'core',
24+
```ts
25+
const res = await fetcher(usersModule, {
26+
path: '/session',
27+
method: 'get',
3228
module: 'users',
3329
});
3430
```
3531

36-
#### Options
32+
The response from `fetcher()` is compatible with the standard `fetch` Response API. Here are common way to work with the response:
33+
34+
```ts
35+
// Basic response handling
36+
if (res.ok) {
37+
const data = await res.json();
38+
console.log(data);
39+
}
40+
```
41+
42+
### Options
3743

38-
You can pass the `options` object to modify the `fetch` function. For example we can pass `cache` option to enable the cache.
44+
You can pass the `options` object to modify the `fetch` function. For example we can pass `cache` option to enable the cache from `next`:
3945

40-
```tsx
41-
const client = await fetcher<UsersTypes>({
42-
plugin: 'core',
46+
```ts
47+
const res = await fetcher(usersModule, {
48+
path: '/session',
49+
method: 'get',
4350
module: 'users',
4451
// [!code ++]
4552
options: {
@@ -50,167 +57,28 @@ const client = await fetcher<UsersTypes>({
5057
});
5158
```
5259

53-
</Step>
54-
55-
<Step>
56-
57-
### Fetch data
58-
59-
Call `client.{path}.{method}` with the data you want to send to the server as an argument.
60-
61-
```tsx
62-
const data = await client.sign_in.$post(input);
63-
```
64-
65-
</Step>
66-
</Steps>
67-
68-
## Server Functions
69-
70-
Methods like `POST`, `PUT` and `DELETE` require to use [Server Functions](https://react.dev/reference/rsc/server-functions). You can create `mutation-api.ts` file and use it to call the `fetcher()` function with `FetcherInput` type.
71-
72-
```ts title="mutation-api.ts"
73-
'use server';
74-
75-
import { UsersTypes } from 'vitnode/api/modules/users/users.module';
76-
import { fetcher, FetcherInput } from 'vitnode/lib/fetcher';
77-
78-
export const mutationApi = async (
79-
input: FetcherInput<UsersTypes, '/sign_in', 'post'>,
80-
) => {
81-
const res = await fetcher<UsersTypes>({
82-
plugin: 'core',
83-
module: 'users',
84-
});
85-
86-
await res.sign_in.$post(input);
87-
};
88-
```
89-
90-
Now you can call the `mutationApi()` function on the server-side.
91-
92-
```ts title="useForm.ts"
93-
export const useForm = () => {
94-
const onSubmit = async (values: z.infer<typeof formSchema>) => {
95-
// [!code ++]
96-
await mutationApi({
97-
// [!code ++]
98-
json: values,
99-
// [!code ++]
100-
});
101-
};
102-
103-
return {
104-
onSubmit,
105-
};
106-
};
107-
```
108-
109-
### Handling errors
110-
111-
You can handle errors by checking the `status` property of the response.
112-
113-
```ts title="mutation-api.ts"
114-
export const mutationApi = async (
115-
input: FetcherInput<UsersTypes, '/sign_in', 'post'>,
116-
) => {
117-
const res = await fetcher<UsersTypes>({
118-
plugin: 'core',
119-
module: 'users',
120-
});
121-
122-
// [!code --]
123-
await res.sign_in.$post(input);
124-
// [!code ++]
125-
const data = await res.sign_in.$post(input);
126-
// [!code ++]
127-
128-
// [!code ++]
129-
if (data.status !== 200) {
130-
// [!code ++]
131-
return { message: await data.text() };
132-
// [!code ++]
133-
}
134-
};
135-
```
136-
137-
```ts title="useForm.ts"
138-
// [!code ++]
139-
import { useTranslations } from 'next-intl';
140-
// [!code ++]
141-
import { toast } from 'sonner';
142-
143-
export const useForm = () => {
144-
// [!code ++]
145-
const t = useTranslations('core.global.errors');
146-
147-
const onSubmit = async (values: z.infer<typeof formSchema>) => {
148-
const mutation = await mutationApi({
149-
json: values,
150-
});
151-
152-
// [!code ++]
153-
if (!mutation?.message) return;
154-
// [!code ++]
155-
156-
// [!code ++]
157-
toast.error(t('title'), {
158-
// [!code ++]
159-
description: t('internal_server_error'),
160-
// [!code ++]
161-
});
162-
};
163-
164-
return {
165-
onSubmit,
166-
};
167-
};
168-
```
169-
170-
### Handling set-cookies
60+
### Handle set-cookies
17161

172-
React Server Components cannot handle `set-cookies` headers. You need to handle them by using the `handleCookiesFetcher()` function.
62+
React Server Components cannot handle `set-cookies` headers from the server. To handle
63+
this, you need to pass the `allowSaveCookies` param to the `fetcher()` function. This will allow the cookies to be saved in the response.
17364

174-
```ts title="mutation-api.ts"
175-
'use server';
65+
<Callout type="warn" emoji="⚠️" title="Server-side only">
66+
The `allowSaveCookies` param works only when your request has method different
67+
from `get` and response status has 2xx.
68+
</Callout>
17669

177-
import { UsersTypes } from 'vitnode/api/modules/users/users.module';
178-
import {
179-
fetcher,
180-
FetcherInput,
181-
// [!code ++]
182-
handleSetCookiesFetcher,
183-
} from 'vitnode/lib/fetcher';
184-
185-
export const mutationApi = async (
186-
input: FetcherInput<UsersTypes, '/sign_in', 'post'>,
187-
) => {
188-
const res = await fetcher<UsersTypes>({
189-
plugin: 'core',
190-
module: 'users',
191-
});
192-
193-
// [!code --]
194-
await res.sign_in.$post(input);
195-
// [!code ++]
196-
const data = await res.sign_in.$post(input);
70+
```ts
71+
const res = await fetcher(usersModule, {
72+
path: '/sign_in',
73+
method: 'post',
74+
module: 'users',
19775
// [!code ++]
198-
await handleSetCookiesFetcher(data);
199-
};
200-
```
201-
202-
## Client-side
203-
204-
If you want to use the `fetcher()` on the client-side, you need to use the `fetcherClient()` function.
205-
206-
```tsx
207-
76+
allowSaveCookies: true,
77+
args: {
78+
body: {
79+
email: '',
80+
password: '',
81+
},
82+
},
83+
});
20884
```
209-
210-
## Custom fetcher
211-
212-
If you want you can create your own `fetch` function, but you need to remember to pass headers like:
213-
214-
- `x-forwarded-for` header - client IP address,
215-
- `Cookie` header - client cookies,
216-
- `user-agent` header - client user agent.

apps/web/src/plugins/core/langs/en.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,10 @@
9696
"label": "Email",
9797
"invalid": "Invalid email address."
9898
},
99-
"password": "Password",
99+
"password": {
100+
"label": "Password",
101+
"required": "Password is required."
102+
},
100103
"errors": {
101104
"access_denied": {
102105
"title": "Invalid credentials",

packages/vitnode/src/api/lib/module.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ export interface BaseBuildModuleReturn<
2222
export interface BuildModuleReturn<
2323
P extends string,
2424
M extends string,
25-
Routes extends Route[] = [],
26-
Modules extends BaseBuildModuleReturn<P>[] = [],
25+
Routes extends Route[] = Route[],
26+
Modules extends BaseBuildModuleReturn<P>[] = BaseBuildModuleReturn<P>[],
2727
> extends BaseBuildModuleReturn<P, M, Routes> {
2828
modules?: Modules;
2929
}

packages/vitnode/src/api/lib/plugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export function buildPlugin<P extends string>({
1111
name,
1212
modules = [],
1313
}: {
14-
modules?: BuildModuleReturn<P>[];
14+
modules?: BuildModuleReturn<P, string>[];
1515
name: P;
1616
}): BuildPluginReturn {
1717
const hono = new OpenAPIHono();

packages/vitnode/src/api/modules/admin/admin.module.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,3 @@ export const adminModule = buildModule({
77
plugin: 'core',
88
routes: [sessionAdminRoute],
99
});
10-
11-
export type AdminTypes = typeof adminModule;

packages/vitnode/src/api/modules/middleware/middleware.module.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,8 @@ import { buildModule } from '@/api/lib/module';
22

33
import { routeMiddleware } from './route';
44

5-
export type MiddlewareTypes = typeof middlewareModule;
6-
75
export const middlewareModule = buildModule({
86
plugin: 'core',
97
name: 'middleware',
108
routes: [routeMiddleware],
119
});
12-
13-
export type BuildMiddlewareTypes = typeof middlewareModule;

packages/vitnode/src/api/modules/users/routes/sign-in.route.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ export const signInRoute = buildRoute({
3030
},
3131
},
3232
responses: {
33+
403: {
34+
description: 'Access Denied',
35+
},
3336
201: {
3437
content: {
3538
'application/json': {
@@ -41,15 +44,6 @@ export const signInRoute = buildRoute({
4144
},
4245
description: 'User signed in',
4346
},
44-
403: {
45-
description: 'Access Denied',
46-
'application/json': {
47-
schema: z.object({
48-
id: z.string(),
49-
token: z.string(),
50-
}),
51-
},
52-
},
5347
},
5448
},
5549
handler: async c => {
@@ -61,10 +55,10 @@ export const signInRoute = buildRoute({
6155
data.id,
6256
);
6357

64-
return c.json({ id: data.id, token });
58+
return c.json({ id: data.id, token }, 201);
6559
}
6660
const { token } = await new SessionModel(c).createSessionByUserId(data.id);
6761

68-
return c.json({ id: data.id, token });
62+
return c.json({ id: data.id, token }, 201);
6963
},
7064
});

packages/vitnode/src/api/modules/users/users.module.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,3 @@ export const usersModule = buildModule({
1313
routes: [sessionRoute, signInRoute, signOutRoute, signUpRoute, testRoute],
1414
modules: [ssoUserModule],
1515
});
16-
17-
export type UsersTypes = typeof usersModule;
Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
1-
import { MiddlewareTypes } from '@/api/modules/middleware/middleware.module';
2-
3-
import { fetcher } from '../fetcher';
1+
import { middlewareModule } from '@/api/modules/middleware/middleware.module';
2+
import { fetcher } from '@/lib/fetcher';
43

54
export const getMiddlewareApi = async () => {
6-
const client = await fetcher<MiddlewareTypes>({
7-
plugin: 'core',
5+
const res = await fetcher(middlewareModule, {
6+
path: '/',
7+
method: 'get',
88
module: 'middleware',
9-
options: {
10-
cache: 'force-cache',
11-
},
129
});
1310

14-
const res = await client.index.$get();
15-
1611
return await res.json();
1712
};

0 commit comments

Comments
 (0)