h3 2.0.1-rc.2 → 2.0.1-rc.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/h3.mjs +35 -0
- package/dist/THIRD-PARTY-LICENSES.md +70 -0
- package/dist/_entries/bun.d.mts +3 -8
- package/dist/_entries/bun.mjs +3 -9
- package/dist/_entries/cloudflare.d.mts +4 -9
- package/dist/_entries/cloudflare.mjs +3 -9
- package/dist/_entries/deno.d.mts +3 -8
- package/dist/_entries/deno.mjs +3 -9
- package/dist/_entries/generic.d.mts +3 -8
- package/dist/_entries/generic.mjs +3 -9
- package/dist/_entries/node.d.mts +3 -8
- package/dist/_entries/node.mjs +3 -12
- package/dist/_entries/service-worker.d.mts +3 -8
- package/dist/_entries/service-worker.mjs +3 -9
- package/dist/docs/0.guide/0.index/index.md +117 -0
- package/dist/docs/0.guide/1.basics/0.lifecycle.md +68 -0
- package/dist/docs/0.guide/1.basics/1.routing.md +92 -0
- package/dist/docs/0.guide/1.basics/2.middleware.md +97 -0
- package/dist/docs/0.guide/1.basics/3.handler.md +165 -0
- package/dist/docs/0.guide/1.basics/4.response.md +162 -0
- package/dist/docs/0.guide/1.basics/5.error.md +117 -0
- package/dist/docs/0.guide/1.basics/6.nested-apps.md +57 -0
- package/dist/docs/0.guide/2.api/0.h3.md +145 -0
- package/dist/docs/0.guide/2.api/1.h3event.md +113 -0
- package/dist/docs/0.guide/3.advanced/0.plugins.md +50 -0
- package/dist/docs/0.guide/3.advanced/1.websocket.md +124 -0
- package/dist/docs/0.guide/3.advanced/2.nightly.md +13 -0
- package/dist/docs/1.utils/0.index/index.md +46 -0
- package/dist/docs/1.utils/1.request.md +355 -0
- package/dist/docs/1.utils/2.response.md +144 -0
- package/dist/docs/1.utils/3.cookie.md +33 -0
- package/dist/docs/1.utils/4.security.md +109 -0
- package/dist/docs/1.utils/5.proxy.md +31 -0
- package/dist/docs/1.utils/6.mcp.md +71 -0
- package/dist/docs/1.utils/7.more.md +78 -0
- package/dist/docs/1.utils/8.community.md +42 -0
- package/dist/docs/2.examples/0.index/index.md +16 -0
- package/dist/docs/2.examples/1.handle-cookie.md +67 -0
- package/dist/docs/2.examples/2.handle-session.md +130 -0
- package/dist/docs/2.examples/3.serve-static-assets.md +66 -0
- package/dist/docs/2.examples/4.stream-response.md +76 -0
- package/dist/docs/2.examples/5.validate-data.md +193 -0
- package/dist/docs/3.migration/0.index/index.md +200 -0
- package/dist/docs/README.md +35 -0
- package/dist/{h3.mjs → h3-CRCltuUf.mjs} +915 -1218
- package/dist/h3-D76FUMrE.d.mts +833 -0
- package/dist/h3-DagAgogP.mjs +4 -0
- package/dist/{h3.d.mts → h3-DiSMXP1G.d.mts} +320 -656
- package/dist/tracing.d.mts +24 -0
- package/dist/tracing.mjs +76 -0
- package/package.json +56 -44
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Sessions
|
|
2
|
+
|
|
3
|
+
> Remember your users using a session.
|
|
4
|
+
|
|
5
|
+
A session is a way to remember users using cookies. It is a very common method for authenticating users or saving data about them, such as their language or preferences on the web.
|
|
6
|
+
|
|
7
|
+
H3 provides many utilities to handle sessions:
|
|
8
|
+
|
|
9
|
+
- `useSession` initializes a session and returns a wrapper to control it.
|
|
10
|
+
- `getSession` initializes or retrieves the current user session.
|
|
11
|
+
- `updateSession` updates the data of the current session.
|
|
12
|
+
- `clearSession` clears the current session.
|
|
13
|
+
Most of the time, you will use `useSession` to manipulate the session.
|
|
14
|
+
|
|
15
|
+
## Initialize a Session
|
|
16
|
+
|
|
17
|
+
To initialize a session, you need to use `useSession` in an [event handler](/guide/handler):
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
import { useSession } from "h3";
|
|
21
|
+
|
|
22
|
+
app.use(async (event) => {
|
|
23
|
+
const session = await useSession(event, {
|
|
24
|
+
password: "80d42cfb-1cd2-462c-8f17-e3237d9027e9",
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// do something...
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> [!WARNING]
|
|
32
|
+
> You must provide a password to encrypt the session.
|
|
33
|
+
|
|
34
|
+
This will initialize a session and return an header `Set-Cookie` with a cookie named `h3` and an encrypted content.
|
|
35
|
+
|
|
36
|
+
If the request contains a cookie named `h3` or a header named `x-h3-session`, the session will be initialized with the content of the cookie or the header.
|
|
37
|
+
|
|
38
|
+
> [!NOTE]
|
|
39
|
+
> The header take precedence over the cookie.
|
|
40
|
+
|
|
41
|
+
## Get Data from a Session
|
|
42
|
+
|
|
43
|
+
To get data from a session, we will still use `useSession`. Under the hood, it will use `getSession` to get the session.
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
import { useSession } from "h3";
|
|
47
|
+
|
|
48
|
+
app.use(async (event) => {
|
|
49
|
+
const session = await useSession(event, {
|
|
50
|
+
password: "80d42cfb-1cd2-462c-8f17-e3237d9027e9",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return session.data;
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Data are stored in the `data` property of the session. If there is no data, it will be an empty object.
|
|
58
|
+
|
|
59
|
+
## Add Data to a Session
|
|
60
|
+
|
|
61
|
+
To add data to a session, we will still use `useSession`. Under the hood, it will use `updateSession` to update the session.
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
import { useSession } from "h3";
|
|
65
|
+
|
|
66
|
+
app.use(async (event) => {
|
|
67
|
+
const session = await useSession(event, {
|
|
68
|
+
password: "80d42cfb-1cd2-462c-8f17-e3237d9027e9",
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const count = (session.data.count || 0) + 1;
|
|
72
|
+
await session.update({
|
|
73
|
+
count: count,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return count === 0 ? "Hello world!" : `Hello world! You have visited this page ${count} times.`;
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
What is happening here?
|
|
81
|
+
|
|
82
|
+
We try to get a session from the request. If there is no session, a new one will be created. Then, we increment the `count` property of the session and we update the session with the new value. Finally, we return a message with the number of times the user visited the page.
|
|
83
|
+
|
|
84
|
+
Try to visit the page multiple times and you will see the number of times you visited the page.
|
|
85
|
+
|
|
86
|
+
> [!NOTE]
|
|
87
|
+
> If you use a CLI tool like `curl` to test this example, you will not see the number of times you visited the page because the CLI tool does not save cookies. You must get the cookie from the response and send it back to the server.
|
|
88
|
+
|
|
89
|
+
## Clear a Session
|
|
90
|
+
|
|
91
|
+
To clear a session, we will still use `useSession`. Under the hood, it will use `clearSession` to clear the session.
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
import { useSession } from "h3";
|
|
95
|
+
|
|
96
|
+
app.use("/clear", async (event) => {
|
|
97
|
+
const session = await useSession(event, {
|
|
98
|
+
password: "80d42cfb-1cd2-462c-8f17-e3237d9027e9",
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
await session.clear();
|
|
102
|
+
|
|
103
|
+
return "Session cleared";
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
H3 will send a header `Set-Cookie` with an empty cookie named `h3` to clear the session.
|
|
108
|
+
|
|
109
|
+
## Options
|
|
110
|
+
|
|
111
|
+
When to use `useSession`, you can pass an object with options as the second argument to configure the session:
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
import { useSession } from "h3";
|
|
115
|
+
|
|
116
|
+
app.use(async (event) => {
|
|
117
|
+
const session = await useSession(event, {
|
|
118
|
+
name: "my-session",
|
|
119
|
+
password: "80d42cfb-1cd2-462c-8f17-e3237d9027e9",
|
|
120
|
+
cookie: {
|
|
121
|
+
httpOnly: true,
|
|
122
|
+
secure: true,
|
|
123
|
+
sameSite: "strict",
|
|
124
|
+
},
|
|
125
|
+
maxAge: 60 * 60 * 24 * 7, // 7 days
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return session.data;
|
|
129
|
+
});
|
|
130
|
+
```
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Static Assets
|
|
2
|
+
|
|
3
|
+
> Serve static assets such as HTML, images, CSS, JavaScript, etc.
|
|
4
|
+
|
|
5
|
+
H3 can serve static assets such as HTML, images, CSS, JavaScript, etc.
|
|
6
|
+
|
|
7
|
+
To serve a static directory, you can use the `serveStatic` utility.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { H3, serveStatic } from "h3";
|
|
11
|
+
|
|
12
|
+
const app = new H3();
|
|
13
|
+
|
|
14
|
+
app.use("/public/**", (event) => {
|
|
15
|
+
return serveStatic(event, {
|
|
16
|
+
getContents: (id) => {
|
|
17
|
+
// TODO
|
|
18
|
+
},
|
|
19
|
+
getMeta: (id) => {
|
|
20
|
+
// TODO
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
This does not serve any files yet. You need to implement the `getContents` and `getMeta` methods.
|
|
27
|
+
|
|
28
|
+
- `getContents` is used to read the contents of a file. It should return a `Promise` that resolves to the contents of the file or `undefined` if the file does not exist.
|
|
29
|
+
- `getMeta` is used to get the metadata of a file. It should return a `Promise` that resolves to the metadata of the file or `undefined` if the file does not exist.
|
|
30
|
+
They are separated to allow H3 to respond to `HEAD` requests without reading the contents of the file and to use the `Last-Modified` header.
|
|
31
|
+
|
|
32
|
+
## Read files
|
|
33
|
+
|
|
34
|
+
Now, create a `index.html` file in the `public` directory with a simple message and open your browser to http://localhost:3000. You should see the message.
|
|
35
|
+
|
|
36
|
+
Then, we can create the `getContents` and `getMeta` methods:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { stat, readFile } from "node:fs/promises";
|
|
40
|
+
import { join } from "node:path";
|
|
41
|
+
import { H3, serve, serveStatic } from "h3";
|
|
42
|
+
|
|
43
|
+
const app = new H3();
|
|
44
|
+
|
|
45
|
+
app.use("/public/**", (event) => {
|
|
46
|
+
return serveStatic(event, {
|
|
47
|
+
indexNames: ["/index.html"],
|
|
48
|
+
getContents: (id) => readFile(join("public", id)),
|
|
49
|
+
getMeta: async (id) => {
|
|
50
|
+
const stats = await stat(join("public", id)).catch(() => {});
|
|
51
|
+
if (stats?.isFile()) {
|
|
52
|
+
return {
|
|
53
|
+
size: stats.size,
|
|
54
|
+
mtime: stats.mtimeMs,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
serve(app);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The `getContents` reads the file and returns its contents, pretty simple. The `getMeta` uses `fs.stat` to get the file metadata. If the file does not exist or is not a file, it returns `undefined`. Otherwise, it returns the file size and the last modification time.
|
|
65
|
+
|
|
66
|
+
The file size and last modification time are used to create an etag to send a `304 Not Modified` response if the file has not been modified since the last request. This is useful to avoid sending the same file multiple times if it has not changed.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Stream Response
|
|
2
|
+
|
|
3
|
+
> Stream response to the client.
|
|
4
|
+
|
|
5
|
+
Using stream responses It allows you to send data to the client as soon as you have it. This is useful for large files or long running responses.
|
|
6
|
+
|
|
7
|
+
## Create a Stream
|
|
8
|
+
|
|
9
|
+
To stream a response, you first need to create a stream using the [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) API:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
const stream = new ReadableStream();
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
For the example, we will create a start function that will send a random number every 100 milliseconds. After 1000 milliseconds, it will close the stream:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
let interval: NodeJS.Timeout;
|
|
19
|
+
const stream = new ReadableStream({
|
|
20
|
+
start(controller) {
|
|
21
|
+
controller.enqueue("<ul>");
|
|
22
|
+
|
|
23
|
+
interval = setInterval(() => {
|
|
24
|
+
controller.enqueue("<li>" + Math.random() + "</li>");
|
|
25
|
+
}, 100);
|
|
26
|
+
|
|
27
|
+
setTimeout(() => {
|
|
28
|
+
clearInterval(interval);
|
|
29
|
+
controller.close();
|
|
30
|
+
}, 1000);
|
|
31
|
+
},
|
|
32
|
+
cancel() {
|
|
33
|
+
clearInterval(interval);
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Send a Stream
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { H3 } from "h3";
|
|
42
|
+
|
|
43
|
+
export const app = new H3();
|
|
44
|
+
|
|
45
|
+
app.use((event) => {
|
|
46
|
+
// Set to response header to tell to the client that we are sending a stream.
|
|
47
|
+
event.res.headers.set("Content-Type", "text/html");
|
|
48
|
+
event.res.headers.set("Cache-Control", "no-cache");
|
|
49
|
+
event.res.headers.set("Transfer-Encoding", "chunked");
|
|
50
|
+
|
|
51
|
+
let interval: NodeJS.Timeout;
|
|
52
|
+
const stream = new ReadableStream({
|
|
53
|
+
start(controller) {
|
|
54
|
+
controller.enqueue("<ul>");
|
|
55
|
+
|
|
56
|
+
interval = setInterval(() => {
|
|
57
|
+
controller.enqueue("<li>" + Math.random() + "</li>");
|
|
58
|
+
}, 100);
|
|
59
|
+
|
|
60
|
+
setTimeout(() => {
|
|
61
|
+
clearInterval(interval);
|
|
62
|
+
controller.close();
|
|
63
|
+
}, 1000);
|
|
64
|
+
},
|
|
65
|
+
cancel() {
|
|
66
|
+
clearInterval(interval);
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return stream;
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Open your browser to http://localhost:3000 and you should see a list of random numbers appearing every 100 milliseconds.
|
|
75
|
+
|
|
76
|
+
Magic! 🎉
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Validate Data
|
|
2
|
+
|
|
3
|
+
> Ensure that your data are valid and safe before processing them.
|
|
4
|
+
|
|
5
|
+
When you receive data on your server, you must validate them. By validate, we mean that the shape of the received data must match the expected shape. It's important because you can't trust the data coming from unknown sources, like a user or an external API.
|
|
6
|
+
|
|
7
|
+
> [!WARNING]
|
|
8
|
+
> Do not use type generics as a validation. Providing an interface to a utility like `readBody` is not a validation. You must validate the data before using it.
|
|
9
|
+
|
|
10
|
+
## Utilities for Validation
|
|
11
|
+
|
|
12
|
+
H3 provide some utilities to help you to handle data validation. You will be able to validate:
|
|
13
|
+
|
|
14
|
+
- query with `getValidatedQuery`
|
|
15
|
+
- params with `getValidatedRouterParams`.
|
|
16
|
+
- body with `readValidatedBody`
|
|
17
|
+
H3 doesn't provide any validation library but it does support schemas coming from a **Standard-Schema** compatible one, like: [Zod](https://zod.dev), [Valibot](https://valibot.dev), [ArkType](https://arktype.io/), etc... (for all compatible libraries please check [their official repository](https://github.com/standard-schema/standard-schema)). If you want to use a validation library that is not compatible with Standard-Schema, you can still use it, but you will have to use parsing functions provided by the library itself (refer to the [Safe Parsing](#safe-parsing) section below).
|
|
18
|
+
|
|
19
|
+
> [!WARNING]
|
|
20
|
+
> H3 is runtime agnostic. This means that you can use it in [any runtime](/adapters). But some validation libraries are not compatible with all runtimes.
|
|
21
|
+
|
|
22
|
+
Let's see how to validate data with [Zod](https://zod.dev) and [Valibot](https://valibot.dev).
|
|
23
|
+
|
|
24
|
+
### Validate Params
|
|
25
|
+
|
|
26
|
+
You can use `getValidatedRouterParams` to validate params and get the result, as a replacement of `getRouterParams`:
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
import { getValidatedRouterParams } from "h3";
|
|
30
|
+
import * as z from "zod";
|
|
31
|
+
import * as v from "valibot";
|
|
32
|
+
|
|
33
|
+
// Example with Zod
|
|
34
|
+
const contentSchema = z.object({
|
|
35
|
+
topic: z.string().min(1),
|
|
36
|
+
uuid: z.string().uuid(),
|
|
37
|
+
});
|
|
38
|
+
// Example with Valibot
|
|
39
|
+
const contentSchema = v.object({
|
|
40
|
+
topic: v.pipe(v.string(), v.nonEmpty()),
|
|
41
|
+
uuid: v.pipe(v.string(), v.uuid()),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
app.all(
|
|
45
|
+
// You must use a router to use params
|
|
46
|
+
"/content/:topic/:uuid",
|
|
47
|
+
async (event) => {
|
|
48
|
+
const params = await getValidatedRouterParams(event, contentSchema);
|
|
49
|
+
return `You are looking for content with topic "${params.topic}" and uuid "${params.uuid}".`;
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
If you send a valid request like `/content/posts/123e4567-e89b-12d3-a456-426614174000` to this event handler, you will get a response like this:
|
|
55
|
+
|
|
56
|
+
```txt
|
|
57
|
+
You are looking for content with topic "posts" and uuid "123e4567-e89b-12d3-a456-426614174000".
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
If you send an invalid request and the validation fails, H3 will throw a `400 Validation Error` error. In the data of the error, you will find the validation errors you can use on your client to display a nice error message to your user.
|
|
61
|
+
|
|
62
|
+
### Validate Query
|
|
63
|
+
|
|
64
|
+
You can use `getValidatedQuery` to validate query and get the result, as a replacement of `getQuery`:
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
import { getValidatedQuery } from "h3";
|
|
68
|
+
import * as z from "zod";
|
|
69
|
+
import * as v from "valibot";
|
|
70
|
+
|
|
71
|
+
// Example with Zod
|
|
72
|
+
const stringToNumber = z.string().regex(/^\d+$/, "Must be a number string").transform(Number);
|
|
73
|
+
const paginationSchema = z.object({
|
|
74
|
+
page: stringToNumber.optional().default(1),
|
|
75
|
+
size: stringToNumber.optional().default(10),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Example with Valibot
|
|
79
|
+
const stringToNumber = v.pipe(
|
|
80
|
+
v.string(),
|
|
81
|
+
v.regex(/^\d+$/, "Must be a number string"),
|
|
82
|
+
v.transform(Number),
|
|
83
|
+
);
|
|
84
|
+
const paginationSchema = v.object({
|
|
85
|
+
page: v.optional(stringToNumber, 1),
|
|
86
|
+
size: v.optional(stringToNumber, 10),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
app.use(async (event) => {
|
|
90
|
+
const query = await getValidatedQuery(event, paginationSchema);
|
|
91
|
+
return `You are on page ${query.page} with ${query.size} items per page.`;
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
As you may have noticed, compared to the `getValidatedRouterParams` example, we can leverage validation libraries to transform the incoming data. In this case, we transform the string representation of a number into a real number, which is useful for things like content pagination.
|
|
96
|
+
|
|
97
|
+
If you send a valid request like `/?page=2&size=20` to this event handler, you will get a response like this:
|
|
98
|
+
|
|
99
|
+
```txt
|
|
100
|
+
You are on page 2 with 20 items per page.
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
If you send an invalid request and the validation fails, H3 will throw a `400 Validation Error` error. In the data of the error, you will find the validation errors you can use on your client to display a nice error message to your user.
|
|
104
|
+
|
|
105
|
+
### Validate Body
|
|
106
|
+
|
|
107
|
+
You can use `readValidatedBody` to validate body and get the result, as a replacement of `readBody`:
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
import { readValidatedBody } from "h3";
|
|
111
|
+
import { z } from "zod";
|
|
112
|
+
import * as v from "valibot";
|
|
113
|
+
|
|
114
|
+
// Example with Zod
|
|
115
|
+
const userSchema = z.object({
|
|
116
|
+
name: z.string().min(3).max(20),
|
|
117
|
+
age: z.number({ coerce: true }).positive().int(),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Example with Valibot
|
|
121
|
+
const userSchema = v.object({
|
|
122
|
+
name: v.pipe(v.string(), v.minLength(3), v.maxLength(20)),
|
|
123
|
+
age: v.pipe(v.number(), v.integer(), v.minValue(0)),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
app.use(async (event) => {
|
|
127
|
+
const body = await readValidatedBody(event, userSchema);
|
|
128
|
+
return `Hello ${body.name}! You are ${body.age} years old.`;
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
If you send a valid POST request with a JSON body like this:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"name": "John",
|
|
137
|
+
"age": 42
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
You will get a response like this:
|
|
142
|
+
|
|
143
|
+
```txt
|
|
144
|
+
Hello John! You are 42 years old.
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
If you send an invalid request and the validation fails, H3 will throw a `400 Validation Error` error. In the data of the error, you will find the validation errors you can use on your client to display a nice error message to your user.
|
|
148
|
+
|
|
149
|
+
## Safe Parsing
|
|
150
|
+
|
|
151
|
+
By default if a schema is directly provided as e second argument for each validation utility (`getValidatedRouterParams`, `getValidatedQuery`, and `readValidatedBody`) it will throw a `400 Validation Error` error if the validation fails, but in some cases you may want to handle the validation errors yourself. For this you should provide the actual safe validation function as the second argument, depending on the validation library you are using.
|
|
152
|
+
|
|
153
|
+
Going back to the first example with `getValidatedRouterParams`, for Zod it would look like this:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import { getValidatedRouterParams } from "h3";
|
|
157
|
+
import { z } from "zod/v4";
|
|
158
|
+
|
|
159
|
+
const contentSchema = z.object({
|
|
160
|
+
topic: z.string().min(1),
|
|
161
|
+
uuid: z.string().uuid(),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
app.all("/content/:topic/:uuid", async (event) => {
|
|
165
|
+
const params = await getValidatedRouterParams(event, contentSchema.safeParse);
|
|
166
|
+
if (!params.success) {
|
|
167
|
+
// Handle validation errors
|
|
168
|
+
return `Validation failed:\n${z.prettifyError(params.error)}`;
|
|
169
|
+
}
|
|
170
|
+
return `You are looking for content with topic "${params.data.topic}" and uuid "${params.data.uuid}".`;
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
And for Valibot, it would look like this:
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import { getValidatedRouterParams } from "h3";
|
|
178
|
+
import * as v from "valibot";
|
|
179
|
+
|
|
180
|
+
const contentSchema = v.object({
|
|
181
|
+
topic: v.pipe(v.string(), v.nonEmpty()),
|
|
182
|
+
uuid: v.pipe(v.string(), v.uuid()),
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
app.all("/content/:topic/:uuid", async (event) => {
|
|
186
|
+
const params = await getValidatedRouterParams(event, v.safeParser(contentSchema));
|
|
187
|
+
if (!params.success) {
|
|
188
|
+
// Handle validation errors
|
|
189
|
+
return `Validation failed:\n${v.summarize(params.issues)}`;
|
|
190
|
+
}
|
|
191
|
+
return `You are looking for content with topic "${params.output.topic}" and uuid "${params.output.uuid}".`;
|
|
192
|
+
});
|
|
193
|
+
```
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Migration guide for v1 to v2
|
|
2
|
+
|
|
3
|
+
H3 version 2 includes some behavior and API changes that you need to consider applying when migrating.
|
|
4
|
+
|
|
5
|
+
> [!NOTE]
|
|
6
|
+
> Currently H3 v2 in beta stage. You can try with [nightly channel](/guide/advanced/nightly).
|
|
7
|
+
|
|
8
|
+
> [!NOTE]
|
|
9
|
+
> This is an undergoing migration guide and might be updated.
|
|
10
|
+
|
|
11
|
+
> [!TIP]
|
|
12
|
+
> H3 has a brand new documentation rewrite. Head to the new [Guide](/guide) section to learn more!
|
|
13
|
+
|
|
14
|
+
## Latest Node.js and ESM-only
|
|
15
|
+
|
|
16
|
+
> [!TIP]
|
|
17
|
+
> H3 v2 requires Node.js >= 20.11 (latest LTS recommended) .
|
|
18
|
+
|
|
19
|
+
If your application is currently using CommonJS modules (`require` and `module.exports`), You can still use `require("h3")` thanks to `require(esm)` supported in latest Node.js versions.
|
|
20
|
+
|
|
21
|
+
You can alternatively use other compatible runtimes [Bun](https://bun.sh/) or [Deno](https://deno.com/).
|
|
22
|
+
|
|
23
|
+
## Web Standards
|
|
24
|
+
|
|
25
|
+
> [!TIP]
|
|
26
|
+
> H3 v2 is rewritten based on web standard primitives ([`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL), [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers), [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request), and [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)).
|
|
27
|
+
|
|
28
|
+
When using Node.js, H3 uses a compatibility layer ([💥 srvx](https://srvx.h3.dev/guide/node)) and in other runtimes uses native web compatibility APIs.
|
|
29
|
+
|
|
30
|
+
Access to the native `event.node.{req,res}` is only available when running server in Node.js runtime.
|
|
31
|
+
|
|
32
|
+
`event.web` is renamed to `event.req` (instance of web [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request)).
|
|
33
|
+
|
|
34
|
+
## Response Handling
|
|
35
|
+
|
|
36
|
+
> [!TIP]
|
|
37
|
+
> You should always explicitly **return** the response body or **throw** an error.
|
|
38
|
+
|
|
39
|
+
If you were previously using methods below, you can replace them with `return` statements returning a text, JSON, stream, or web `Response` (h3 smartly detects and handles each):
|
|
40
|
+
|
|
41
|
+
- `send(event, value)`: Migrate to `return <value>`.
|
|
42
|
+
- `sendError(event, <error>)`: Migrate to `throw createError(<error>)`.
|
|
43
|
+
- `sendStream(event, <stream>)`: Migrate to `return <stream>`.
|
|
44
|
+
- `sendWebResponse(event, <response>)`: Migrate to `return <response>`.
|
|
45
|
+
Other send utils that are renamed and need explicit `return`:
|
|
46
|
+
|
|
47
|
+
- `sendNoContent(event)` / `return null`: Migrate to `return noContent()`.
|
|
48
|
+
- `sendIterable(event, <value>)`: Migrate to `return iterable(<value>)`.
|
|
49
|
+
- `sendProxy(event, target)`: Migrate to `return proxy(event, target)`.
|
|
50
|
+
- `handleCors(event)`: Check return value and early `return` if handled(not `false`).
|
|
51
|
+
- `serveStatic(event, content)`: Make sure to add `return` before.
|
|
52
|
+
- `sendRedirect(event, location, code)`: Migrate to `return redirect(location, code)`.
|
|
53
|
+
<read-more></read-more>
|
|
54
|
+
|
|
55
|
+
## H3 and Router
|
|
56
|
+
|
|
57
|
+
> [!TIP]
|
|
58
|
+
> Router function is now integrated into the H3 core.
|
|
59
|
+
> Instead of `createApp()` and `createRouter()` you can use [`new H3()`](/guide/api/h3).
|
|
60
|
+
|
|
61
|
+
Any handler can return a response. If middleware don't return a response, next handlers will be tried and finally make a 404 if neither responses. Router handlers can return or not return any response, in this case, H3 will send a simple 200 with empty content.
|
|
62
|
+
|
|
63
|
+
<read-more></read-more>
|
|
64
|
+
|
|
65
|
+
H3 migrated to a brand new route-matching engine ([🌳 rou3](https://rou3.h3.dev/)). You might experience slight (but more intuitive) behavior changes for matching patterns.
|
|
66
|
+
|
|
67
|
+
**Other changes from v1:**
|
|
68
|
+
|
|
69
|
+
- Middleware added with `app.use("/path", handler)` only matches `/path` (not `/path/foo/bar`). For matching all subpaths like before, it should be updated to `app.use("/path/**", handler)`.
|
|
70
|
+
- The `event.path` received in each handler will have a full path without omitting the prefixes. use `withBase(base, handler)` utility to make prefixed app. (example: `withBase("/api", app.handler)`).
|
|
71
|
+
- **`router.add(path, method: Method | Method[]` signature is changed to `router.add(method: Method, path)`**
|
|
72
|
+
- `router.use(path, handler)` is deprecated. Use `router.all(path, handler)` instead.
|
|
73
|
+
- `app.use(() => handler, { lazy: true })` is no supported anymore. Instead you can use `app.use(defineLazyEventHandler(() => handler), { lazy: true })`.
|
|
74
|
+
- `app.use(["/path1", "/path2"], ...)` and `app.use("/path", [handler1, handler2])` are not supported anymore. Instead, use multiple `app.use()` calls.
|
|
75
|
+
- `app.resolve(path)` removed.
|
|
76
|
+
<read-more></read-more>
|
|
77
|
+
|
|
78
|
+
<read-more></read-more>
|
|
79
|
+
|
|
80
|
+
## Request Body
|
|
81
|
+
|
|
82
|
+
> [!TIP]
|
|
83
|
+
> Most of request body utilities can now be replaced with native `event.req.*` methods which is based on web [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Response) interface.
|
|
84
|
+
|
|
85
|
+
`readBody(event)` utility will use [`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) or [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) for parsing requests with `application/x-www-form-urlencoded` content-type.
|
|
86
|
+
|
|
87
|
+
- For text: Use [event.req.text()](https://developer.mozilla.org/en-US/docs/Web/API/Request/text).
|
|
88
|
+
- For json: Use [event.req.json()](https://developer.mozilla.org/en-US/docs/Web/API/Request/json).
|
|
89
|
+
- For formData: Use [event.req.formData()](https://developer.mozilla.org/en-US/docs/Web/API/Request/formData).
|
|
90
|
+
- For stream: Use [event.req.body](https://developer.mozilla.org/en-US/docs/Web/API/Request/body).
|
|
91
|
+
**Behavior changes:**
|
|
92
|
+
|
|
93
|
+
- Body utils won't throw an error if the incoming request has no body (or is a `GET` method for example) but instead, return empty values.
|
|
94
|
+
- Native `request.json` and `readBody` does not use [unjs/destr](https://destr.unjs.io) anymore. You should always filter and sanitize data coming from user to avoid [prototype-poisoning](https://medium.com/intrinsic-blog/javascript-prototype-poisoning-vulnerabilities-in-the-wild-7bc15347c96).
|
|
95
|
+
|
|
96
|
+
## Cookie and Headers
|
|
97
|
+
|
|
98
|
+
> [!TIP]
|
|
99
|
+
H3 now natively uses standard web [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) for all utils.
|
|
100
|
+
|
|
101
|
+
Header values are always a plain `string` now (no `null` or `undefined` or `number` or `string[]`).
|
|
102
|
+
|
|
103
|
+
For the [`Set-Cookie`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) header, you can use [`headers.getSetCookie`](https://developer.mozilla.org/en-US/docs/Web/API/Headers/getSetCookie) that always returns a string array.
|
|
104
|
+
|
|
105
|
+
## Other Deprecations
|
|
106
|
+
|
|
107
|
+
H3 v2 deprecated some legacy and aliased utilities.
|
|
108
|
+
|
|
109
|
+
### App and router utils
|
|
110
|
+
|
|
111
|
+
- `createApp` / `createRouter`: Migrate to `new H3()`.
|
|
112
|
+
|
|
113
|
+
### Error utils
|
|
114
|
+
|
|
115
|
+
- `createError`/`H3Error`: Migrate to `HTTPError`
|
|
116
|
+
- `isError`: Migrate to `HTTPError.isError`
|
|
117
|
+
|
|
118
|
+
### Handler utils
|
|
119
|
+
|
|
120
|
+
- `eventHandler`/`defineEventHandler`: Migrate to `defineHandler` (you can also directly use a function!).
|
|
121
|
+
- `lazyEventHandler`: Migrate to `defineLazyEventHandler`.
|
|
122
|
+
- `isEventHandler`: (removed) Any function can be an event handler.
|
|
123
|
+
- `useBase`: Migrate to `withBase`.
|
|
124
|
+
- `defineRequestMiddleware` and `defineResponseMiddleware` removed.
|
|
125
|
+
|
|
126
|
+
### Request utils
|
|
127
|
+
|
|
128
|
+
- `getHeader` / `getRequestHeader`: Migrate to `event.req.headers.get(name)`.
|
|
129
|
+
- `getHeaders` / `getRequestHeaders`: Migrate to `Object.fromEntries(event.req.headers.entries())`.
|
|
130
|
+
- `getRequestPath`: Migrate to `event.url.pathname`.
|
|
131
|
+
- `getMethod`: Migrate to `event.req.method`.
|
|
132
|
+
|
|
133
|
+
> [!NOTE]
|
|
134
|
+
The following `H3Event` properties are deprecated in v2 and might be removed in a future version:
|
|
135
|
+
|
|
136
|
+
> - `event.path` → use `event.url.pathname + event.url.search`
|
|
137
|
+
> - `event.method` → use `event.req.method`
|
|
138
|
+
> - `event.headers` → use `event.req.headers`
|
|
139
|
+
> - `event.node` → use `event.runtime.node`
|
|
140
|
+
|
|
141
|
+
### Response utils
|
|
142
|
+
|
|
143
|
+
- `getResponseHeader` / `getResponseHeaders`: Migrate to `event.res.headers.get(name)`
|
|
144
|
+
- `setHeader` / `setResponseHeader` / `setHeaders` / `setResponseHeaders`: Migrate to `event.res.headers.set(name, value)`.
|
|
145
|
+
- `appendHeader` / `appendResponseHeader` / `appendResponseHeaders`: Migrate to `event.res.headers.append(name, value)`.
|
|
146
|
+
- `removeResponseHeader` / `clearResponseHeaders`: Migrate to `event.res.headers.delete(name)`
|
|
147
|
+
- `appendHeaders`: Migrate to `appendResponseHeaders`.
|
|
148
|
+
- `defaultContentType`: Migrate to `event.res.headers.set("content-type", type)`
|
|
149
|
+
- `getResponseStatus` / `getResponseStatusText` / `setResponseStatus`: Use `event.res.status` and `event.res.statusText`.
|
|
150
|
+
|
|
151
|
+
### Node.js utils
|
|
152
|
+
|
|
153
|
+
- `defineNodeListener`: Migrate to `defineNodeHandler`.
|
|
154
|
+
- `fromNodeMiddleware`: Migrate to `fromNodeHandler`.
|
|
155
|
+
- `toNodeListener`: Migrate to `toNodeHandler`.
|
|
156
|
+
- `createEvent`: (removed): Use Node.js adapter (`toNodeHandler(app)`).
|
|
157
|
+
- `fromNodeRequest`: (removed): Use Node.js adapter (`toNodeHandler(app)`).
|
|
158
|
+
- `promisifyNodeListener` (removed).
|
|
159
|
+
- `callNodeListener`: (removed).
|
|
160
|
+
|
|
161
|
+
### Web Utils
|
|
162
|
+
|
|
163
|
+
- `fromPlainHandler`: (removed) Migrate to Web API.
|
|
164
|
+
- `toPlainHandler`: (removed) Migrate to Web API.
|
|
165
|
+
- `fromPlainRequest` (removed) Migrate to Web API or use `mockEvent` util for testing.
|
|
166
|
+
- `callWithPlainRequest` (removed) Migrate to Web API.
|
|
167
|
+
- `fromWebRequest`: (removed) Migrate to Web API.
|
|
168
|
+
- `callWithWebRequest`: (removed).
|
|
169
|
+
|
|
170
|
+
### Body Utils
|
|
171
|
+
|
|
172
|
+
- `readRawBody`: Migrate to `event.req.text()` or `event.req.arrayBuffer()`.
|
|
173
|
+
- `getBodyStream` / `getRequestWebStream`: Migrate to `event.req.body`.
|
|
174
|
+
- `readFormData` / `readMultipartFormData` / `readFormDataBody`: Migrate to `event.req.formData()`.
|
|
175
|
+
|
|
176
|
+
### Other Utils
|
|
177
|
+
|
|
178
|
+
- `isStream`: Migrate to `instanceof ReadableStream`.
|
|
179
|
+
- `isWebResponse`: Migrate to `instanceof Response`.
|
|
180
|
+
- `splitCookiesString`: Use `splitSetCookieString` from [cookie-es](https://github.com/unjs/cookie-es).
|
|
181
|
+
- `MIMES`: (removed).
|
|
182
|
+
|
|
183
|
+
### Type Exports
|
|
184
|
+
|
|
185
|
+
> [!NOTE]
|
|
186
|
+
There might be more type changes.
|
|
187
|
+
|
|
188
|
+
- `App`: Migrate to `H3`.
|
|
189
|
+
- `AppOptions`: Migrate to `H3Config`.
|
|
190
|
+
- `_RequestMiddleware`: Migrate to `RequestMiddleware`.
|
|
191
|
+
- `_ResponseMiddleware`: Migrate to `ResponseMiddleware`.
|
|
192
|
+
- `NodeListener`: Migrate to `NodeHandler`.
|
|
193
|
+
- `TypedHeaders`: Migrate to `RequestHeaders` and `ResponseHeaders`.
|
|
194
|
+
- `HTTPHeaderName`: Migrate to `RequestHeaderName` and `ResponseHeaderName`.
|
|
195
|
+
- `H3Headers`: Migrate to native `Headers`.
|
|
196
|
+
- `H3Response`: Migrate to native `Response`.
|
|
197
|
+
- `MultiPartData`: Migrate to native `FormData`.
|
|
198
|
+
- `RouteNode`: Migrate to `RouterEntry`.
|
|
199
|
+
`CreateRouterOptions`: Migrate to `RouterOptions`.
|
|
200
|
+
Removed type exports: `WebEventContext`, `NodeEventContext`, `NodePromisifiedHandler`, `AppUse`, `Stack`, `InputLayer`, `InputStack`, `Layer`, `Matcher`, `PlainHandler`, `PlainRequest`, `PlainResponse`, `WebHandler`.
|