h3
Version:
144 lines (93 loc) • 4.94 kB
Markdown
# 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>
## 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>