├── .config └── automd.yaml ├── .github ├── CODEOWNERS ├── banner.svg ├── codecov.yml └── workflows │ ├── autofix.yml │ └── ci.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── MIGRATION.md ├── README.md ├── build.config.mjs ├── docs ├── .config │ └── docs.yaml ├── .docs │ └── public │ │ └── icon.svg ├── .gitignore ├── .npmrc ├── .partials │ └── beta.md ├── 1.guide │ ├── .navigation.yml │ ├── 0.index.md │ ├── 1.basics │ │ ├── .navigation.yml │ │ ├── 1.lifecycle.md │ │ ├── 2.routing.md │ │ ├── 3.middleware.md │ │ ├── 4.handler.md │ │ ├── 5.response.md │ │ └── 6.error.md │ ├── 900.api │ │ ├── .navigation.yml │ │ ├── 1.h3.md │ │ └── 2.h3event.md │ └── 901.advanced │ │ ├── .navigation.yml │ │ ├── 1.plugins.md │ │ ├── 2.websocket.md │ │ └── 9.nightly.md ├── 2.utils │ ├── 0.index.md │ ├── 1.request.md │ ├── 2.response.md │ ├── 3.cookie.md │ ├── 4.security.md │ ├── 5.proxy.md │ ├── 9.more.md │ └── 99.community.md ├── 4.examples │ ├── 0.index.md │ ├── handle-cookie.md │ ├── handle-session.md │ ├── serve-static-assets.md │ ├── stream-response.md │ └── validate-data.md ├── 5.migration │ └── 0.index.md ├── 99.blog │ ├── 1.v1.8.md │ ├── 2.v2-beta.md │ └── index.md ├── package.json ├── pnpm-lock.yaml └── pnpm-workspace.yaml ├── eslint.config.mjs ├── examples ├── auth.mjs ├── body.mjs ├── cookies.mjs ├── cors.mjs ├── errors.mjs ├── handler-fetch.mjs ├── handler-obj.mjs ├── headers.mjs ├── middleware.mjs ├── nested-router.mjs ├── package.json ├── plugin.mjs ├── query-params.mjs ├── redirect.mjs ├── response-types.mjs ├── router.mjs ├── server-sent-events.mjs ├── status.mjs ├── url-params.mjs ├── vite.mjs └── websocket.mjs ├── package.json ├── playground ├── package.json └── server.mjs ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── renovate.json ├── src ├── _deprecated.ts ├── _entries │ ├── bun.ts │ ├── cloudflare.ts │ ├── deno.ts │ ├── generic.ts │ ├── node.ts │ └── service-worker.ts ├── adapters.ts ├── error.ts ├── event.ts ├── h3.ts ├── handler.ts ├── index.ts ├── middleware.ts ├── response.ts ├── types │ ├── _utils.ts │ ├── context.ts │ ├── h3.ts │ └── handler.ts └── utils │ ├── auth.ts │ ├── base.ts │ ├── body.ts │ ├── cache.ts │ ├── cookie.ts │ ├── cors.ts │ ├── event-stream.ts │ ├── event.ts │ ├── fingerprint.ts │ ├── internal │ ├── body.ts │ ├── cors.ts │ ├── encoding.ts │ ├── event-stream.ts │ ├── iron-crypto.ts │ ├── iterable.ts │ ├── obj.ts │ ├── object.ts │ ├── path.ts │ ├── proxy.ts │ ├── query.ts │ ├── session.ts │ ├── standard-schema.ts │ └── validate.ts │ ├── middleware.ts │ ├── proxy.ts │ ├── request.ts │ ├── response.ts │ ├── sanitize.ts │ ├── session.ts │ ├── static.ts │ └── ws.ts ├── test ├── _setup.ts ├── app.test.ts ├── assets │ ├── dummy.pdf │ └── sample.json ├── auth.test.ts ├── bench │ ├── bench.impl.ts │ ├── bench.test.ts │ ├── bench.ts │ ├── bundle.test.ts │ ├── input.ts │ └── session.ts ├── body.test.ts ├── cookies.test.ts ├── errors.test.ts ├── event.test.ts ├── handler.test.ts ├── hooks.test.ts ├── integrations.test.ts ├── iterable.test.ts ├── lazy.test.ts ├── middleware.test.ts ├── proxy.test.ts ├── router.test.ts ├── session.test.ts ├── sse.test.ts ├── static.test.ts ├── status.test.ts ├── unit │ ├── cors.test.ts │ ├── encoding.test.ts │ ├── h3.test.ts │ ├── iron-crypto.test.ts │ ├── iterable.test.ts │ ├── package.test.ts │ ├── query.test.ts │ ├── sse.test.ts │ └── types.test-d.ts ├── utils.test.ts ├── validate.test.ts └── ws.test.ts ├── tsconfig.json ├── typos.toml └── vitest.config.mjs /.config/automd.yaml: -------------------------------------------------------------------------------- 1 | input: ["README.md", "docs/**/*.md"] 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @pi0 2 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | threshold: 5% 6 | -------------------------------------------------------------------------------- /.github/workflows/autofix.yml: -------------------------------------------------------------------------------- 1 | name: autofix.ci # needed to securely identify the workflow 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: ["main"] 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | autofix: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - run: npm i -fg corepack && corepack enable 17 | - uses: actions/setup-node@v4 18 | with: 19 | node-version: 22 20 | cache: "pnpm" 21 | - run: pnpm install 22 | - run: pnpm automd 23 | - run: pnpm run lint:fix 24 | - uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef 25 | with: 26 | commit-message: "chore: apply automated updates" 27 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | permissions: 12 | id-token: write 13 | 14 | jobs: 15 | ci: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | node: [20, 22] 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 24 | - run: npm i -fg corepack && corepack enable 25 | - uses: actions/setup-node@v4 26 | with: 27 | node-version: ${{ matrix.node }} 28 | cache: "pnpm" 29 | - run: pnpm install 30 | - run: pnpm lint 31 | - run: pnpm build 32 | - run: pnpm vitest --coverage && rm -rf coverage/tmp 33 | - uses: codecov/codecov-action@v5 34 | - name: nightly release 35 | if: | 36 | github.event_name == 'push' && 37 | matrix.node == 22 && 38 | !contains(github.event.head_commit.message, '[skip-release]') && 39 | !startsWith(github.event.head_commit.message, 'docs') 40 | run: | 41 | echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> ~/.npmrc && 42 | pnpm changelogen --canary nightly --publish --publishTag beta 43 | env: 44 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 45 | NPM_CONFIG_PROVENANCE: true 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | *.log* 4 | dist 5 | coverage 6 | .profile 7 | .idea 8 | .eslintcache 9 | tsconfig.vitest-temp.json 10 | .DS_Store 11 | tsconfig.tsbuildinfo 12 | *.tmp* 13 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ignore-workspace-root-check=true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | lib 3 | .nuxt 4 | .output 5 | src/types/_headers.ts 6 | docs/1.guide/6.websocket.md 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Pooya Parsa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # H3 2 | 3 | 4 | 5 | [![npm version](https://img.shields.io/npm/v/h3)](https://npmjs.com/package/h3) 6 | [![npm downloads](https://img.shields.io/npm/dm/h3)](https://npm.chart.dev/h3) 7 | 8 | 9 | 10 | H3 (pronounced as /eɪtʃθriː/, like h-3) is a minimal h(ttp) framework built for high performance and portability. 11 | 12 | > [!NOTE] 13 | > You are on the v2 development branch. Check out [v1 branch](https://github.com/h3js/h3/tree/v1) and [v1 documentation](https://v1.h3.dev/) for current v1. 14 | 15 | 👉 [Documentation](https://h3.dev) 16 | 17 | ## Contribution 18 | 19 |
20 | Local development 21 | 22 | - Clone this repository 23 | - Install the latest LTS version of [Node.js](https://nodejs.org/en/) 24 | - Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable` 25 | - Install dependencies using `pnpm install` 26 | - Run tests using `pnpm dev` or `pnpm test` 27 | 28 |
29 | 30 | 31 | 32 | ## License 33 | 34 | 35 | 36 | Published under the [MIT](https://github.com/h3js/h3/blob/main/LICENSE) license. 37 | Made by [@pi0](https://github.com/pi0) and [community](https://github.com/h3js/h3/graphs/contributors) 💛 38 |

39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | --- 48 | 49 | _🤖 auto updated with [automd](https://automd.unjs.io)_ 50 | 51 | 52 | -------------------------------------------------------------------------------- /build.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from "obuild/config"; 2 | 3 | const { exports } = await import("./package.json", { with: { type: "json" } }); 4 | 5 | export default defineBuildConfig({ 6 | entries: [ 7 | { 8 | type: "bundle", 9 | input: [...inferExports(exports)], 10 | }, 11 | ], 12 | hooks: { 13 | rolldownOutput(outConcig) { 14 | outConcig.chunkFileNames = "h3.mjs"; 15 | }, 16 | }, 17 | }); 18 | 19 | function inferExports(exports) { 20 | const entries = new Set(); 21 | for (const value of Object.values(exports)) { 22 | if (typeof value === "string") { 23 | if (value.endsWith(".mjs")) { 24 | entries.add(value.replace("./dist", "./src").replace(".mjs", ".ts")); 25 | } 26 | } else if (typeof value === "object" && value !== null) { 27 | entries.add(...inferExports(value)); 28 | } 29 | } 30 | return entries; 31 | } 32 | -------------------------------------------------------------------------------- /docs/.config/docs.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://unpkg.com/undocs/schema/config.json 2 | name: H3 3 | url: https://h3.dev 4 | shortDescription: Universal, Tiny, and Fast Servers 5 | description: H(TTP) server framework built on top of web standards for high performance and composability. 6 | 7 | themeColor: amber 8 | automd: true 9 | 10 | redirects: 11 | "/guide/event-handler": "/guide/handler" 12 | "/guide/router": "/guide/routing" 13 | "/adapters/**": "/guide" 14 | 15 | github: h3js/h3 16 | 17 | socials: 18 | discord: "https://discord.h3.dev" 19 | 20 | sponsors: 21 | api: https://sponsors.pi0.io/sponsors.json 22 | 23 | landing: 24 | heroLinks: 25 | playOnline: 26 | label: "Try Online" 27 | icon: "simple-icons:stackblitz" 28 | to: "https://stackblitz.com/github/h3js/h3/tree/main/playground?file=server.mjs" 29 | heroCode: 30 | lang: "js" 31 | title: "server.mjs" 32 | content: | 33 | import { H3, serve } from "h3" 34 | 35 | const app = new H3().get("/", () => "⚡️ Tadaa!") 36 | 37 | serve(app) 38 | contributors: true 39 | features: 40 | - title: "Runtime Agnostic" 41 | description: "Seamlessly works with any JavaScript runtime, including [Deno](https://deno.com/), [Bun](https://bun.sh/), [Node.js](https://nodejs.org), Workers, and more. Based on Web Standards and powered by [Srvx](https://srvx.h3.dev)." 42 | icon: "clarity:success-standard-line" 43 | 44 | - title: "Tiny, Composable and Fast" 45 | description: "Small core, with low latency and minimal bundle impact. Designed with composability and maximum compatibility in mind." 46 | icon: "devicon-plain:fastapi" 47 | 48 | - title: "Ergonomic and Safe API" 49 | description: "Carefully crafted, ergonomic, and strongly-typed APIs with dozens of tree-shakeable built-in utilities." 50 | icon: "hugeicons:happy" 51 | -------------------------------------------------------------------------------- /docs/.docs/public/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .nuxt 3 | .data 4 | .output 5 | dist 6 | -------------------------------------------------------------------------------- /docs/.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | -------------------------------------------------------------------------------- /docs/.partials/beta.md: -------------------------------------------------------------------------------- 1 | > [!IMPORTANT] 2 | > You are currently reading H3 version 2 ([beta](/blog/v2-beta)) documentation. See [v1.h3.dev](https://v1.h3.dev/) for older docs. 3 | -------------------------------------------------------------------------------- /docs/1.guide/.navigation.yml: -------------------------------------------------------------------------------- 1 | icon: i-ph:book-open-duotone 2 | title: Guide 3 | -------------------------------------------------------------------------------- /docs/1.guide/0.index.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: pixel:play 3 | --- 4 | 5 | # Getting Started 6 | 7 | > Get started with H3. 8 | 9 | 10 | 11 | > [!IMPORTANT] 12 | > You are currently reading H3 version 2 ([beta](/blog/v2-beta)) documentation. See [v1.h3.dev](https://v1.h3.dev/) for older docs. 13 | 14 | 15 | 16 | ## Overview 17 | 18 | ⚡ H3 (short for H(TTP), pronounced as /eɪtʃθriː/, like h-3) is a lightweight, fast, and composable server framework for modern JavaScript runtimes. It is based on web standard primitives such as [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request), [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response), [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL), and [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers). You can integrate H3 with any compatible runtime or [mount](/guide/api/h3#h3mount) other web-compatible handlers to H3 with almost no added latency. 19 | 20 | H3 is designed to be extendable and composable. Instead of providing one big core, you start with a lightweight [H3 instance](/guide/api/h3) and then import built-in, tree-shakable [utilities](/utils) or bring your own for more functionality. 21 | Composable utilities has several advantages: 22 | 23 | - The server only includes used code and runs them exactly where is needed. 24 | - Application size can scale better. Usage of utilities is explicit and clean, with less global impact. 25 | - H3 is minimally opinionated and won't limit your choices. 26 | 27 | All utilities, share an [H3Event](/guide/api/h3event) context. 28 | 29 | :read-more{to="/utils" title="built-in H3 utilities"} 30 | 31 | ## Quick Start 32 | 33 | > [!TIP] 34 | > You try H3 online [on ⚡️ Stackblitz ](https://stackblitz.com/github/h3js/h3/tree/main/playground?file=server.mjs). 35 | 36 | Install `h3` as a dependency: 37 | 38 | :pm-install{name="h3@beta"} 39 | 40 | Create a new file for server entry: 41 | 42 | ```ts [server.mjs] 43 | import { H3, serve } from "h3"; 44 | 45 | const app = new H3().get("/", (event) => "⚡️ Tadaa!"); 46 | 47 | serve(app, { port: 3000 }); 48 | ``` 49 | 50 | Then, run the server using your favorite runtime: 51 | 52 | ::code-group 53 | 54 | ```bash [node] 55 | node --watch ./server.mjs 56 | ``` 57 | 58 | ```bash [deno] 59 | deno run -A --watch ./server.mjs 60 | ``` 61 | 62 | ```bash [bun] 63 | bun run --watch server.mjs 64 | ``` 65 | 66 | :: 67 | 68 | And tadaa! We have a web server running locally. 69 | 70 | ### What Happened? 71 | 72 | Okay, let's now break down our hello world example. 73 | 74 | We first created an [H3](/guide/api/h3) app instance using `new H3()`: 75 | 76 | ```ts 77 | const app = new H3(); 78 | ``` 79 | 80 | [H3](/guide/api/h3) is a tiny class capable of [matching routes](/guide/basics/routing), [generating responses](/guide/basics/response) and calling [middleware](/guide/basics/middleware) and [global hooks](/guide/api/h3#global-hooks). 81 | 82 | Then we add a route for handling HTTP GET requests to `/` path. 83 | 84 | ```ts 85 | app.get("/", (event) => { 86 | return { message: "⚡️ Tadaa!" }; 87 | }); 88 | ``` 89 | 90 | :read-more{title="Routing" to="/guide/basics/routing"} 91 | 92 | We simply returned an object. H3 automatically [converts](/guide/basics/response#response-types) values into web responses. 93 | 94 | :read-more{title="Sending Response" to="/guide/basics/response"} 95 | 96 | Finally, we use `serve` method to start the server listener. Using `serve` method you can easily start an H3 server in various runtimes. 97 | 98 | ```js 99 | serve(app, { port: 3000 }); 100 | ``` 101 | 102 | > [!TIP] 103 | > The `serve` method is powered by [💥 Srvx](https://srvx.h3.dev/), a runtime-agnostic universal server listener based on web standards that works seamlessly with [Deno](https://deno.com/), [Node.js](https://nodejs.org/) and [Bun](https://bun.sh/). 104 | 105 | We also have [`app.fetch`](/guide/api/h3#h3fetch) which can be directly used to run H3 apps in any web-compatible runtime or even directly called for testing purposes. 106 | 107 | :read-more{to="/guide/api/h3#h3fetch" title="H3.fetch"} 108 | 109 | ```js 110 | import { H3, serve } from "h3"; 111 | 112 | const app = new H3().get("/", () => "⚡️ Tadaa!"); 113 | 114 | // Test without listening 115 | const response = await app.fetch("/"); 116 | console.log(await response.text()); 117 | ``` 118 | 119 | You can directly import `h3` library from CDN alternatively. This method can be used for Bun, Deno and other runtimes such as Cloudflare Workers. 120 | 121 | ```js 122 | import { H3 } from "https://esm.sh/h3@beta"; 123 | 124 | const app = new H3().get("/", () => "⚡️ Tadaa!"); 125 | 126 | export const fetch = app.fetch; 127 | ``` 128 | 129 | 132 | -------------------------------------------------------------------------------- /docs/1.guide/1.basics/.navigation.yml: -------------------------------------------------------------------------------- 1 | icon: ph:book-open-duotone 2 | title: Guide 3 | -------------------------------------------------------------------------------- /docs/1.guide/1.basics/1.lifecycle.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: icon-park-outline:handle-round 3 | --- 4 | 5 | # Request Lifecycle 6 | 7 | > H3 dispatches incoming web requests to final web responses. 8 | 9 | Below is an overview of what happens in a H3 server from when an HTTP request arrives until a response is generated. 10 | 11 | ## 1. Incoming Request 12 | 13 | When An HTTP request is made by Browser or [fetch()](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), server fetch handler receives a [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. 14 | 15 | ```mermaid 16 | %%{init: {'theme':'neutral'}}%% 17 | flowchart LR 18 | A1["fetch(request)"] --> A2["server.fetch(request)"] 19 | 20 | click A2 "/guide/api/h3#h3fetch" 21 | ``` 22 | 23 | > [!TIP] 24 | > ​[💥 Srvx](https://srvx.h3.dev) provides unified `server.fetch` interface and adds [Node.js compatibility](https://srvx.h3.dev/guide/node). 25 | 26 | ## 2. Accept Request 27 | 28 | H3 Initializes an [`H3Event`](/guide/api/h3event) instance from incoming request, calls [`onRequest`](/guide/api/h3#global-hooks) global hook and finally [`H3.handler`](/guide/api/h3#h3handler) with the initialized event. 29 | 30 | ```mermaid 31 | %%{init: {'theme':'neutral'}}%% 32 | flowchart LR 33 | B1["new H3Event(request)"] --> B2["onRequest(event)"] --> B3["h3.handler(event)"] 34 | 35 | click B1 "/guide/api/h3event" 36 | click B2 "/guide/api/h3#global-hooks" 37 | click B3 "/guide/api/h3#apphandler" 38 | ``` 39 | 40 | ## 3. Dispatch Request 41 | 42 | H3 [matches route](/guide/basics/routing) based on `request.url` and `request.method`, calls global [middleware](/guide/basics/middleware) and finally matched route handler function with event. 43 | 44 | ```mermaid 45 | %%{init: {'theme':'neutral'}}%% 46 | flowchart LR 47 | C1["[match route]"] --> C2["middlewareA(middlewareB(route.handler(event)))"] 48 | 49 | click C1 "/guide/basics/routing" 50 | click C2 "/guide/basics/middleware" 51 | ``` 52 | 53 | > [!TIP] 54 | > 🚀 Internally, H3 uses srvx `FastURL` instead of `new URL(req.url).pathname`. 55 | 56 | ## 4. Send Response 57 | 58 | H3 [converts](/guide/basics/response#response-types) returned value and [prepared headers](/guide/basics/response#preparing-response) into a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response), calls [`onResponse`](/guide/api/h3#global-hooks) global hook and finally returns response back to the server fetch handler. 59 | 60 | ```mermaid 61 | %%{init: {'theme':'neutral'}}%% 62 | flowchart LR 63 | D1["Returned Value => Response"] --> D2["onResponse(response)"] --> D3["Response"] 64 | 65 | click D1 "/guide/basics/response" 66 | click D2 "/guide/api/h3#global-hooks" 67 | ``` 68 | -------------------------------------------------------------------------------- /docs/1.guide/1.basics/2.routing.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: solar:routing-bold 3 | --- 4 | 5 | # Routing 6 | 7 | > Each request is matched to one (most specific) route handler. 8 | 9 | ## Adding Routes 10 | 11 | You can register route [handlers](/guide/basics/handler) to [H3 instance](/guide/api/h3) using [`H3.on`](/guide/api/h3#h3on), [`H3.[method]`](/guide/api/h3#h3method), or [`H3.all`](/guide/api/h3#h3all). 12 | 13 | > [!TIP] 14 | > Router is powered by [🌳 Rou3](https://github.com/h3js/rou3), an ultra-fast and tiny route matcher engine. 15 | 16 | **Example:** Register a route to match requests to the `/hello` endpoint with HTTP **GET** method. 17 | 18 | - Using [`H3.[method]`](/guide/api/h3#h3method) 19 | 20 | ```js 21 | app.get("/hello", () => "Hello world!"); 22 | ``` 23 | 24 | - Using [`H3.on`](/guide/api/h3#h3on) 25 | 26 | ```js 27 | app.on("GET", "/hello", () => "Hello world!"); 28 | ``` 29 | 30 | You can register multiple event handlers for the same route with different methods: 31 | 32 | ```js 33 | app 34 | .get("/hello", () => "GET Hello world!") 35 | .post("/hello", () => "POST Hello world!") 36 | .any("/hello", () => "Any other method!"); 37 | ``` 38 | 39 | You can also use [`H3.all`](/guide/api/h3#h3all) method to register a route accepting any HTTP method: 40 | 41 | ```js 42 | app.all("/hello", (event) => `This is a ${event.req.method} request!`); 43 | ``` 44 | 45 | ## Dynamic Routes 46 | 47 | You can define dynamic route parameters using `:` prefix: 48 | 49 | ```js 50 | // [GET] /hello/Bob => "Hello, Bob!" 51 | app.get("/hello/:name", (event) => { 52 | return `Hello, ${event.context.params.name}!`; 53 | }); 54 | ``` 55 | 56 | Instead of named parameters, you can use `*` for unnamed **optional** parameters: 57 | 58 | ```js 59 | app.get("/hello/*", (event) => `Hello!`); 60 | ``` 61 | 62 | ## Wildcard Routes 63 | 64 | Adding `/hello/:name` route will match `/hello/world` or `/hello/123`. But it will not match `/hello/foo/bar`. 65 | When you need to match multiple levels of sub routes, you can use `**` prefix: 66 | 67 | ```js 68 | app.get("/hello/**", (event) => `Hello ${event.context.params._}!`); 69 | ``` 70 | 71 | This will match `/hello`, `/hello/world`, `/hello/123`, `/hello/world/123`, etc. 72 | 73 | > [!NOTE] 74 | > Param `_` will store the full wildcard content as a single string. 75 | -------------------------------------------------------------------------------- /docs/1.guide/1.basics/3.middleware.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: mdi:middleware-outline 3 | --- 4 | 5 | # Middleware 6 | 7 | > Intercept request, response and errors using H3 middleware. 8 | 9 | > [!IMPORTANT] 10 | > We recommend using composable utilities whenever possible. Global middleware can complicate application logic, making it less predictable and harder to understand. 11 | 12 | Global middleware run on each request before route handler and act as wrappers to intercept request, response and errors. 13 | 14 | :read-more{to="/guide/basics/lifecycle#\_3-dispatch-request" title="Request Lifecycle"} 15 | 16 | You can register global middleware to [app instance](/guide/api/h3) using the [`H3.use`](/guide/api/h3#h3use). 17 | 18 | **Example:** Register a global middleware that logs every request. 19 | 20 | ```js 21 | app.use((event) => { 22 | console.log(event); 23 | }); 24 | ``` 25 | 26 | **Example:** Register a global middleware that matches certain requests. 27 | 28 | ```js 29 | app.use( 30 | "/blog/**", 31 | (event, next) => { 32 | console.log("[alert] POST request on /blog paths!"); 33 | }, 34 | { 35 | method: "POST", 36 | // match: (event) => event.req.method === "POST", 37 | }, 38 | ); 39 | ``` 40 | 41 | You can register middleware with `next` argument to intercept return values of next middleware and handler. 42 | 43 | ```js 44 | app.use(async (event, next) => { 45 | const rawBody = await next(); 46 | // [intercept response] 47 | return rawBody; 48 | }); 49 | ``` 50 | 51 | Example below, always responsds with `Middleware 1`. 52 | 53 | ```js 54 | app 55 | .use(() => "Middleware 1") 56 | .use(() => "Middleware 2") 57 | .get("/", "Hello"); 58 | ``` 59 | 60 | > [!IMPORTANT] 61 | > If middleware returns a value other than `undefined` or the result of `next()`, it immediately intercepts request handling and sends a response. 62 | 63 | When adding routes, you can register middleware that only run with them. 64 | 65 | ```js 66 | import { basicAuth } from "h3"; 67 | 68 | app.get( 69 | "/secret", 70 | (event) => { 71 | /* ... */ 72 | }, 73 | { 74 | middleware: [basicAuth({ password: "test" })], 75 | }, 76 | ); 77 | ``` 78 | 79 | For convenience, H3 provides middleware factory functions `onRequest`, `onResponse`, and `onError`: 80 | 81 | ```js 82 | import { onRequest, onResponse, onError } from "h3"; 83 | 84 | app.use( 85 | onRequest((event) => { 86 | console.log(`[${event.req.method}] ${event.url.pathname}`); 87 | }), 88 | ); 89 | 90 | app.use( 91 | onResponse((response, event) => { 92 | console.log(`[${event.req.method}] ${event.url.pathname} ~>`, body); 93 | }), 94 | ); 95 | 96 | app.use( 97 | onError((error, event) => { 98 | console.log( 99 | `[${event.req.method}] ${event.url.pathname} !! ${error.message}`, 100 | ); 101 | }), 102 | ); 103 | ``` 104 | -------------------------------------------------------------------------------- /docs/1.guide/1.basics/4.handler.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: mdi:function 3 | --- 4 | 5 | # Event Handlers 6 | 7 | > An event handler is a function that receives an H3Event and returns a response. 8 | 9 | You can define typed event handlers using `defineHandler`. 10 | 11 | ```js 12 | import { H3, defineHandler } from "h3"; 13 | 14 | const app = new H3(); 15 | 16 | const handler = defineHandler((event) => "Response"); 17 | 18 | app.get("/", handler); 19 | ``` 20 | 21 | > [!NOTE] 22 | > Using `defineHandler` is optional. 23 | > You can instead, simply use a function that accepts an [`H3Event`](/guide/api/h3event) and returns a response. 24 | 25 | The callback function can be sync or async: 26 | 27 | ```js 28 | defineHandler(async (event) => "Response"); 29 | ``` 30 | 31 | You can optionally register some [middleware](/guide/basics/middleware) to run with event handler to intercept request, response or errors. 32 | 33 | ```js 34 | import { basicAuth } from "h3"; 35 | 36 | defineHandler({ 37 | middleware: [basicAuth({ password: "test" })], 38 | handler: (event) => "Hi!", 39 | }); 40 | ``` 41 | 42 | :read-more{to="/guide/basics/response" title="Response Handling"} 43 | 44 | :read-more{to="/guide/api/h3event" } 45 | 46 | ## Handler `.fetch` 47 | 48 | Event handlers defined with `defineHandler`, can act as a web handler without even using [H3](/guide/api/h3) class. 49 | 50 | ```js 51 | const handler = defineHandler(async (event) => `Request: ${event.req.url}`); 52 | 53 | const response = await handler.fetch("http://localhost/"); 54 | console.log(response, await response.text()); 55 | ``` 56 | 57 | ## Lazy Handlers 58 | 59 | You can define lazy event handlers using `defineLazyEventHandler`. This allow you to define some one-time logic that will be executed only once when the first request matching the route is received. 60 | 61 | A lazy event handler must return an event handler. 62 | 63 | ```js 64 | import { defineLazyEventHandler } from "h3"; 65 | 66 | defineLazyEventHandler(async () => { 67 | await initSomething(); // Will be executed only once 68 | return (event) => { 69 | return "Response"; 70 | }; 71 | }); 72 | ``` 73 | 74 | This is useful to define some one-time logic such as configuration, class initialization, heavy computation, etc. 75 | 76 | Another use-case is lazy loading route chunks: 77 | 78 | ```js [app.mjs] 79 | import { H3, defineLazyEventHandler } from "h3"; 80 | 81 | const app = new H3(); 82 | 83 | app.all( 84 | "/route", 85 | defineLazyEventHandler(() => 86 | import("./route.mjs").then((mod) => mod.default), 87 | ), 88 | ); 89 | ``` 90 | 91 | ```js [route.mjs] 92 | import { defineHandler } from "h3"; 93 | 94 | export default defineHandler((event) => "Hello!"); 95 | ``` 96 | 97 | ## Converting to Handler 98 | 99 | There are situations that you might want to convert an event handler or utility made for Node.js or another framework to H3. 100 | There are built-in utils to do this. 101 | 102 | ### From Web Handlers 103 | 104 | Request handlers with [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) => [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) signuture can be converted into H3 event handlers using `fromWebHandler` utility or [H3.mount](/guide/api/h3#h3mount). 105 | 106 | ```js 107 | import { H3, fromWebHandler } from "h3"; 108 | 109 | export const app = new H3(); 110 | 111 | const webHandler = (request) => new Response("👋 Hello!")); 112 | 113 | // Using fromWebHandler utiliy 114 | app.all("/web", fromWebHandler(webHandler)); 115 | 116 | // Using simple wrapper 117 | app.all("/web", event => webHandler(event.req)); 118 | 119 | // Using app.mount 120 | app.mount("/web", webHandler) 121 | ``` 122 | 123 | ### From Node.js Handlers 124 | 125 | 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. 126 | 127 | > [!IMPORTANT] 128 | > Node.js event handlers can only run within Node.js server runtime! 129 | 130 | ```js 131 | import { H3, fromNodeHandler } from "h3"; 132 | 133 | // Force using Node.js compatibility (also works with Bun and Deno) 134 | import { serve } from "h3/node"; 135 | 136 | export const app = new H3(); 137 | 138 | const nodeHandler = (req, res) => { 139 | res.end("Node handlers work!"); 140 | }; 141 | 142 | app.get("/web", fromNodeHandler(nodeHandler)); 143 | ``` 144 | -------------------------------------------------------------------------------- /docs/1.guide/1.basics/6.error.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: tabler:error-404 3 | --- 4 | 5 | # Error Handling 6 | 7 | > Send errors by throwing an HTTPError. 8 | 9 | H3 captures all possible errors during [request lifecycle](/guide/basics/lifecycle). 10 | 11 | ## `HTTPError` 12 | 13 | You can create and throw HTTP errors using `HTTPError` with different syntaxes. 14 | 15 | ```js 16 | import { HTTPError } from "h3"; 17 | 18 | app.get("/error", (event) => { 19 | // Using message and details 20 | throw new HTTPError("Invalid user input", { status: 400 }); 21 | 22 | // Using HTTPError.status(code) 23 | throw HTTPError.status(400, "Bad Request"); 24 | 25 | // Using single pbject 26 | throw new HTTPError({ 27 | status: 400, 28 | statusText: "Bad Request", 29 | message: "Invalid user input", 30 | data: { field: "email" }, 31 | body: { date: new Date().toJSON() }, 32 | headers: {}, 33 | }); 34 | }); 35 | ``` 36 | 37 | This will end the request with `400 - Bad Request` status code and the following JSON response: 38 | 39 | ```json 40 | { 41 | "date": "2025-06-05T04:20:00.0Z", 42 | "status": 400, 43 | "statusText": "Bad Request", 44 | "message": "Invalid user input", 45 | "data": { 46 | "field": "email" 47 | } 48 | } 49 | ``` 50 | 51 | ### `HTTPError` Fields 52 | 53 | - `status`: HTTP status code in the range 200–599. 54 | - `statusText`: HTTP status text to be sent in the response header. 55 | - `message`: Error message to be included in the JSON body. 56 | - `data`: Additional data to be attached under the `data` key in the error JSON body. 57 | - `body`: Additional top-level properties to be attached in the error JSON body. 58 | - `headers`: Additional HTTP headers to be sent in the error response. 59 | - `cause`: The original error object that caused this error, useful for tracing and debugging. 60 | - `unhandled`: Indicates whether the error was thrown for unknown reasons. See [Unhandled Errors](#unhandled-errors). 61 | 62 | > [!IMPORTANT] 63 | > Error `statusText` should be short (max 512 to 1024 characters) and only include tab, spaces or visible ASCII characters and extended characters (byte value 128–255). Prefer `message` in JSON body for extended message. 64 | 65 | ## Unhandled Errors 66 | 67 | Any error that occurs during calling [request lifecycle](/guide/basics/lifecycle) without using `HTTPError` will be processed as an _unhandled_ error. 68 | 69 | ```js 70 | app.get("/error", (event) => { 71 | // This will cause an unhandled error. 72 | throw new Error("Something went wrong"); 73 | }); 74 | ``` 75 | 76 | > [!TIP] 77 | > For enhanced security, H3 hides certain fields of unhandled errors (`data`, `body`, `stack` and `message`) in JSON response. 78 | 79 | ## Catching Errors 80 | 81 | Using global [`onError`](/guide/api/h3#global-hooks) hook: 82 | 83 | ```js 84 | import { H3, onError } from "h3"; 85 | 86 | // Globally handling errors 87 | const app = new H3({ 88 | onError: (error) => { 89 | console.error(error); 90 | }, 91 | }); 92 | ``` 93 | 94 | Using [`onError` middleware](/guide/basics/middleware) to catch errors. 95 | 96 | ```js 97 | import { onError } from "h3"; 98 | 99 | // Handling errors using middleware 100 | app.use( 101 | onError(event, (event, error) => { 102 | console.error(error); 103 | }), 104 | ); 105 | ``` 106 | 107 | > [!TIP] 108 | > When using nested apps, global hooks of sub-apps will not be called. Therefore it is better to use `onError` middleware. 109 | -------------------------------------------------------------------------------- /docs/1.guide/900.api/.navigation.yml: -------------------------------------------------------------------------------- 1 | icon: material-symbols-light:api-rounded 2 | title: API 3 | -------------------------------------------------------------------------------- /docs/1.guide/900.api/1.h3.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: material-symbols:bolt-rounded 3 | --- 4 | 5 | # H3 6 | 7 | > H3 class is the core of server. 8 | 9 | You can create a new H3 app instance using `new H3()`: 10 | 11 | ```js 12 | import { H3 } from "h3"; 13 | 14 | const app = new H3({ 15 | /* optional config */ 16 | }); 17 | ``` 18 | 19 | ## `H3` Methods 20 | 21 | ### `H3.fetch` 22 | 23 | A [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)-compatible function allowing to fetch app routes. 24 | 25 | - Input can be a relative path, [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL), or [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request). 26 | - Returned value is a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) promise. 27 | 28 | ```ts 29 | const response = await app.fetch("/"); 30 | console.log(response, await response.text()); 31 | ``` 32 | 33 | ### `H3.on` 34 | 35 | Register route handler for specific HTTP method. 36 | 37 | ```js 38 | const app = new H3().on("GET", "/", () => "OK"); 39 | ``` 40 | 41 | :read-more{to="/guide/basics/routing" title="Routing"} 42 | 43 | ### `H3.[method]` 44 | 45 | Register route handler for specific HTTP method (shortcut for `app.on(method, ...)`). 46 | 47 | ```js 48 | const app = new H3().get("/", () => "OK"); 49 | ``` 50 | 51 | ### `H3.all` 52 | 53 | Register route handler for all HTTP methods. 54 | 55 | ```js 56 | const app = new H3().all("/", () => "OK"); 57 | ``` 58 | 59 | ### `H3.use` 60 | 61 | Register a global [middleware](/guide/basics/middleware). 62 | 63 | ```js 64 | const app = new H3() 65 | .use((event) => { 66 | console.log(`request: ${event.req.url}`); 67 | }) 68 | .all("/", () => "OK"); 69 | ``` 70 | 71 | :read-more{to="/guide/basics/middleware" title="Middleware"} 72 | 73 | ### `H3.register` 74 | 75 | Register a H3 plugin to extend app. 76 | 77 | :read-more{to="/guide/advanced/plugins" title="Plugins"} 78 | 79 | ### `H3.handler` 80 | 81 | An H3 [event handler](/guide/basics/handler) useful to compose multiple H3 app instances. 82 | 83 | **Example:** Nested apps. 84 | 85 | ```js 86 | import { H3, serve, redirect, withBase } from "h3"; 87 | 88 | const nestedApp = new H3().get("/test", () => "/test (sub app)"); 89 | 90 | const app = new H3() 91 | .get("/", (event) => redirect(event, "/api/test")) 92 | .all("/api/**", withBase("/api", nestedApp.handler)); 93 | 94 | serve(app); 95 | ``` 96 | 97 | ### `H3.mount` 98 | 99 | Mount a `.fetch` compatible server instance like [Hono](https://hono.dev/) or [Elysia](https://elysiajs.com/) under the base URL. 100 | 101 | ```js 102 | import { H3 } from "h3"; 103 | import { Hono } from "hono"; 104 | import { Elysia } from "elysia"; 105 | 106 | const app = new H3() 107 | .mount( 108 | "/elysia", 109 | new Elysia().get("/test", () => "Hello Elysia!"), 110 | ) 111 | .mount( 112 | "/hono", 113 | new Hono().get("/test", (c) => c.text("Hello Hono!")), 114 | ); 115 | ``` 116 | 117 | > [!NOTE] 118 | > Base prefix will be removed from `request.url` passed to the mounted app. 119 | 120 | > [!TIP] 121 | > Similarly, you can mount an H3 app in [Hono](https://hono.dev/docs/api/hono#mount) or [Elysia](https://elysiajs.com/patterns/mount#mount-1). 122 | 123 | ## `H3` Options 124 | 125 | You can pass global app configuration when initializing an app. 126 | 127 | Supported options: 128 | 129 | - `debug` 130 | - `plugins`: (see [plugins](/guide/advanced/plugins) for more information) 131 | 132 | > [!IMPORTANT] 133 | > Enabling `debug` option, sends important stuff like stack traces in error responses. Only enable during development. 134 | 135 | ### Global Hooks 136 | 137 | When initializing an H3 app, you can register global hooks: 138 | 139 | - `onError` 140 | - `onRequest` 141 | - `onResponse` 142 | 143 | These hooks are called for every request and can be used to add global logic to your app such as logging, error handling, etc. 144 | 145 | ```js 146 | const app = new H3({ 147 | onRequest: (event) => { 148 | console.log("Request:", event.req.url); 149 | }, 150 | onResponse: (response, event) => { 151 | console.log("Response:", event.path, response.status); 152 | }, 153 | onError: (error, event) => { 154 | console.error(error); 155 | }, 156 | }); 157 | ``` 158 | 159 | > [!IMPORTANT] 160 | > Global hooks only run from main H3 app and **not** sub-apps. Use [middleware](/guide/basics/middleware) for more flexibility. 161 | 162 | ## `H3` Properties 163 | 164 | ### `H3.config` 165 | 166 | Global H3 instance config. 167 | -------------------------------------------------------------------------------- /docs/1.guide/900.api/2.h3event.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: material-symbols:data-object-rounded 3 | --- 4 | 5 | # H3Event 6 | 7 | > H3Event, carries incoming request, prepared response and context. 8 | 9 | With each HTTP request, H3 internally creates an `H3Event` object and passes it though event handlers until sending the response. 10 | 11 | :read-more{to="/guide/basics/lifecycle" title="Request Lifecycle"} 12 | 13 | An event is passed through all the lifecycle hooks and composable utils to use it as context. 14 | 15 | **Example:** 16 | 17 | ```js 18 | app.get("/", async (event) => { 19 | // Log HTTP request 20 | console.log(`[${event.req.method}] ${event.req.url}`); 21 | 22 | // Parsed URL and query params 23 | const searchParams = event.url.searchParams; 24 | 25 | // Try to read request JSON body 26 | const jsonBody = await event.req.json().catch(() => {}); 27 | 28 | return "OK"; 29 | }); 30 | ``` 31 | 32 | ## `H3Event` Methods 33 | 34 | ### `H3Event.waitUntil` 35 | 36 | Tell the runtime about an ongoing operation that shouldn't close until the promise resolves. 37 | 38 | ```js [app.mjs] 39 | import { logRequest } from "./tracing.mjs"; 40 | 41 | app.get("/", (event) => { 42 | request.waitUntil(logRequest(request)); 43 | return "OK"; 44 | }); 45 | ``` 46 | 47 | ```js [tracing.mjs] 48 | export async function logRequest(request) { 49 | await fetch("https://telemetry.example.com", { 50 | method: "POST", 51 | body: JSON.stringify({ 52 | method: request.method, 53 | url: request.url, 54 | ip: request.ip, 55 | }), 56 | }); 57 | } 58 | ``` 59 | 60 | ## `H3Event` Properties 61 | 62 | ### `H3Event.context` 63 | 64 | The context is an object that contains arbitrary information about the request. 65 | 66 | You can store your custom properties inside `event.context` to share across utils. 67 | 68 | **Known context keys:** 69 | 70 | - `context.params`: Matched router parameters. 71 | - `middlewareParams`: Matched middleware parameters 72 | - `matchedRoute`: Matched router route object. 73 | - `sessions`: Cached session data. 74 | - `basicAuth`: Basic authentication data. 75 | 76 | ### `H3Event.req` 77 | 78 | Incoming HTTP request info based on native [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) with additional runtime addons (see [srvx docs](https://srvx.h3.dev/guide/handler#extended-request-context)). 79 | 80 | ```ts 81 | app.get("/", async (event) => { 82 | const url = event.req.url; 83 | const method = event.req.method; 84 | const headers = event.req.headers; 85 | 86 | // (note: you can consume body only once with either of this) 87 | const bodyStream = await event.req.body; 88 | const textBody = await event.req.text(); 89 | const jsonBody = await event.req.json(); 90 | const formDataBody = await event.req.formData(); 91 | 92 | return "OK"; 93 | }); 94 | ``` 95 | 96 | ### `H3Event.url` 97 | 98 | Access to the full parsed request [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL). 99 | 100 | ### `H3Event.res` 101 | 102 | Prepared HTTP response status and headers. 103 | 104 | ```ts 105 | app.get("/", (event) => { 106 | event.res.status = 200; 107 | event.res.statusText = "OK"; 108 | event.res.headers.set("x-test", "works"); 109 | 110 | return "OK"; 111 | }); 112 | ``` 113 | 114 | :read-more{to="/guide/basics/response#preparing-response" title="Preparing Response"} 115 | -------------------------------------------------------------------------------- /docs/1.guide/901.advanced/.navigation.yml: -------------------------------------------------------------------------------- 1 | icon: hugeicons:more-01 2 | -------------------------------------------------------------------------------- /docs/1.guide/901.advanced/1.plugins.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: clarity:plugin-line 3 | --- 4 | 5 | # Plugins 6 | 7 | > H3 plugins allow you to extend an H3 app instance with reusable logic. 8 | 9 | ## Register Plugins 10 | 11 | Plugins can be registered either when creating a new [H3 instance](/guide/api/h3) or by using [H3.register](/guide/api/h3#h3register). 12 | 13 | ```js 14 | import { H3 } from "h3"; 15 | import { logger } from "./logger.mjs"; 16 | 17 | // Using instance config 18 | const app = new H3({ 19 | plugins: [logger()], 20 | }); 21 | 22 | // Or register later 23 | app.register(logger()); 24 | 25 | // ... rest of the code.. 26 | app.get("/**", () => "Hello, World!"); 27 | ``` 28 | 29 | > [!NOTE] 30 | > Plugins are always registered immediately. Therefore, the order in which they are used might be important depending on the plugin's functionality. 31 | 32 | ## Creating Plugins 33 | 34 | H3 plugins are simply functions that accept an [H3 instance](/guide/api/h3) as the first argument and immediately apply logic to extend it. 35 | 36 | ```js 37 | app.register((app) => { 38 | app.use(...) 39 | }) 40 | ``` 41 | 42 | For convenience, H3 provides a built-in `definePlugin` utility, which creates a typed factory function with optional plugin-specific options. 43 | 44 | ```js 45 | import { definePlugin } from "h3"; 46 | 47 | const logger = definePlugin((h3, _options) => { 48 | if (h3.config.debug) { 49 | h3.use((req) => { 50 | console.log(`[${req.method}] ${req.url}`); 51 | }); 52 | } 53 | }); 54 | ``` 55 | -------------------------------------------------------------------------------- /docs/1.guide/901.advanced/2.websocket.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: hugeicons:live-streaming-02 3 | --- 4 | 5 | # WebSockets 6 | 7 | > H3 has built-in utilities for cross platform WebSocket and Server-Sent Events. 8 | 9 | You can add cross platform WebSocket support to H3 servers using [🔌 CrossWS](https://crossws.h3.dev/). 10 | 11 | > [!IMPORTANT] 12 | > Built-in support of WebSockets in h3 version is WIP. 13 | 14 | ## Usage 15 | 16 | WebSocket handlers can be defined using the `defineWebSocketHandler()` utility and registered to any route like event handlers. 17 | 18 | You need to register CrossWS as a server plugin in the `serve` function and provide a `resolve` function to resolve the correct hooks from the route. 19 | 20 | ```js 21 | import { H3, serve, defineWebSocketHandler } from "h3"; 22 | 23 | import { plugin as ws } from "crossws/server"; 24 | 25 | const app = new H3(); 26 | 27 | app.get("/_ws", defineWebSocketHandler({ message: console.log })); 28 | 29 | serve(app, { 30 | plugins: [ws({ resolve: async (req) => (await app.fetch(req)).crossws })], 31 | }); 32 | ``` 33 | 34 | **Full example:** 35 | 36 | 37 | 38 | ```js [websocket.mjs] 39 | import { H3, serve, defineWebSocketHandler } from "h3"; 40 | import { plugin as ws } from "crossws/server"; 41 | 42 | export const app = new H3(); 43 | 44 | const demoURL = 45 | "https://raw.githubusercontent.com/h3js/crossws/refs/heads/main/playground/public/index.html"; 46 | 47 | app.get("/", () => 48 | fetch(demoURL).then( 49 | (res) => 50 | new Response(res.body, { headers: { "Content-Type": "text/html" } }), 51 | ), 52 | ); 53 | 54 | app.get( 55 | "/_ws", 56 | defineWebSocketHandler({ 57 | // upgrade(req) {}, 58 | open(peer) { 59 | console.log("[open]", peer); 60 | 61 | // Send welcome to the new client 62 | peer.send("Welcome to the server!"); 63 | 64 | // Join new client to the "chat" channel 65 | peer.subscribe("chat"); 66 | 67 | // Notify every other connected client 68 | peer.publish("chat", `[system] ${peer} joined!`); 69 | }, 70 | 71 | message(peer, message) { 72 | console.log("[message]", peer); 73 | 74 | if (message.text() === "ping") { 75 | // Reply to the client with a ping response 76 | peer.send("pong"); 77 | return; 78 | } 79 | 80 | // The server re-broadcasts incoming messages to everyone 81 | peer.publish("chat", `[${peer}] ${message}`); 82 | 83 | // Echo the message back to the sender 84 | peer.send(message); 85 | }, 86 | 87 | close(peer) { 88 | console.log("[close]", peer); 89 | peer.publish("chat", `[system] ${peer} has left the chat!`); 90 | peer.unsubscribe("chat"); 91 | }, 92 | }), 93 | ); 94 | 95 | serve(app, { 96 | plugins: [ws({ resolve: async (req) => (await app.fetch(req)).crossws })], 97 | }); 98 | ``` 99 | 100 | 101 | 102 | ## Server-Sent Events (SSE) 103 | 104 | As an alternative to WebSockets, you can use [Server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events). 105 | 106 | H3 has a built-in API to create server-sent events using `createEventStream(event)` utility. 107 | 108 | ### Example 109 | 110 | 111 | 112 | ```js [server-sent-events.mjs] 113 | import { H3, serve, createEventStream } from "h3"; 114 | 115 | export const app = new H3(); 116 | 117 | app.get("/", (event) => { 118 | const eventStream = createEventStream(event); 119 | 120 | // Send a message every second 121 | const interval = setInterval(async () => { 122 | await eventStream.push("Hello world"); 123 | }, 1000); 124 | 125 | // cleanup the interval when the connection is terminated or the writer is closed 126 | eventStream.onClosed(() => { 127 | console.log("Connection closed"); 128 | clearInterval(interval); 129 | }); 130 | 131 | return eventStream.send(); 132 | }); 133 | 134 | serve(app); 135 | ``` 136 | 137 | 138 | -------------------------------------------------------------------------------- /docs/1.guide/901.advanced/9.nightly.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: game-icons:barn-owl 3 | --- 4 | 5 | # Nightly Builds 6 | 7 | You can opt-in to early test latest H3 changes using automated nightly release channel. 8 | 9 | If you are directly using `h3` as a dependency in your project: 10 | 11 | ```json 12 | { 13 | "dependencies": { 14 | "h3": "npm:h3-nightly@latest" 15 | } 16 | } 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/2.utils/0.index.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: ph:function-bold 3 | --- 4 | 5 | # H3 Utils 6 | 7 | H3 is a composable framework. Instead of providing a big core, you start with a lightweight [H3](/guide/api/h3) instance and for every functionality, there is either a built-in utility or you can make yours. 8 | 9 | 10 | ::card-group 11 | 12 | ::card 13 | --- 14 | title: Request 15 | icon: material-symbols-light:input 16 | to: /utils/request 17 | --- 18 | Utilities for incoming request. 19 | :: 20 | 21 | ::card 22 | --- 23 | title: Response 24 | icon: material-symbols-light:output 25 | to: /utils/response 26 | --- 27 | Utilities for preparing and sending response. 28 | :: 29 | 30 | ::card 31 | --- 32 | title: Cookie 33 | icon: material-symbols:cookie-outline 34 | to: /utils/cookie 35 | --- 36 | Cookie utilities. 37 | :: 38 | 39 | ::card 40 | --- 41 | title: Security 42 | icon: wpf:key-security 43 | to: /utils/security 44 | --- 45 | Security utilities. 46 | :: 47 | 48 | ::card 49 | --- 50 | title: Proxy 51 | icon: arcticons:super-proxy 52 | to: /utils/proxy 53 | --- 54 | Proxy utilities. 55 | :: 56 | 57 | ::card 58 | --- 59 | title: More 60 | icon: ri:more-line 61 | to: /utils/more 62 | --- 63 | More Utilities. 64 | :: 65 | 66 | ::card 67 | --- 68 | title: Community 69 | icon: pixelarticons:github 70 | to: /utils/community 71 | --- 72 | Community made utilities. 73 | :: 74 | 75 | 76 | :: 77 | 78 | 79 | -------------------------------------------------------------------------------- /docs/2.utils/2.response.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: material-symbols-light:output 3 | --- 4 | 5 | # Response 6 | 7 | > H3 response utilities. 8 | 9 | ## Event Stream 10 | 11 | 12 | 13 | ### `createEventStream(event, opts?)` 14 | 15 | Initialize an EventStream instance for creating [server sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) 16 | 17 | **Example:** 18 | 19 | ```ts 20 | import { createEventStream, sendEventStream } from "h3"; 21 | 22 | app.get("/sse", (event) => { 23 | const eventStream = createEventStream(event); 24 | 25 | // Send a message every second 26 | const interval = setInterval(async () => { 27 | await eventStream.push("Hello world"); 28 | }, 1000); 29 | 30 | // cleanup the interval and close the stream when the connection is terminated 31 | eventStream.onClosed(async () => { 32 | console.log("closing SSE..."); 33 | clearInterval(interval); 34 | await eventStream.close(); 35 | }); 36 | 37 | return eventStream.send(); 38 | }); 39 | ``` 40 | 41 | 42 | 43 | ## Sanitize 44 | 45 | 46 | 47 | ### `sanitizeStatusCode(statusCode?, defaultStatusCode)` 48 | 49 | Make sure the status code is a valid HTTP status code. 50 | 51 | ### `sanitizeStatusMessage(statusMessage)` 52 | 53 | Make sure the status message is safe to use in a response. 54 | 55 | Allowed characters: horizontal tabs, spaces or visible ascii characters: https://www.rfc-editor.org/rfc/rfc7230#section-3.1.2 56 | 57 | 58 | 59 | ## Serve Static 60 | 61 | 62 | 63 | ### `serveStatic(event, options)` 64 | 65 | Dynamically serve static assets based on the request path. 66 | 67 | 68 | 69 | ## More Response Utils 70 | 71 | 72 | 73 | ### `html(event, content)` 74 | 75 | Respond with HTML content. 76 | 77 | **Example:** 78 | 79 | ```ts 80 | app.get("/", (event) => html(event, "

Hello, World!

")); 81 | ``` 82 | 83 | ### `iterable(_event, iterable)` 84 | 85 | Iterate a source of chunks and send back each chunk in order. Supports mixing async work together with emitting chunks. 86 | 87 | Each chunk must be a string or a buffer. 88 | 89 | For generator (yielding) functions, the returned value is treated the same as yielded values. 90 | 91 | **Example:** 92 | 93 | ```ts 94 | return iterable(event, async function* work() { 95 | // Open document body 96 | yield "\n

Executing...

    \n"; 97 | // Do work ... 98 | for (let i = 0; i < 1000) { 99 | await delay(1000); 100 | // Report progress 101 | yield `
  1. Completed job #`; 102 | yield i; 103 | yield `
  2. \n`; 104 | } 105 | // Close out the report 106 | return `
`; 107 | }) 108 | async function delay(ms) { 109 | return new Promise(resolve => setTimeout(resolve, ms)); 110 | } 111 | ``` 112 | 113 | ### `noContent(event, code?)` 114 | 115 | Respond with an empty payload.
116 | 117 | **Example:** 118 | 119 | ```ts 120 | app.get("/", () => noContent()); 121 | ``` 122 | 123 | ### `redirect(event, location, code)` 124 | 125 | Send a redirect response to the client. 126 | 127 | It adds the `location` header to the response and sets the status code to 302 by default. 128 | 129 | In the body, it sends a simple HTML page with a meta refresh tag to redirect the client in case the headers are ignored. 130 | 131 | **Example:** 132 | 133 | ```ts 134 | app.get("/", (event) => { 135 | return redirect(event, "https://example.com"); 136 | }); 137 | ``` 138 | 139 | **Example:** 140 | 141 | ```ts 142 | app.get("/", (event) => { 143 | return redirect(event, "https://example.com", 301); // Permanent redirect 144 | }); 145 | ``` 146 | 147 | ### `writeEarlyHints(event, hints)` 148 | 149 | Write `HTTP/1.1 103 Early Hints` to the client. 150 | 151 | 152 | -------------------------------------------------------------------------------- /docs/2.utils/3.cookie.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: material-symbols:cookie-outline 3 | --- 4 | 5 | # Cookie 6 | 7 | > H3 cookie utilities. 8 | 9 | 10 | 11 | ### `deleteCookie(event, name, serializeOptions?)` 12 | 13 | Remove a cookie by name. 14 | 15 | ### `getCookie(event, name)` 16 | 17 | Get a cookie value by name. 18 | 19 | ### `parseCookies(event)` 20 | 21 | Parse the request to get HTTP Cookie header string and returning an object of all cookie name-value pairs. 22 | 23 | ### `setCookie(event, name, value, options?)` 24 | 25 | Set a cookie value by name. 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/2.utils/4.security.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: wpf:key-security 3 | --- 4 | 5 | # Security 6 | 7 | > H3 security utilities. 8 | 9 | ## Authentication 10 | 11 | 12 | 13 | ### `basicAuth(opts)` 14 | 15 | Create a basic authentication middleware. 16 | 17 | **Example:** 18 | 19 | ```ts 20 | import { H3, serve, basicAuth } from "h3"; 21 | const auth = basicAuth({ password: "test" }); 22 | app.get("/", (event) => `Hello ${event.context.basicAuth?.username}!`, [auth]); 23 | serve(app, { port: 3000 }); 24 | ``` 25 | 26 | ### `requireBasicAuth(event, opts)` 27 | 28 | Apply basic authentication for current request. 29 | 30 | **Example:** 31 | 32 | ```ts 33 | import { defineHandler, requireBasicAuth } from "h3"; 34 | export default defineHandler(async (event) => { 35 | await requireBasicAuth(event, { password: "test" }); 36 | return `Hello, ${event.context.basicAuth.username}!`; 37 | }); 38 | ``` 39 | 40 | 41 | 42 | ## Session 43 | 44 | 45 | 46 | ### `clearSession(event, config)` 47 | 48 | Clear the session data for the current request. 49 | 50 | ### `getSession(event, config)` 51 | 52 | Get the session for the current request. 53 | 54 | ### `sealSession(event, config)` 55 | 56 | Encrypt and sign the session data for the current request. 57 | 58 | ### `unsealSession(_event, config, sealed)` 59 | 60 | Decrypt and verify the session data for the current request. 61 | 62 | ### `updateSession(event, config, update?)` 63 | 64 | Update the session data for the current request. 65 | 66 | ### `useSession(event, config)` 67 | 68 | Create a session manager for the current request. 69 | 70 | 71 | 72 | ## Fingerprint 73 | 74 | 75 | 76 | ### `getRequestFingerprint(event, opts)` 77 | 78 | Get a unique fingerprint for the incoming request. 79 | 80 | 81 | 82 | ## CORS 83 | 84 | 85 | 86 | ### `appendCorsHeaders(event, options)` 87 | 88 | Append CORS headers to the response. 89 | 90 | ### `appendCorsPreflightHeaders(event, options)` 91 | 92 | Append CORS preflight headers to the response. 93 | 94 | ### `handleCors(event, options)` 95 | 96 | Handle CORS for the incoming request. 97 | 98 | If the incoming request is a CORS preflight request, it will append the CORS preflight headers and send a 204 response. 99 | 100 | If return value is `true`, the request is handled and no further action is needed. 101 | 102 | **Example:** 103 | 104 | ```ts 105 | const app = new H3(); 106 | const router = createRouter(); 107 | router.use("/", async (event) => { 108 | const corsRes = handleCors(event, { 109 | origin: "*", 110 | preflight: { 111 | statusCode: 204, 112 | }, 113 | methods: "*", 114 | }); 115 | if (corsRes) { 116 | return corsRes; 117 | } 118 | // Your code here 119 | }); 120 | ``` 121 | 122 | ### `isCorsOriginAllowed(origin, options)` 123 | 124 | Check if the origin is allowed. 125 | 126 | ### `isPreflightRequest(event)` 127 | 128 | Check if the incoming request is a CORS preflight request. 129 | 130 | 131 | -------------------------------------------------------------------------------- /docs/2.utils/5.proxy.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: arcticons:super-proxy 3 | --- 4 | 5 | # Proxy 6 | 7 | > H3 proxy utilities. 8 | 9 | 10 | 11 | ### `fetchWithEvent(event, req, init?, options?: { fetch: F })` 12 | 13 | Make a fetch request with the event's context and headers. 14 | 15 | ### `getProxyRequestHeaders(event)` 16 | 17 | Get the request headers object without headers known to cause issues when proxying. 18 | 19 | ### `proxy(event, target, opts)` 20 | 21 | Make a proxy request to a target URL and send the response back to the client. 22 | 23 | ### `proxyRequest(event, target, opts)` 24 | 25 | Proxy the incoming request to a target URL. 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/2.utils/9.more.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: mingcute:plus-line 3 | --- 4 | 5 | # More utils 6 | 7 | > More H3 utilities. 8 | 9 | ## Base 10 | 11 | 12 | 13 | ### `withBase(base, input)` 14 | 15 | Returns a new event handler that removes the base url of the event before calling the original handler. 16 | 17 | **Example:** 18 | 19 | ```ts 20 | const api = new H3() 21 | .get("/", () => "Hello API!"); 22 | const app = new H3(); 23 | .use("/api/**", withBase("/api", api.handler)); 24 | ``` 25 | 26 | 27 | 28 | ## Event 29 | 30 | 31 | 32 | ### `isEvent(input)` 33 | 34 | Checks if the input is an H3Event object. 35 | 36 | ### `mockEvent(_request, options?)` 37 | 38 | 39 | 40 | ## Middleware 41 | 42 | 43 | 44 | ### `onError(hook)` 45 | 46 | Define a middleware that runs when an error occurs. 47 | 48 | You can return a new Response from the handler to gracefully handle the error. 49 | 50 | ### `onRequest(hook)` 51 | 52 | Define a middleware that runs on each request. 53 | 54 | ### `onResponse(hook)` 55 | 56 | Define a middleware that runs after Response is generated. 57 | 58 | You can return a new Response from the handler to replace the original response. 59 | 60 | 61 | 62 | ## WebSocket 63 | 64 | 65 | 66 | ### `defineWebSocket(hooks)` 67 | 68 | Define WebSocket hooks. 69 | 70 | ### `defineWebSocketHandler(hooks)` 71 | 72 | Define WebSocket event handler. 73 | 74 | 75 | 76 | ## Adapters 77 | 78 | 79 | 80 | ### `defineNodeHandler(handler)` 81 | 82 | ### `defineNodeMiddleware(handler)` 83 | 84 | ### `fromNodeHandler(handler)` 85 | 86 | ### `fromWebHandler()` 87 | 88 | ### `toNodeHandler(app)` 89 | 90 | Convert H3 app instance to a NodeHandler with (IncomingMessage, ServerResponse) => void signature. 91 | 92 | 93 | -------------------------------------------------------------------------------- /docs/2.utils/99.community.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: lets-icons:external 3 | --- 4 | 5 | # Community 6 | 7 | > H3 utils from community. 8 | 9 | You can use external H3 event utilities made by the community. 10 | 11 | This section is placeholder for any new H3 version 2 compatible community library. 12 |
13 |
14 | 15 | > [!TIP] 16 | > 💛 PR is more than welcome to list yours. 17 | -------------------------------------------------------------------------------- /docs/4.examples/0.index.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: ph:code 3 | --- 4 | 5 | # Examples 6 | 7 | > Common examples for h3. 8 | 9 | ::read-more{to="https://github.com/h3js/h3/tree/main/examples"} 10 | Check [`examples/` dir](https://github.com/h3js/h3/tree/main/examples) for more examples. 11 | :: 12 | 13 | **Examples:** 14 | 15 | - [Cookies](/examples/handle-cookie) 16 | - [Session](/examples/handle-session) 17 | - [Static Assets](/examples/serve-static-assets) 18 | - [Streaming Response](/examples/stream-response) 19 | - [Validation](/examples/validate-data) 20 | -------------------------------------------------------------------------------- /docs/4.examples/handle-cookie.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: ph:arrow-right 3 | --- 4 | 5 | # Cookies 6 | 7 | > Use cookies to store data on the client. 8 | 9 | Handling cookies with H3 is straightforward. There is three utilities to handle cookies: 10 | 11 | - `setCookie` to attach a cookie to the response. 12 | - `getCookie` to get a cookie from the request. 13 | - `deleteCookie` to clear a cookie from the response. 14 | 15 | ## Set a Cookie 16 | 17 | To set a cookie, you need to use `setCookie` in an event handler: 18 | 19 | ```ts 20 | import { setCookie } from "h3"; 21 | 22 | app.use(async (event) => { 23 | setCookie(event, "name", "value", { maxAge: 60 * 60 * 24 * 7 }); 24 | return ""; 25 | }); 26 | ``` 27 | 28 | In the options, you can configure the [cookie flags](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie): 29 | 30 | - `maxAge` to set the expiration date of the cookie in seconds. 31 | - `expires` to set the expiration date of the cookie in a `Date` object. 32 | - `path` to set the path of the cookie. 33 | - `domain` to set the domain of the cookie. 34 | - `secure` to set the `Secure` flag of the cookie. 35 | - `httpOnly` to set the `HttpOnly` flag of the cookie. 36 | - `sameSite` to set the `SameSite` flag of the cookie. 37 | 38 | :read-more{to="/utils"} 39 | 40 | ## Get a Cookie 41 | 42 | To get a cookie, you need to use `getCookie` in an event handler. 43 | 44 | ```ts 45 | import { getCookie } from "h3"; 46 | 47 | app.use(async (event) => { 48 | const name = getCookie(event, "name"); 49 | 50 | // do something... 51 | 52 | return ""; 53 | }); 54 | ``` 55 | 56 | This will return the value of the cookie if it exists, or `undefined` otherwise. 57 | 58 | ## Delete a Cookie 59 | 60 | To delete a cookie, you need to use `deleteCookie` in an event handler: 61 | 62 | ```ts 63 | import { deleteCookie } from "h3"; 64 | 65 | app.use(async (event) => { 66 | deleteCookie(event, "name"); 67 | return ""; 68 | }); 69 | ``` 70 | 71 | The utility `deleteCookie` is a wrapper around `setCookie` with the value set to `""` and the `maxAge` set to `0`. 72 | 73 | This will erase the cookie from the client. 74 | -------------------------------------------------------------------------------- /docs/4.examples/handle-session.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: ph:arrow-right 3 | --- 4 | 5 | # Sessions 6 | 7 | > Remember your users using a session. 8 | 9 | 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. 10 | 11 | H3 provides many utilities to handle sessions: 12 | 13 | - `useSession` initializes a session and returns a wrapper to control it. 14 | - `getSession` initializes or retrieves the current user session. 15 | - `updateSession` updates the data of the current session. 16 | - `clearSession` clears the current session. 17 | 18 | Most of the time, you will use `useSession` to manipulate the session. 19 | 20 | ## Initialize a Session 21 | 22 | To initialize a session, you need to use `useSession` in an [event handler](/guide/handler): 23 | 24 | ```js 25 | import { useSession } from "h3"; 26 | 27 | app.use(async (event) => { 28 | const session = await useSession(event, { 29 | password: "80d42cfb-1cd2-462c-8f17-e3237d9027e9", 30 | }); 31 | 32 | // do something... 33 | }); 34 | ``` 35 | 36 | > [!WARNING] 37 | > You must provide a password to encrypt the session. 38 | 39 | This will initialize a session and return an header `Set-Cookie` with a cookie named `h3` and an encrypted content. 40 | 41 | 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. 42 | 43 | > [!NOTE] 44 | > The header take precedence over the cookie. 45 | 46 | ## Get Data from a Session 47 | 48 | To get data from a session, we will still use `useSession`. Under the hood, it will use `getSession` to get the session. 49 | 50 | ```js 51 | import { useSession } from "h3"; 52 | 53 | app.use(async (event) => { 54 | const session = await useSession(event, { 55 | password: "80d42cfb-1cd2-462c-8f17-e3237d9027e9", 56 | }); 57 | 58 | return session.data; 59 | }); 60 | ``` 61 | 62 | Data are stored in the `data` property of the session. If there is no data, it will be an empty object. 63 | 64 | ## Add Data to a Session 65 | 66 | To add data to a session, we will still use `useSession`. Under the hood, it will use `updateSession` to update the session. 67 | 68 | ```js 69 | import { useSession } from "h3"; 70 | 71 | app.use(async (event) => { 72 | const session = await useSession(event, { 73 | password: "80d42cfb-1cd2-462c-8f17-e3237d9027e9", 74 | }); 75 | 76 | const count = (session.data.count || 0) + 1; 77 | await session.update({ 78 | count: count, 79 | }); 80 | 81 | return count === 0 82 | ? "Hello world!" 83 | : `Hello world! You have visited this page ${count} times.`; 84 | }); 85 | ``` 86 | 87 | What is happening here? 88 | 89 | 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. 90 | 91 | Try to visit the page multiple times and you will see the number of times you visited the page. 92 | 93 | > [!NOTE] 94 | > 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. 95 | 96 | ## Clear a Session 97 | 98 | To clear a session, we will still use `useSession`. Under the hood, it will use `clearSession` to clear the session. 99 | 100 | ```js 101 | import { useSession } from "h3"; 102 | 103 | app.use("/clear", async (event) => { 104 | const session = await useSession(event, { 105 | password: "80d42cfb-1cd2-462c-8f17-e3237d9027e9", 106 | }); 107 | 108 | await session.clear(); 109 | 110 | return "Session cleared"; 111 | }); 112 | ``` 113 | 114 | H3 will send a header `Set-Cookie` with an empty cookie named `h3` to clear the session. 115 | 116 | ## Options 117 | 118 | When to use `useSession`, you can pass an object with options as the second argument to configure the session: 119 | 120 | ```js 121 | import { useSession } from "h3"; 122 | 123 | app.use(async (event) => { 124 | const session = await useSession(event, { 125 | name: "my-session", 126 | password: "80d42cfb-1cd2-462c-8f17-e3237d9027e9", 127 | cookie: { 128 | httpOnly: true, 129 | secure: true, 130 | sameSite: "strict", 131 | }, 132 | maxAge: 60 * 60 * 24 * 7, // 7 days 133 | }); 134 | 135 | return session.data; 136 | }); 137 | ``` 138 | -------------------------------------------------------------------------------- /docs/4.examples/serve-static-assets.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: ph:arrow-right 3 | --- 4 | 5 | # Static Assets 6 | 7 | > Serve static assets such as HTML, images, CSS, JavaScript, etc. 8 | 9 | H3 can serve static assets such as HTML, images, CSS, JavaScript, etc. 10 | 11 | To serve a static directory, you can use the `serveStatic` utility. 12 | 13 | ```ts 14 | import { H3, serveStatic } from "h3"; 15 | 16 | const app = new H3(); 17 | 18 | app.use("/public/**", (event) => { 19 | return serveStatic(event, { 20 | getContents: (id) => { 21 | // TODO 22 | }, 23 | getMeta: (id) => { 24 | // TODO 25 | }, 26 | }); 27 | }); 28 | ``` 29 | 30 | This does not serve any files yet. You need to implement the `getContents` and `getMeta` methods. 31 | 32 | - `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. 33 | - `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. 34 | 35 | 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. 36 | 37 | ## Read files 38 | 39 | 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. 40 | 41 | Then, we can create the `getContents` and `getMeta` methods: 42 | 43 | ```ts 44 | import { stat, readFile } from "node:fs/promises"; 45 | import { join } from "node:path"; 46 | import { H3, serve, serveStatic } from "h3"; 47 | 48 | const app = new H3(); 49 | 50 | app.use("/public/**", (event) => { 51 | return serveStatic(event, { 52 | indexNames: ["/index.html"], 53 | getContents: (id) => readFile(join("public", id)), 54 | getMeta: async (id) => { 55 | const stats = await stat(join("public", id)).catch(() => {}); 56 | if (stats?.isFile()) { 57 | return { 58 | size: stats.size, 59 | mtime: stats.mtimeMs, 60 | }; 61 | } 62 | }, 63 | }); 64 | }); 65 | 66 | serve(app); 67 | ``` 68 | 69 | The `getContents` read 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. 70 | 71 | 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. 72 | -------------------------------------------------------------------------------- /docs/4.examples/stream-response.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: ph:arrow-right 3 | --- 4 | 5 | # Stream Response 6 | 7 | > Stream response to the client. 8 | 9 | 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. 10 | 11 | ## Create a Stream 12 | 13 | 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: 14 | 15 | ```ts 16 | const stream = new ReadableStream(); 17 | ``` 18 | 19 | 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: 20 | 21 | ```ts 22 | let interval: NodeJS.Timeout; 23 | const stream = new ReadableStream({ 24 | start(controller) { 25 | controller.enqueue("