Skip to content

Commit

Permalink
Merge branch 'main' into pr/jerelmiller/795
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 committed Jul 7, 2024
2 parents 83f8776 + 2009428 commit a2d9b5b
Show file tree
Hide file tree
Showing 91 changed files with 2,349 additions and 2,986 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/autofix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ jobs:
node-version: 20
cache: "pnpm"
- run: pnpm install
- run: pnpm run lint:fix
- run: pnpm automd
- uses: autofix-ci/action@ea32e3a12414e6d3183163c3424a7d7a8631ad84
- run: pnpm run lint:fix
- uses: autofix-ci/action@2891949f3779a1cafafae1523058501de3d4e944
with:
commit-message: "chore: apply automated updates"
1 change: 0 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@ dist
lib
.nuxt
.output
docs/**/*.md
src/types/_headers.ts
2 changes: 1 addition & 1 deletion docs/.config/docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ landing:
target: "_blank"

# Ecosystem
- title: "UnJS ecosystem"
- title: "UnJS Ecosystem"
description: "Built on top of powerful UnJS ecosystem powering Nitro, Nuxt and more frameworks!"
icon: "i-mdi-umbrella-outline"
to: "https://unjs.io"
Expand Down
17 changes: 4 additions & 13 deletions docs/1.guide/1.index.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Create a new file `app.ts` (or `app.js`):

```ts [app.ts]
// Import h3 as npm dependency
import { createApp, createRouter, defineEventHandler } from "h3";
import { createApp, createRouter } from "h3";

// Create an app instance
export const app = createApp();
Expand All @@ -31,12 +31,9 @@ const router = createRouter();
app.use(router);

// Add a new route that matches GET requests to / path
router.get(
"/",
defineEventHandler((event) => {
return { message: "⚡️ Tadaa!" };
}),
);
router.get("/", () => {
return { message: "⚡️ Tadaa!" };
});
```

Now run the development server using [unjs/listhen](https://listhen.unjs.io):
Expand Down Expand Up @@ -68,12 +65,6 @@ const router = createRouter();
app.use(router);
```

Now it is time to add our first endpoint. In h3, request handlers can be defined using `defineEventHandler` or `eventHandler` helpers (they are aliases). Using wrappers, h3 can supercharge your code with better typehints and future compatibility.

```ts
defineEventHandler((event) => {});
```

What is beautiful in h3 is that all you have to do to make a response, is to simply return it! Responses can be simple string, JSON objects, data buffers, streams or standard [Web Response](https://developer.mozilla.org/en-US/docs/Web/API/Response/Response).

```ts
Expand Down
67 changes: 15 additions & 52 deletions docs/1.guide/2.app.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,53 +57,28 @@ const app = createApp({
You can register [event handlers](/guide/event-handler) to app instance using the `app.use`:

```js
import { defineEventHandler } from "h3";

app.use(
"/hello",
defineEventHandler((event) => {
return "Hello world!";
}),
);
app.use("/hello", () => "Hello world!");
```

This will register the event handler to the app instance and will be called for every request starting with the prefix `/hello`. This means that the event handler will be called for `/hello`, `/hello/world`, `/hello/123`, etc.

You can define multiple event handlers for the same route. h3 will try to to call them one by one in order of registration until one of them returns a response. This is called `stack runner`.

```js
app.use(
"/",
defineEventHandler((event) => {
return "First";
}),
);
app.use(
"/",
defineEventHandler((event) => {
return "Second";
}),
);
app.use("/", () => "First");
app.use("/", () => "Second");
```

In this example, the first event handler will be called for every request starting with `/hello` and the second one will never be called.

However, if you do not return a response from the first event handler, the second one will be called. This is useful to have a _middleware_ pattern.

```js
app.use(
"/",
defineEventHandler((event) => {
console.log("First");
// No response returned
}),
);
app.use(
"/",
defineEventHandler((event) => {
return "Second";
}),
);
app.use("/", () => {
console.log("First");
// No response returned
});
app.use("/", () => "Second");
```

If all handlers get called and no response is returned, h3 will end the request with 404 status response.
Expand All @@ -120,15 +95,9 @@ If all handlers get called and no response is returned, h3 will end the request
The method `use` accepts an optional `options` object as third argument:

```js
app.use(
"/hello",
defineEventHandler((event) => {
return "Hello world!";
}),
{
// Options
},
);
app.use("/hello", () => "Hello world!", {
// Options
});
```

### `matcher`
Expand All @@ -138,17 +107,11 @@ You can define a custom matcher function to have more advanced logic for matchin
For example, you can match only odd URLs, `/1`, `/3`, `/5`, etc.:

```js
app.use(
"/",
defineEventHandler((event) => {
return "Odd URLs only";
}),
{
match: (url) => {
return url.substr(1) % 2;
},
app.use("/", () => "Odd URLs only", {
match: (url) => {
return url.substr(1) % 2;
},
);
});
```

> [!WARNING]
Expand Down
37 changes: 12 additions & 25 deletions docs/1.guide/2.event-handler.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,18 @@ An event handler is a function that receive an `Event` instance and returns a re

## Defining event handlers

You can define event handlers using `defineEventHandler` or `eventHandler` utilities:

> [!NOTE]
> You can use `defineEventHandler` and `eventHandler` interchangeably. They are aliases. You can use the one you prefer but **stick to it** for consistency.
You can define typed event handlers using `defineEventHandler`:

```js
import { defineEventHandler } from "h3";

defineEventHandler((event) => {
return "Response";
});
defineEventHandler(() => "Response");
```

The callback function can be sync or async:

```js
defineEventHandler(async (event) => {
return "Response";
});
defineEventHandler(async () => "Response");
```

### Object Syntax
Expand All @@ -41,9 +34,7 @@ You can use an object syntax in `defineEventHandler` for more flexible options.
defineEventHandler({
onRequest: [],
onBeforeResponse: []
handler: (event) => {
return "Response";
},
handler: () => "Response",
})
```

Expand All @@ -64,15 +55,15 @@ Any of above values could also be wrapped in a [`Promise`](https://developer.moz
**Example:** Send HTML response:

```js
app.use(defineEventHandler(async (event) => "<h1>Hello world!</h1>"));
app.use(defineEventHandler(async () => "<h1>Hello world!</h1>"));
```

**Example:** Send JSON response:

```js
app.use(
"/api",
defineEventHandler(async (event) => ({ url: event.node.req.url })),
defineEventHandler(async (event) => event.path),
);
```

Expand All @@ -83,7 +74,7 @@ app.use(
defineEventHandler(async (event) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ url: event.node.req.url });
resolve(event.path);
}, 1000);
});
}),
Expand All @@ -104,7 +95,7 @@ app.use(
status: 400,
statusMessage: "Bad Request",
message: "Invalid user input",
data: { field: "email" }
data: { field: "email" },
});
}),
);
Expand Down Expand Up @@ -210,11 +201,7 @@ app.use(
console.log("Middleware 2");
}),
);
app.use(
defineEventHandler((event) => {
return "Response";
}),
);
app.use(defineEventHandler(() => "Response"));
```

You can define as much middleware as you need. They will be called in order of registration.
Expand All @@ -226,16 +213,16 @@ There are built-in utils to do this.!

### Converting from Node.js handlers

If you have a legacy request handler with `(req, res) => {}` syntax made for Node.js, you can use `fromNodeListener` to convert it to an h3 event handler.
If you have a legacy request handler with `(req, res) => {}` syntax made for Node.js, you can use `fromNodeHandler` to convert it to an h3 event handler.

```js [app.mjs]
import { createApp, fromNodeMiddleware } from "h3";
import { createApp, fromNodeHandler } from "h3";

import exampleMiddleware from "example-node-middleware";

export const app = createApp();

app.use(fromNodeListener(exampleMiddleware()));
app.use(fromNodeHandler(exampleMiddleware()));
```

> [!TIP]
Expand Down
72 changes: 7 additions & 65 deletions docs/1.guide/4.event.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,50 +13,32 @@ An event is passed through all the lifecycle hooks and composable utils to use i
**Example:**

```js
import { defineEventHandler, getQuery, readBody } from "h3";
import { getQuery, readJSONBody } from "h3";

app.use(defineEventHandler(async (event) => {
app.use(async (event) => {
// Log event. `.toString()` stringifies to a simple string like `[GET] /<path>`
console.log(`Request: ${event.toString()}`);

// Parse query params
const query = getQuery(event)
const query = getQuery(event);

// Try to read request body
const body = await readBody(event).catch(() => {})
const body = await readJSONBody(event).catch(() => {});

// Echo back request as response
return {
path: event.path,
method: event.method,
query,
body,
}
}));
};
});
```

## Properties

The main properties of an event are:

### `event.node`

The `event.node` allows you to access the native Node.js request and response. In runtimes other than Node.js/Bun, h3 makes a compatible shim using [unjs/unenv](https://unenv.unjs.io).

> [!IMPORTANT]
> Try to **avoid** depending on `event.node.*` context as much as you can and instead prefer h3 utils.
```js
defineEventHandler((event) => {
event.node.req; // Node.js HTTP Request
event.node.res; // Node.js HTTP Response
});
```

### `event.web?`

If available, an object with [`request`](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request) and [`url`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) properties to access native web request context.

### `event.method`

Access to the normalized (uppercase) request [method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods).
Expand All @@ -74,49 +56,9 @@ Access to the request path. (**Example:** `/test?test=123`)
Access to the normalized request [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers).

> [!TIP]
> You can alternatively use `getHeaders(event)` or `getHeader(event, name)` for a simplified interface.
> You can alternatively use `getRequestHeaders(event)` or `getRequestHeader(event, name)` for a simplified interface.
### `event.context`

The context is an object that contains arbitrary information about the request.
You can store your custom properties inside `event.context` to share across composable utils.

### `event.handled`

Specifies if response is already handled or not. Initially for each request it is `false`, and when a response is generated, it is set to `true`.

**Advanced:** If you manually handle the response, set it to `true` to tell h3 stop sending any responses.

## Methods

h3 provides a function to help you to create a response before the end of the request.

### `event.respondWith`

The `respondWith` method is used to create a response without ending the request.

You must craft a response using the [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response/Response) constructor.

> [!TIP]
> Prefer explicit `return` over `respondWith` as best practice.
> [!IMPORTANT]
> A `respondWith` call will **always** take precedence over the returned value, from current and next event handlers. If there is no returned value, the request will continue until the end of the stack runner.
**Example:**

```js
defineEventHandler(async (event) => {
await event.respondWith(new Response("Hello World"));
return "..."; // DOES NOT WORKS
});

app.use(
defineEventHandler(async (event) => {
await event.respondWith(new Response("Hello World"));
return "..."; // DOES NOT WORK
}),
);
```

With this example, the client will receive `Hello World`.
Loading

0 comments on commit a2d9b5b

Please sign in to comment.