Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 13 additions & 17 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
# AI Rules for VitNode
# VitNode

VitNode is a comprehensive framework designed to simplify and accelerate application development with Next.js and Hono.js. Built as a monorepo solution managed by Turborepo, VitNode provides a structured environment that makes development faster and less complex. The framework includes an integrated AdminCP and plugin system to extend its core functionality.

# VitNode Technology Stack
> **Important:** VitNode is ESM-only (ECMAScript Modules)

- **Frontend (Web)**: Next.js 15 with Tailwind CSS 4 and Shadcn UI components
- **Backend (API)**: Hono.js with RPC for type-safe API calls
- **Database**: PostgreSQL
- **Documentation**: Fumadocs & Swagger 3
- **Build System**: Turborepo for monorepo management
- **CI/CD**: GitHub Actions for automated testing, building, and deployment
- ESM (ECMAScript Modules) only
## Frontend Development Guidelines

### Core Technologies

## Frontend
- **Framework:** Next.js 15
- **Styling:** Tailwind CSS 4
- **UI Components:** Shadcn UI
- **Internationalization:** next-intl 4
- **Icons:** lucide-react 5
- **Schema Validation:** zod 3
- **Form Handling:** react-hook-form 7

- **Framework**: Next.js 15
- **Styling**: Tailwind CSS 4
- **UI Components**: Shadcn UI
- **Internationalization**: next-intl 4
- **Icons**: lucide-react 5
- **Schema Validation**: zod 3
- **Form Handling**: react-hook-form 7
### Architecture Requirements

- Use App Router and Server Components for improved performance and SEO
- Use server actions for form handling and data mutations from Server Components
Expand Down
224 changes: 46 additions & 178 deletions apps/docs/content/docs/dev/fetcher.mdx
Original file line number Diff line number Diff line change
@@ -1,45 +1,52 @@
---
title: Fetcher
description: Fetch data from the server.
description: Fetch data with type-safe API
---

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

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

import { Step, Steps } from 'fumadocs-ui/components/steps';

<Steps>
<Step>

### Initial client
## Usage

`fetcher()` is a function to create client. Pass generic type with the response type and pass the `plugin` and `module` as arguments.
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.

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

```ts
import { fetcher } from 'vitnode/lib/fetcher';
import { UsersTypes } from 'vitnode/api/modules/users/users.module';
import { usersModule } from 'vitnode/api/modules/users/users.module';
```

```tsx
const client = await fetcher<UsersTypes>({
plugin: 'core',
```ts
const res = await fetcher(usersModule, {
path: '/session',
method: 'get',
module: 'users',
});
```

#### Options
The response from `fetcher()` is compatible with the standard `fetch` Response API. Here are common way to work with the response:

```ts
// Basic response handling
if (res.ok) {
const data = await res.json();
console.log(data);
}
```

### Options

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

```tsx
const client = await fetcher<UsersTypes>({
plugin: 'core',
```ts
const res = await fetcher(usersModule, {
path: '/session',
method: 'get',
module: 'users',
// [!code ++]
options: {
Expand All @@ -50,167 +57,28 @@ const client = await fetcher<UsersTypes>({
});
```

</Step>

<Step>

### Fetch data

Call `client.{path}.{method}` with the data you want to send to the server as an argument.

```tsx
const data = await client.sign_in.$post(input);
```

</Step>
</Steps>

## Server Functions

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.

```ts title="mutation-api.ts"
'use server';

import { UsersTypes } from 'vitnode/api/modules/users/users.module';
import { fetcher, FetcherInput } from 'vitnode/lib/fetcher';

export const mutationApi = async (
input: FetcherInput<UsersTypes, '/sign_in', 'post'>,
) => {
const res = await fetcher<UsersTypes>({
plugin: 'core',
module: 'users',
});

await res.sign_in.$post(input);
};
```

Now you can call the `mutationApi()` function on the server-side.

```ts title="useForm.ts"
export const useForm = () => {
const onSubmit = async (values: z.infer<typeof formSchema>) => {
// [!code ++]
await mutationApi({
// [!code ++]
json: values,
// [!code ++]
});
};

return {
onSubmit,
};
};
```

### Handling errors

You can handle errors by checking the `status` property of the response.

```ts title="mutation-api.ts"
export const mutationApi = async (
input: FetcherInput<UsersTypes, '/sign_in', 'post'>,
) => {
const res = await fetcher<UsersTypes>({
plugin: 'core',
module: 'users',
});

// [!code --]
await res.sign_in.$post(input);
// [!code ++]
const data = await res.sign_in.$post(input);
// [!code ++]

// [!code ++]
if (data.status !== 200) {
// [!code ++]
return { message: await data.text() };
// [!code ++]
}
};
```

```ts title="useForm.ts"
// [!code ++]
import { useTranslations } from 'next-intl';
// [!code ++]
import { toast } from 'sonner';

export const useForm = () => {
// [!code ++]
const t = useTranslations('core.global.errors');

const onSubmit = async (values: z.infer<typeof formSchema>) => {
const mutation = await mutationApi({
json: values,
});

// [!code ++]
if (!mutation?.message) return;
// [!code ++]

// [!code ++]
toast.error(t('title'), {
// [!code ++]
description: t('internal_server_error'),
// [!code ++]
});
};

return {
onSubmit,
};
};
```

### Handling set-cookies
### Handle set-cookies

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

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

import { UsersTypes } from 'vitnode/api/modules/users/users.module';
import {
fetcher,
FetcherInput,
// [!code ++]
handleSetCookiesFetcher,
} from 'vitnode/lib/fetcher';

export const mutationApi = async (
input: FetcherInput<UsersTypes, '/sign_in', 'post'>,
) => {
const res = await fetcher<UsersTypes>({
plugin: 'core',
module: 'users',
});

// [!code --]
await res.sign_in.$post(input);
// [!code ++]
const data = await res.sign_in.$post(input);
```ts
const res = await fetcher(usersModule, {
path: '/sign_in',
method: 'post',
module: 'users',
// [!code ++]
await handleSetCookiesFetcher(data);
};
```

## Client-side

If you want to use the `fetcher()` on the client-side, you need to use the `fetcherClient()` function.

```tsx

allowSaveCookies: true,
args: {
body: {
email: '',
password: '',
},
},
});
```

## Custom fetcher

If you want you can create your own `fetch` function, but you need to remember to pass headers like:

- `x-forwarded-for` header - client IP address,
- `Cookie` header - client cookies,
- `user-agent` header - client user agent.
3 changes: 2 additions & 1 deletion apps/docs/content/docs/dev/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
"structure",
"debugging",
"swagger",
"---Plugins---",
"modules",
"---Framework---",
"auth",
"fetcher",
"modules",
"routes",
"---API---",
"sso",
Expand Down
4 changes: 4 additions & 0 deletions apps/docs/content/docs/dev/modules.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ description: xdd
---

xdd

```ts

```
13 changes: 7 additions & 6 deletions apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
"postinstall": "fumadocs-mdx"
},
"dependencies": {
"babel-plugin-react-compiler": "19.0.0-beta-ebf51a3-20250411",
"fumadocs-core": "^15.2.8",
"fumadocs-mdx": "^11.6.0",
"fumadocs-ui": "^15.2.8",
"babel-plugin-react-compiler": "19.1.0-rc.1",
"fumadocs-core": "^15.2.11",
"fumadocs-mdx": "^11.6.1",
"fumadocs-ui": "^15.2.11",
"lucide-react": "^0.488.0",
"motion": "^12.9.2",
"next": "^15.3.1",
"react": "^19.1.0",
"react-dom": "^19.1.0",
Expand All @@ -22,11 +23,11 @@
"devDependencies": {
"@tailwindcss/postcss": "^4.1.4",
"@types/mdx": "^2.0.13",
"@types/node": "^22.14.1",
"@types/node": "^22.15.2",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
"postcss": "^8.5.3",
"shiki": "^3.2.2",
"shiki": "^3.3.0",
"tailwindcss": "^4.1.4",
"typescript": "^5.8.3"
}
Expand Down
13 changes: 7 additions & 6 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,33 @@
"@hono/zod-validator": "^0.4.3",
"@hookform/resolvers": "^5.0.1",
"@react-email/components": "0.0.36",
"babel-plugin-react-compiler": "19.0.0-beta-ebf51a3-20250411",
"babel-plugin-react-compiler": "19.1.0-rc.1",
"dotenv": "^16.5.0",
"drizzle-kit": "^0.31.0",
"drizzle-orm": "^0.42.0",
"hono": "^4.7.7",
"lucide-react": "^0.488.0",
"next": "^15.3.1",
"next-intl": "^4.0.2",
"next-intl": "^4.1.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-hook-form": "^7.55.0",
"react-hook-form": "^7.56.1",
"sonner": "^2.0.3",
"vitnode": "workspace:*",
"vitnode-blog": "workspace:*",
"zod": "^3.24.3"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.4",
"@types/node": "^22.14.1",
"@types/node": "^22.15.2",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
"eslint": "^9.25.0",
"eslint": "^9.25.1",
"eslint-config-typescript-vitnode": "workspace:*",
"prettier": "^3.5.3",
"react-email": "^4.0.7",
"tailwindcss": "^4.1.4",
"tw-animate-css": "^1.2.5",
"tw-animate-css": "^1.2.8",
"typescript": "^5.8.3"
}
}
Loading
Loading