UNPKG

h3

Version:

Minimal H(TTP) framework built for high performance and portability.

165 lines (108 loc) 7.2 kB
# Routing > Each request is matched to one (most specific) route handler. ## Adding Routes 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). > [!TIP] > Router is powered by [🌳 Rou3](https://github.com/h3js/rou3), an ultra-fast and tiny route matcher engine. **Example:** Register a route to match requests to the `/hello` endpoint with HTTP **GET** method. - Using [`H3.[method]`](/guide/api/h3#h3method) ```js app.get("/hello", () => "Hello world!"); ``` - Using [`H3.on`](/guide/api/h3#h3on) ```js app.on("GET", "/hello", () => "Hello world!"); ``` You can register multiple event handlers for the same route with different methods: ```js app .get("/hello", () => "GET Hello world!") .post("/hello", () => "POST Hello world!") .all("/hello", () => "Any other method!"); ``` You can also use [`H3.all`](/guide/api/h3#h3all) method to register a route accepting any HTTP method: ```js app.all("/hello", (event) => `This is a ${event.req.method} request!`); ``` ## HEAD Requests Following [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110#name-head), `HEAD` requests automatically match the corresponding `GET` route and run its handler, but the response body is omitted (only the headers and status are sent). You don't need to register a separate `HEAD` handler: ```js app.get("/hello", () => "Hello world!"); // HEAD /hello → 200 with the same headers as GET, but an empty body ``` Register an explicit `HEAD` handler when you want to override this — for example, to skip computing the body: ```js app.head("/hello", (event) => { event.res.headers.set("content-length", "12"); return null; }); ``` An explicit `head()` route always takes precedence over the automatic `GET` fallback. ## HTTP `QUERY` Method H3 supports the [HTTP `QUERY` method (RFC 10008)](https://www.rfc-editor.org/rfc/rfc10008) as a first-class method. `QUERY` is like `GET` — **safe, idempotent, and cacheable** — but carries a request body (with a `Content-Type`), closing the long-standing "GET with a body" gap. It's ideal for complex read operations where filters don't fit in a URL. Register a `QUERY` handler with `app.query()` (or `app.on("QUERY", …)`) and read the request body as usual: ```js import { readBody } from "h3"; app.query("/search", async (event) => { const criteria = await readBody(event); // read the query body return runSearch(criteria); }); ``` Because `QUERY` carries an attacker-controllable body, [body-size limits](/utils/request#assertbodysizeevent-limit) apply just like `POST`. Two utilities help implement the RFC: - [`requireContentType(event, acceptedTypes)`](/utils/request#requirecontenttypeevent-acceptedtypes) — assert the request `Content-Type` (`400`/`415`/`422`). - [`appendAcceptQuery(event, mediaTypes)`](/utils/request#appendacceptqueryevent-mediatypes) — advertise accepted query formats via the `Accept-Query` response header. > [!NOTE] `QUERY` is treated like `GET` for [conditional caching](/utils/request#handlecacheheadersevent-opts) (`304` responses via `handleCacheHeaders`), and [`proxy`](/utils/proxy) forwards it **with** its body. Unlike `GET`, `QUERY` is **not** CORS-safelisted, so browsers send a preflight — if you pass an explicit `methods` allowlist to [`handleCors`](/utils/security#handlecorsevent-options), include `"QUERY"`. <read-more> See the [HTTP `QUERY` method example](/examples/handle-query) for a runnable `/books` resource that validates the `Content-Type` and advertises a cacheable `GET` alternative. </read-more> ## Route Patterns A route pattern is a **pathname**, not a URL: the same shape as `event.url.pathname`, plus [rou3](https://github.com/h3js/rou3) syntax. [`H3.on`](/guide/api/h3#h3on), [`H3.[method]`](/guide/api/h3#h3method), [`H3.all`](/guide/api/h3#h3all), [`H3.use(route, ...)`](/guide/api/h3#h3use), [`H3.mount`](/guide/api/h3#h3mount) and [`removeRoute`](/utils/more#removerouteapp-method-route) normalize it identically, so a middleware registered with the same string as a route always guards that route. Normalization rules: - A leading `/` is added when missing (`"hello"``/hello`). - A URL is **rejected** (`app.get("http://example.com/admin")` throws). An authority is never silently dropped: `//admin` registers as the two-segment path `//admin`, not as `/`. - `.` and `..` segments resolve exactly as the URL parser resolves them in a request path (`/admin/../admin``/admin`). - Characters that a request pathname always carries percent-encoded are encoded: space, non-ASCII, control characters, `"`, `#`, `<`, `>` and ```. So `app.get("/café")` registers `/caf%C3%A9` — what the browser actually sends. - Needless escapes are decoded to the literal that the request pathname is canonicalized to (`/%40handle` → `/@handle`, see [Security utils](/utils/security)). Characters that carry rou3 meaning are left exactly as written, including the escape `\` (never valid in a request pathname). To match `?`, `{`, `}` or `^` **literally**, write it percent-encoded: ```js app.get("/u/:id?", () => "optional param"); // rou3 syntax, kept as written app.get("/x%3Fy", () => "literal ?"); // matches the path a client sends for /x?y ``` > [!NOTE] > Non-ASCII text mixes freely with dynamic syntax — `app.get("/café/:id")` registers `/caf%C3%A9/:id` and matches `/café/42` — with two exceptions, both from the encoded form reaching rou3's own syntax. A **param name** must be ASCII (`[\w-]`): `/:naïve` becomes `/:na%C3%AFve`, which rou3 reads as a param named `na` followed by the literal `%C3%AFve`. And inside a `(...)` group, only literal text and alternation survive encoding (`(café|thé)` works); a **character class** does not, since `[é]` becomes `[%C3%A9]` — write the encoded alternation `(?:%C3%A9)` instead. ## Dynamic Routes You can define dynamic route parameters using `:` prefix: ```js // [GET] /hello/Bob => "Hello, Bob!" app.get("/hello/:name", (event) => { return `Hello, ${event.context.params.name}!`; }); ``` Instead of named parameters, you can use `*` for unnamed **optional** parameters: ```js app.get("/hello/*", (event) => `Hello!`); ``` ## Wildcard Routes Adding `/hello/:name` route will match `/hello/world` or `/hello/123`. But it will not match `/hello/foo/bar`. When you need to match multiple levels of sub routes, you can use `**` prefix: ```js app.get("/hello/**", (event) => `Hello ${event.context.params._}!`); ``` This will match `/hello`, `/hello/world`, `/hello/123`, `/hello/world/123`, etc. > [!NOTE] > Param `_` will store the full wildcard content as a single string. ## Route Meta You can define optional route meta when registering them, accessible from any middleware. ```js import { H3 } from "h3"; const app = new H3(); app.use((event) => { console.log(event.context.matchedRoute?.meta); // { auth: true } }); app.get("/", (event) => "Hi!", { meta: { auth: true } }); ``` <read-more> It is also possible to add route meta when defining them using `defineHandler` object syntax. </read-more>