@shaferllc/keel 0.59.0 → 0.68.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/AGENTS.md +167 -0
  2. package/README.md +30 -1
  3. package/bin/keel-mcp.mjs +9 -0
  4. package/dist/core/application.d.ts +5 -5
  5. package/dist/core/application.js +14 -2
  6. package/dist/core/auth.d.ts +47 -0
  7. package/dist/core/auth.js +77 -0
  8. package/dist/core/authorization.d.ts +9 -1
  9. package/dist/core/authorization.js +22 -2
  10. package/dist/core/cache.d.ts +82 -5
  11. package/dist/core/cache.js +181 -23
  12. package/dist/core/cli/stubs.d.ts +12 -0
  13. package/dist/core/cli/stubs.js +120 -0
  14. package/dist/core/container.d.ts +20 -0
  15. package/dist/core/container.js +52 -0
  16. package/dist/core/cors.d.ts +29 -0
  17. package/dist/core/cors.js +72 -0
  18. package/dist/core/crypto.d.ts +40 -4
  19. package/dist/core/crypto.js +66 -6
  20. package/dist/core/csrf.d.ts +25 -0
  21. package/dist/core/csrf.js +78 -0
  22. package/dist/core/database.d.ts +49 -4
  23. package/dist/core/database.js +89 -21
  24. package/dist/core/events.d.ts +129 -5
  25. package/dist/core/events.js +165 -7
  26. package/dist/core/health.d.ts +141 -0
  27. package/dist/core/health.js +226 -0
  28. package/dist/core/helpers.d.ts +15 -3
  29. package/dist/core/helpers.js +23 -3
  30. package/dist/core/index.d.ts +33 -18
  31. package/dist/core/index.js +16 -8
  32. package/dist/core/lock.d.ts +139 -0
  33. package/dist/core/lock.js +215 -0
  34. package/dist/core/logger.d.ts +82 -4
  35. package/dist/core/logger.js +141 -23
  36. package/dist/core/mail.d.ts +128 -7
  37. package/dist/core/mail.js +264 -16
  38. package/dist/core/model.d.ts +2 -0
  39. package/dist/core/model.js +16 -14
  40. package/dist/core/provider.d.ts +7 -0
  41. package/dist/core/provider.js +7 -0
  42. package/dist/core/queue.d.ts +134 -9
  43. package/dist/core/queue.js +304 -14
  44. package/dist/core/rate-limit.js +3 -0
  45. package/dist/core/relations.js +13 -13
  46. package/dist/core/request.d.ts +26 -0
  47. package/dist/core/request.js +77 -0
  48. package/dist/core/shield.d.ts +39 -0
  49. package/dist/core/shield.js +60 -0
  50. package/dist/core/social.d.ts +173 -0
  51. package/dist/core/social.js +337 -0
  52. package/dist/core/storage.d.ts +159 -6
  53. package/dist/core/storage.js +287 -7
  54. package/dist/core/tokens.d.ts +74 -0
  55. package/dist/core/tokens.js +155 -0
  56. package/dist/db/d1.d.ts +32 -0
  57. package/dist/db/d1.js +26 -0
  58. package/dist/db/libsql.d.ts +29 -0
  59. package/dist/db/libsql.js +32 -0
  60. package/dist/db/pg.d.ts +29 -0
  61. package/dist/db/pg.js +33 -0
  62. package/dist/mcp/server.d.ts +19 -0
  63. package/dist/mcp/server.js +355 -0
  64. package/docs/ai-manifest.json +2472 -0
  65. package/docs/ai.md +128 -0
  66. package/docs/architecture.md +331 -0
  67. package/docs/authentication.md +453 -0
  68. package/docs/authorization.md +167 -0
  69. package/docs/broadcasting.md +137 -0
  70. package/docs/broker.md +500 -0
  71. package/docs/cache.md +558 -0
  72. package/docs/configuration.md +311 -0
  73. package/docs/console.md +356 -0
  74. package/docs/container.md +467 -0
  75. package/docs/controllers.md +265 -0
  76. package/docs/cors.md +51 -0
  77. package/docs/database.md +530 -0
  78. package/docs/debugging.md +129 -0
  79. package/docs/decorators.md +127 -0
  80. package/docs/errors.md +395 -0
  81. package/docs/events.md +496 -0
  82. package/docs/examples/architecture-app.ts +27 -0
  83. package/docs/examples/authentication.ts +61 -0
  84. package/docs/examples/authorization.ts +79 -0
  85. package/docs/examples/broadcasting.ts +60 -0
  86. package/docs/examples/broker-cache-validate.ts +34 -0
  87. package/docs/examples/broker-fault-tolerance.ts +29 -0
  88. package/docs/examples/broker-middleware.ts +27 -0
  89. package/docs/examples/broker.ts +203 -0
  90. package/docs/examples/cache.ts +222 -0
  91. package/docs/examples/configuration.ts +81 -0
  92. package/docs/examples/container.ts +134 -0
  93. package/docs/examples/controllers.ts +86 -0
  94. package/docs/examples/database.ts +118 -0
  95. package/docs/examples/debugging.ts +41 -0
  96. package/docs/examples/decorators.ts +40 -0
  97. package/docs/examples/errors.ts +121 -0
  98. package/docs/examples/events.ts +204 -0
  99. package/docs/examples/factories.ts +84 -0
  100. package/docs/examples/hashing.ts +71 -0
  101. package/docs/examples/health.ts +94 -0
  102. package/docs/examples/helpers.ts +171 -0
  103. package/docs/examples/hooks.ts +54 -0
  104. package/docs/examples/inertia.ts +81 -0
  105. package/docs/examples/locks.ts +120 -0
  106. package/docs/examples/logger.ts +92 -0
  107. package/docs/examples/mail.ts +160 -0
  108. package/docs/examples/middleware.ts +119 -0
  109. package/docs/examples/migrations.ts +126 -0
  110. package/docs/examples/models.ts +239 -0
  111. package/docs/examples/notification.ts +124 -0
  112. package/docs/examples/providers.ts +123 -0
  113. package/docs/examples/queues.ts +254 -0
  114. package/docs/examples/rate-limiting.ts +42 -0
  115. package/docs/examples/redis.ts +99 -0
  116. package/docs/examples/request-response.ts +197 -0
  117. package/docs/examples/routing.ts +186 -0
  118. package/docs/examples/scheduling.ts +62 -0
  119. package/docs/examples/sessions.ts +102 -0
  120. package/docs/examples/static-files.ts +63 -0
  121. package/docs/examples/storage.ts +132 -0
  122. package/docs/examples/templates.ts +58 -0
  123. package/docs/examples/testing.ts +66 -0
  124. package/docs/examples/transformer.ts +141 -0
  125. package/docs/examples/transformers.ts +49 -0
  126. package/docs/examples/url-builder.ts +86 -0
  127. package/docs/examples/validation.ts +102 -0
  128. package/docs/examples/views.tsx +62 -0
  129. package/docs/examples/vite.ts +106 -0
  130. package/docs/factories.md +166 -0
  131. package/docs/getting-started.md +290 -0
  132. package/docs/hashing.md +259 -0
  133. package/docs/health.md +225 -0
  134. package/docs/helpers.md +347 -0
  135. package/docs/hono.md +186 -0
  136. package/docs/hooks.md +118 -0
  137. package/docs/inertia.md +241 -0
  138. package/docs/locks.md +323 -0
  139. package/docs/logger.md +290 -0
  140. package/docs/mail.md +678 -0
  141. package/docs/middleware.md +425 -0
  142. package/docs/migrations.md +476 -0
  143. package/docs/models.md +810 -0
  144. package/docs/notifications.md +474 -0
  145. package/docs/providers.md +363 -0
  146. package/docs/queues.md +679 -0
  147. package/docs/rate-limiting.md +155 -0
  148. package/docs/redis.md +178 -0
  149. package/docs/request-response.md +953 -0
  150. package/docs/routing.md +804 -0
  151. package/docs/scheduling.md +110 -0
  152. package/docs/security.md +85 -0
  153. package/docs/sessions.md +354 -0
  154. package/docs/social-auth.md +174 -0
  155. package/docs/static-files.md +211 -0
  156. package/docs/storage.md +450 -0
  157. package/docs/templates.md +315 -0
  158. package/docs/testing.md +125 -0
  159. package/docs/transformers.md +381 -0
  160. package/docs/url-builder.md +295 -0
  161. package/docs/validation.md +288 -0
  162. package/docs/views.md +267 -0
  163. package/docs/vite.md +434 -0
  164. package/llms-full.txt +17694 -0
  165. package/llms.txt +116 -0
  166. package/package.json +38 -7
@@ -0,0 +1,804 @@
1
+ # Routing
2
+
3
+ Routes live in `routes/web.ts`. The default export receives the `Router` and
4
+ registers routes on it. The HTTP kernel later compiles them onto Hono.
5
+
6
+ ```ts
7
+ import type { Router } from "@shaferllc/keel/core";
8
+ import { json, text, param } from "@shaferllc/keel/core";
9
+ import { HomeController } from "../app/Controllers/HomeController.js";
10
+
11
+ export default function routes(router: Router): void {
12
+ router.get("/", [HomeController, "index"]); // controller
13
+ router.get("/health", json({ status: "ok" })); // static response
14
+ router.get("/hi/:name", () => text(`Hi ${param("name")}`)); // dynamic
15
+ }
16
+ ```
17
+
18
+ ## HTTP verbs
19
+
20
+ ```ts
21
+ router.get(path, handler);
22
+ router.post(path, handler);
23
+ router.put(path, handler);
24
+ router.patch(path, handler);
25
+ router.delete(path, handler);
26
+ ```
27
+
28
+ Each returns the router, so calls chain.
29
+
30
+ ## Three kinds of handler
31
+
32
+ **Controller actions** — a `[Controller, method]` tuple, resolved from the
33
+ [container](./container.md) with dependency injection:
34
+
35
+ ```ts
36
+ router.get("/users/:id", [UserController, "show"]);
37
+ ```
38
+
39
+ **Static responses** — pass a ready-made response directly, no closure:
40
+
41
+ ```ts
42
+ router.get("/health", json({ status: "ok" }));
43
+ router.get("/robots.txt", text("User-agent: *\nAllow: /"));
44
+ ```
45
+
46
+ **Closures** — a function that runs per request. Use this whenever the response
47
+ depends on the request (route params, query, body), because those must be read
48
+ at request time:
49
+
50
+ ```ts
51
+ router.get("/users/:id", () => json({ id: param("id") }));
52
+ ```
53
+
54
+ > Rule of thumb: response is the same every time → pass it directly. Response
55
+ > depends on the request → wrap it in `() =>`.
56
+
57
+ ## Reading the request
58
+
59
+ The `request` accessor (or the standalone shortcuts) read the current request —
60
+ no `c` needed:
61
+
62
+ ```ts
63
+ request.param("id"); // route parameter
64
+ request.query("q"); // query string
65
+ request.header("authorization");
66
+ await request.json(); // parse a JSON body
67
+
68
+ // standalone equivalents
69
+ param("id"); query("q"); header("authorization"); await body();
70
+ ```
71
+
72
+ `request` also exposes `request.method`, `request.path`, `request.url`,
73
+ `request.status`, and `request.raw` (the underlying web `Request`).
74
+
75
+ ## Writing the response
76
+
77
+ Build responses with the standalone helpers or the `response` accessor — they're
78
+ the same thing:
79
+
80
+ ```ts
81
+ json({ ok: true }); // JSON response
82
+ text("hello"); // plain text
83
+ html("<h1>Hi</h1>"); // HTML
84
+ redirect("/login"); // redirect
85
+
86
+ response.json({ ok: true });
87
+ response.status(201).json(created); // set status, chainable
88
+ response.header("x-total", "42").json(rows);
89
+ ```
90
+
91
+ Returning a **string** from a handler is shorthand — Keel wraps it as HTML.
92
+
93
+ ## The full helper set
94
+
95
+ | Read (`request.*` or standalone) | Write (`response.*` or standalone) |
96
+ |----------------------------------|------------------------------------|
97
+ | `param(name)` · `query(name)` · `header(name)` | `json(data, status?)` · `text()` · `html()` |
98
+ | `body<T>()` (parse JSON body) | `redirect(location, status?)` |
99
+ | `request.method` · `.path` · `.status` · `.raw` | `response.status(code)` · `response.header(k, v)` |
100
+
101
+ All of these are powered by async-context storage the HTTP kernel enables for
102
+ every request, so they only work inside a request. You can always still take `c`
103
+ explicitly — both styles work.
104
+
105
+ ## Named routes & URL generation
106
+
107
+ Name a route, then build its URL by name — no hardcoded paths:
108
+
109
+ ```ts
110
+ router.get("/users/:id", [UserController, "show"]).name("users.show");
111
+
112
+ router.url("users.show", { id: 42 }); // "/users/42"
113
+ ```
114
+
115
+ Building URLs from names — plain and tamper-proof signed URLs — is its own
116
+ topic. See the [URL builder](./url-builder.md) for `router.url()`,
117
+ `router.signedUrl()`, and `router.hasValidSignature()`.
118
+
119
+ ## Route groups
120
+
121
+ Share a prefix, middleware, and/or name prefix across many routes:
122
+
123
+ ```ts
124
+ router
125
+ .group(() => {
126
+ router.get("/status", json({ up: true })).name("status");
127
+ router.get("/me", [MeController, "show"]).name("me");
128
+ })
129
+ .prefix("/api") // -> /api/status, /api/me
130
+ .middleware([auth]) // runs before each route in the group
131
+ .as("api"); // -> names "api.status", "api.me"
132
+ ```
133
+
134
+ Groups nest — inner prefixes and middleware compose with the outer group's.
135
+
136
+ ## Resource routes
137
+
138
+ Generate RESTful routes for a controller in one line:
139
+
140
+ ```ts
141
+ router.resource("posts", PostController);
142
+ ```
143
+
144
+ | Verb | Path | Action |
145
+ |------|------|--------|
146
+ | GET | `/posts` | `index` |
147
+ | GET | `/posts/create` | `create` |
148
+ | POST | `/posts` | `store` |
149
+ | GET | `/posts/:id` | `show` |
150
+ | GET | `/posts/:id/edit` | `edit` |
151
+ | PUT/PATCH | `/posts/:id` | `update` |
152
+ | DELETE | `/posts/:id` | `destroy` |
153
+
154
+ Trim the set with `.only([...])`, `.except([...])`, or `.apiOnly()` (drops the
155
+ HTML-form `create`/`edit` actions).
156
+
157
+ ## Param constraints
158
+
159
+ Constrain a parameter with a regex, a matcher, or a `{ match }` object —
160
+ non-matching requests fall through to a 404:
161
+
162
+ ```ts
163
+ router.get("/users/:id", [UserController, "show"]).where("id", /\d+/);
164
+
165
+ // built-in matchers
166
+ router.get("/u/:id", handler).where("id", router.matchers.number());
167
+ router.get("/a/:id", handler).where("id", router.matchers.uuid());
168
+ router.get("/s/:slug", handler).where("slug", router.matchers.slug());
169
+
170
+ // a global constraint applied to every matching :id
171
+ router.where("id", router.matchers.number());
172
+ ```
173
+
174
+ Groups take constraints too: `group(...).where("id", router.matchers.uuid())`.
175
+
176
+ ## Per-route middleware
177
+
178
+ ```ts
179
+ router.get("/dashboard", [DashboardController, "index"]).middleware([auth]);
180
+ ```
181
+
182
+ ## Brisk routes: redirects, views & Inertia
183
+
184
+ `on()` is a shortcut for routes with no controller:
185
+
186
+ ```ts
187
+ router.on("/old").redirect("/new"); // path/URL redirect
188
+ router.on("/ext").redirectToPath("https://x.com"); // alias of redirect
189
+ router.on("/posts").redirectToRoute("articles.index", {}, { qs: { page: 1 } });
190
+
191
+ router.on("/about").render(AboutPage, { title: "About" }); // render a view
192
+ router.on("/dashboard").renderInertia("Dashboard", { user }); // Inertia page
193
+ ```
194
+
195
+ See [Inertia](./inertia.md) for the full Inertia adapter.
196
+
197
+ ## Domain & subdomain routing
198
+
199
+ Bind routes (or a group) to a host pattern. `:segments` capture subdomain
200
+ params, readable with `request.subdomain()`:
201
+
202
+ ```ts
203
+ router
204
+ .group(() => {
205
+ router.get("/", () => json({ tenant: request.subdomain("tenant") }));
206
+ })
207
+ .domain(":tenant.example.com");
208
+
209
+ router.get("/", [BlogController, "index"]).domain("blog.example.com");
210
+ ```
211
+
212
+ Requests are dispatched by their `Host` header; non-matching hosts fall through
213
+ to your default (undomained) routes.
214
+
215
+ ## Route config
216
+
217
+ Attach arbitrary metadata to a route (or a whole group) with `.config()`, then
218
+ read it in the handler or route middleware via `request.route.config` — for
219
+ per-route flags like an auth scope, a rate tier, or a layout choice:
220
+
221
+ ```ts
222
+ router.get("/admin", [Admin, "index"]).config({ scope: "admin", rateTier: "high" });
223
+
224
+ router
225
+ .group(() => {
226
+ router.get("/billing", [Billing, "index"]); // inherits { area: "billing" }
227
+ router.get("/billing/export", [Billing, "export"]).config({ heavy: true });
228
+ })
229
+ .config({ area: "billing" }); // a route's own config wins on conflict
230
+ ```
231
+
232
+ ```ts
233
+ // in a guard middleware attached to the route/group:
234
+ if (request.route?.config.scope === "admin") await authorize("access-admin");
235
+ ```
236
+
237
+ Group config is merged into every route in the group, with a route's own keys
238
+ winning. Route config is available to **route/group middleware** and the handler
239
+ (not global middleware, which runs before route matching).
240
+
241
+ ## The current route
242
+
243
+ `request.route` exposes the matched route, and `request.routeIs()` checks it:
244
+
245
+ ```ts
246
+ request.route; // { name, pattern, methods, config }
247
+ request.routeIs("posts.show"); // boolean
248
+ ```
249
+
250
+ ## More verbs
251
+
252
+ ```ts
253
+ router.any("/webhook", [HookController, "handle"]); // every verb
254
+ router.route(["GET", "POST"], "/search", handler); // a specific set
255
+ ```
256
+
257
+ ## Inspecting routes
258
+
259
+ ```bash
260
+ npm run keel routes
261
+ ```
262
+
263
+ ```
264
+ GET / HomeController@index
265
+ GET /health Static
266
+ GET /users/:id Closure
267
+ ```
268
+
269
+ ## Adding more route files
270
+
271
+ `bootstrap/app.ts` loads `routes/web.ts`. To split routes (e.g. an `api.ts`),
272
+ import and call it there:
273
+
274
+ ```ts
275
+ import registerWebRoutes from "../routes/web.js";
276
+ import registerApiRoutes from "../routes/api.js";
277
+
278
+ registerWebRoutes(app.make(Router));
279
+ registerApiRoutes(app.make(Router));
280
+ ```
281
+
282
+ ---
283
+
284
+ ## API reference
285
+
286
+ Registration methods live on `Router`. Each verb method hands back a `Route` you
287
+ chain to name, guard, or constrain it; `group()`, `resource()`, and `on()` hand
288
+ back `RouteGroup`, `RouteResource`, and a brisk-route matcher respectively. You
289
+ never construct these classes — the framework builds the `Router` and passes it
290
+ to your routes file, and the rest come back from its methods.
291
+
292
+ URL generation (`url`, `signedUrl`, `hasValidSignature`) also lives on `Router`
293
+ but is documented separately in the [URL builder](./url-builder.md).
294
+
295
+ ### `matchers`
296
+
297
+ `matchers: { number(): RegExp; uuid(): RegExp; slug(): RegExp; alpha(): RegExp }`
298
+
299
+ The built-in parameter matchers, also reachable as `router.matchers`. Each
300
+ returns a fresh (un-anchored) `RegExp` to hand to `.where()`.
301
+
302
+ ```ts
303
+ import { matchers } from "@shaferllc/keel/core";
304
+
305
+ router.get("/u/:id", handler).where("id", matchers.number());
306
+ ```
307
+
308
+ **Notes:** `number` → `\d+`, `uuid` → a canonical UUID, `slug` →
309
+ `a-z0-9` words joined by `-`, `alpha` → letters only. They are plain regexes, so
310
+ you can also pass your own `/.../ ` or a `{ match }` object.
311
+
312
+ ### Router
313
+
314
+ The route registrar. Injected into your routes file; resolve it elsewhere with
315
+ `app.make(Router)`.
316
+
317
+ #### `get(path, handler)` · `post` · `put` · `patch` · `delete`
318
+
319
+ `get(path: string, handler: RouteHandler): Route`
320
+
321
+ Registers a route for the one HTTP verb and returns the `Route` for chaining.
322
+
323
+ ```ts
324
+ router.get("/users/:id", [UserController, "show"]);
325
+ router.post("/users", [UserController, "store"]);
326
+ router.delete("/users/:id", [UserController, "destroy"]);
327
+ ```
328
+
329
+ **Notes:** `post`, `put`, `patch`, and `delete` share the identical signature.
330
+ `handler` is a closure, a `[Controller, "method"]` tuple, or a ready-made
331
+ `Response` — see [`RouteHandler`](#routehandler). Paths are normalized (a
332
+ trailing slash is trimmed; `"/"` stays `"/"`).
333
+
334
+ #### `any(path, handler)`
335
+
336
+ `any(path: string, handler: RouteHandler): Route`
337
+
338
+ Registers the route for every HTTP verb (`GET POST PUT PATCH DELETE OPTIONS HEAD`).
339
+
340
+ ```ts
341
+ router.any("/webhook", [HookController, "handle"]);
342
+ ```
343
+
344
+ #### `route(methods, path, handler)`
345
+
346
+ `route(methods: Method[], path: string, handler: RouteHandler): Route`
347
+
348
+ Registers the route for a specific set of verbs.
349
+
350
+ ```ts
351
+ router.route(["GET", "POST"], "/search", handler);
352
+ ```
353
+
354
+ **Notes:** `Method` is the uppercase verb union — pass them exactly (`"GET"`, not
355
+ `"get"`).
356
+
357
+ #### `on(path)`
358
+
359
+ `on(path: string): RouteMatcher`
360
+
361
+ Opens a brisk-route matcher for controller-less routes (redirects, views,
362
+ Inertia pages). See [`RouteMatcher`](#routematcher).
363
+
364
+ ```ts
365
+ router.on("/old").redirect("/new");
366
+ router.on("/about").render(AboutPage, { title: "About" });
367
+ ```
368
+
369
+ #### `group(callback)`
370
+
371
+ `group(callback: () => void): RouteGroup`
372
+
373
+ Runs `callback` (which registers routes on the router) and returns a
374
+ [`RouteGroup`](#routegroup) wrapping exactly the routes it added, so you can
375
+ apply a shared prefix / middleware / name prefix to them.
376
+
377
+ ```ts
378
+ router
379
+ .group(() => {
380
+ router.get("/status", json({ up: true })).name("status");
381
+ router.get("/me", [MeController, "show"]).name("me");
382
+ })
383
+ .prefix("/api")
384
+ .middleware([auth])
385
+ .as("api");
386
+ ```
387
+
388
+ **Notes:** the grouping (prefix, middleware, name prefix) is applied *after*
389
+ registration by the returned `RouteGroup` — the callback itself sees no prefix.
390
+ Nest by calling `.prefix()` on the inner group before the outer group's; the
391
+ outer prefix is prepended, so `/api` + `/v1/...` composes correctly.
392
+
393
+ #### `resource(name, controller)`
394
+
395
+ `resource(name: string, controller: ControllerRef): RouteResource`
396
+
397
+ Registers the seven RESTful routes (`index create store show edit update
398
+ destroy`) for `controller` and returns a [`RouteResource`](#routeresource) to
399
+ trim or rename them.
400
+
401
+ ```ts
402
+ router.resource("posts", PostController);
403
+ router.resource("posts.comments", CommentController); // nested
404
+ ```
405
+
406
+ **Notes:** each route is auto-named `${name}.${action}`. A dotted `name` nests
407
+ resources — `"posts.comments"` yields `/posts/:post_id/comments/:id`. The
408
+ controller may be a class or a lazy `() => import(...)` loader.
409
+
410
+ #### `where(param, matcher)`
411
+
412
+ `where(param: string, matcher: Matcher): this`
413
+
414
+ Registers a **global** parameter constraint, applied at `all()` time to every
415
+ route whose path contains `:param` and that doesn't already constrain it.
416
+
417
+ ```ts
418
+ router.where("id", matchers.number());
419
+ ```
420
+
421
+ **Notes:** per-route and group `.where()` win over a global one. Returns the
422
+ router for chaining.
423
+
424
+ #### `named(map)`
425
+
426
+ `named(map: Record<string, MiddlewareHandler>): this`
427
+
428
+ Registers named middleware you can later reference by string in `.middleware()`
429
+ / `.use()`.
430
+
431
+ ```ts
432
+ router.named({ auth, admin });
433
+ router.get("/panel", handler).use("auth");
434
+ ```
435
+
436
+ **Notes:** merges into any previously named middleware. Referencing an
437
+ unregistered name throws at resolve time (see `resolveMiddleware`).
438
+
439
+ #### `resolveMiddleware(ref)`
440
+
441
+ `resolveMiddleware(ref: MiddlewareRef): MiddlewareHandler`
442
+
443
+ Resolves a middleware reference — a handler passes through; a string is looked up
444
+ in the `named()` registry.
445
+
446
+ ```ts
447
+ const mw = router.resolveMiddleware("auth");
448
+ ```
449
+
450
+ **Notes:** throws `No named middleware [name]…` if a string isn't registered.
451
+ Mostly used by the HTTP kernel; handy in tests.
452
+
453
+ #### `all()`
454
+
455
+ `all(): RouteDefinition[]`
456
+
457
+ Returns every live route definition, after folding in global `where()`
458
+ constraints and dropping routes trimmed to zero methods (by `only`/`except`).
459
+
460
+ ```ts
461
+ for (const r of router.all()) console.log(r.methods, r.path, r.name);
462
+ ```
463
+
464
+ **Notes:** this is the list the HTTP kernel compiles onto Hono. Trimmed resource
465
+ actions are excluded here, but `url()` can still find them by name.
466
+
467
+ #### `resolve(handler)`
468
+
469
+ `resolve(handler: RouteHandler): HandlerFn`
470
+
471
+ Turns a `RouteHandler` into a callable `(c: Ctx) => …`, resolving controller
472
+ tuples through the container and lazy loaders.
473
+
474
+ ```ts
475
+ const fn = router.resolve([UserController, "show"]);
476
+ ```
477
+
478
+ **Notes:** a bare `[Controller]` tuple calls the controller's `handle` method. A
479
+ `Response` handler is cloned per call. Throws if the named controller method
480
+ doesn't exist. Called by the kernel; you rarely call it directly.
481
+
482
+ ### Route
483
+
484
+ Returned by every verb method (`get`/`post`/…). Chain to name, guard, or
485
+ constrain a single route. Exposes a readonly `def: RouteDefinition`.
486
+
487
+ #### `name(name)` · `as(name)`
488
+
489
+ `name(name: string): this`
490
+
491
+ Names the route for URL generation. `as()` is an alias.
492
+
493
+ ```ts
494
+ router.get("/users/:id", handler).name("users.show");
495
+ router.get("/users/:id", handler).as("users.show");
496
+ ```
497
+
498
+ #### `middleware(mw)` · `use(mw)`
499
+
500
+ `middleware(mw: MiddlewareRef | MiddlewareRef[]): this`
501
+
502
+ Attaches middleware that runs only for this route, after any group middleware.
503
+ `use()` is an alias.
504
+
505
+ ```ts
506
+ router.get("/dashboard", handler).middleware([auth]);
507
+ router.get("/admin", handler).use(["auth", "admin"]);
508
+ ```
509
+
510
+ **Notes:** accepts a single ref or an array; appends (order preserved). A string
511
+ ref is resolved against `named()`.
512
+
513
+ #### `where(param, matcher)`
514
+
515
+ `where(param: string, matcher: Matcher): this`
516
+
517
+ Constrains a route parameter; non-matching requests fall through to a 404.
518
+
519
+ ```ts
520
+ router.get("/users/:id", handler).where("id", /\d+/);
521
+ ```
522
+
523
+ #### `domain(pattern)`
524
+
525
+ `domain(pattern: string): this`
526
+
527
+ Binds the route to a host pattern; `:segments` capture subdomain params.
528
+
529
+ ```ts
530
+ router.get("/", [BlogController, "index"]).domain("blog.example.com");
531
+ ```
532
+
533
+ ### RouteGroup
534
+
535
+ Returned by `group()`. Its fluent methods apply across every route the group
536
+ callback registered. All return `this`.
537
+
538
+ #### `prefix(prefix)`
539
+
540
+ `prefix(prefix: string): this`
541
+
542
+ Prepends a path prefix to every route in the group.
543
+
544
+ ```ts
545
+ router.group(() => { /* … */ }).prefix("/api");
546
+ ```
547
+
548
+ **Notes:** leading/trailing slashes are normalized. Applying to the group's root
549
+ route (`"/"`) yields just the prefix.
550
+
551
+ #### `middleware(mw)` · `use(mw)`
552
+
553
+ `middleware(mw: MiddlewareRef | MiddlewareRef[]): this`
554
+
555
+ Prepends middleware to every route in the group, so group middleware runs before
556
+ each route's own. `use()` is an alias.
557
+
558
+ ```ts
559
+ router.group(() => { /* … */ }).middleware([auth]);
560
+ ```
561
+
562
+ #### `where(param, matcher)`
563
+
564
+ `where(param: string, matcher: Matcher): this`
565
+
566
+ Constrains a parameter across the group, skipping routes that already constrain
567
+ it themselves.
568
+
569
+ ```ts
570
+ router.group(() => { /* … */ }).where("id", matchers.uuid());
571
+ ```
572
+
573
+ #### `as(namePrefix)`
574
+
575
+ `as(namePrefix: string): this`
576
+
577
+ Prefixes the name of every *already-named* route in the group.
578
+
579
+ ```ts
580
+ router.group(() => { /* named routes */ }).as("api"); // status -> api.status
581
+ ```
582
+
583
+ **Notes:** routes without a `name()` are left untouched — name them inside the
584
+ callback for `as()` to reach them.
585
+
586
+ #### `domain(pattern)`
587
+
588
+ `domain(pattern: string): this`
589
+
590
+ Binds every route in the group to a host pattern.
591
+
592
+ ```ts
593
+ router.group(() => { /* … */ }).domain(":tenant.example.com");
594
+ ```
595
+
596
+ ### RouteResource
597
+
598
+ Returned by `resource()`. Chain to trim, rename, or guard the generated actions.
599
+ All return `this`.
600
+
601
+ #### `only(actions)` · `except(actions)`
602
+
603
+ `only(actions: string[]): this`
604
+ `except(actions: string[]): this`
605
+
606
+ Keep only the listed actions, or drop the listed actions.
607
+
608
+ ```ts
609
+ router.resource("posts", PostController).only(["index", "show"]);
610
+ router.resource("posts", PostController).except(["destroy"]);
611
+ ```
612
+
613
+ **Notes:** trimming empties a route's `methods`; `all()` then filters it out. The
614
+ route name still exists, so `url()` can resolve it even when it won't be served.
615
+
616
+ #### `apiOnly()`
617
+
618
+ `apiOnly(): this`
619
+
620
+ Drops the HTML-form actions (`create`, `edit`) — the shorthand for
621
+ `.except(["create", "edit"])`.
622
+
623
+ ```ts
624
+ router.resource("posts", PostController).apiOnly();
625
+ ```
626
+
627
+ #### `as(name)`
628
+
629
+ `as(name: string): this`
630
+
631
+ Renames the route-name prefix for every action.
632
+
633
+ ```ts
634
+ router.resource("posts", PostController).as("articles"); // articles.index, …
635
+ ```
636
+
637
+ #### `params(map)`
638
+
639
+ `params(map: Record<string, string>): this`
640
+
641
+ Renames route parameters. Maps a resource segment to a new param name.
642
+
643
+ ```ts
644
+ router.resource("posts", PostController).params({ posts: "post" }); // :id -> :post
645
+ ```
646
+
647
+ **Notes:** for the resource's own segment the underlying param is `:id`; for a
648
+ parent segment in a nested resource it's `:{singular}_id`. Only the first
649
+ matching occurrence in each path is renamed.
650
+
651
+ #### `use(actions, mw)`
652
+
653
+ `use(actions: string[] | "*", mw: MiddlewareRef | MiddlewareRef[]): this`
654
+
655
+ Attaches middleware to specific actions, or to all with `"*"`.
656
+
657
+ ```ts
658
+ router.resource("posts", PostController)
659
+ .use(["store", "update", "destroy"], "auth")
660
+ .use("*", logRequests);
661
+ ```
662
+
663
+ ### RouteMatcher
664
+
665
+ Returned by `router.on(path)` — a builder for controller-less `GET` routes. Each
666
+ method registers the route and returns the underlying `Route`.
667
+
668
+ #### `redirect(to, status?)` · `redirectToPath(to, status?)`
669
+
670
+ `redirect(to: string, status?: number): Route`
671
+
672
+ Registers a route that redirects to a path or URL (default status `302`).
673
+ `redirectToPath` is an alias.
674
+
675
+ ```ts
676
+ router.on("/old").redirect("/new");
677
+ router.on("/ext").redirectToPath("https://example.com", 301);
678
+ ```
679
+
680
+ #### `redirectToRoute(name, params?, options?)`
681
+
682
+ `redirectToRoute(name: string, params?: Record<string, string | number>, options?: { qs?: Record<string, string | number>; status?: number }): Route`
683
+
684
+ Registers a route that redirects to a named route, resolving its URL (and
685
+ optional query string).
686
+
687
+ ```ts
688
+ router.on("/posts").redirectToRoute("articles.index", {}, { qs: { page: 1 } });
689
+ ```
690
+
691
+ #### `render(component, props?)`
692
+
693
+ `render(component: (props?: any) => unknown, props?: any): Route`
694
+
695
+ Registers a route that renders a view component directly.
696
+
697
+ ```ts
698
+ router.on("/about").render(AboutPage, { title: "About" });
699
+ ```
700
+
701
+ #### `renderInertia(component, props?)`
702
+
703
+ `renderInertia(component: string, props?: Record<string, unknown>): Route`
704
+
705
+ Registers a route that renders an Inertia page component by name. See
706
+ [Inertia](./inertia.md).
707
+
708
+ ```ts
709
+ router.on("/dashboard").renderInertia("Dashboard", { user });
710
+ ```
711
+
712
+ ### Interfaces & types
713
+
714
+ #### `Ctx`
715
+
716
+ `type Ctx = Context` (Hono's request context)
717
+
718
+ The context handed to every handler and middleware. Every closure handler
719
+ receives it, though the `request`/`response` accessors mean you rarely read from
720
+ it directly.
721
+
722
+ ```ts
723
+ router.get("/", (c: Ctx) => c.text("hi"));
724
+ ```
725
+
726
+ #### `RouteHandler`
727
+
728
+ `type RouteHandler = HandlerFn | ControllerAction | Response`
729
+
730
+ What you pass as the second argument to a verb method. One of three shapes:
731
+
732
+ - **`HandlerFn`** — `(c: Ctx) => Response | string | Promise<Response | string>`;
733
+ returning a bare string is wrapped as HTML.
734
+ - **`ControllerAction`** — `[Controller]` (calls `handle`) or
735
+ `[Controller, "method"]`; the controller may be a lazy `() => import(...)`.
736
+ - **`Response`** — a ready-made response, cloned per request.
737
+
738
+ ```ts
739
+ router.get("/a", (c) => c.json({ ok: true })); // HandlerFn
740
+ router.get("/b", [UserController, "show"]); // ControllerAction
741
+ router.get("/c", json({ up: true })); // Response
742
+ ```
743
+
744
+ #### `Matcher`
745
+
746
+ `type Matcher = RegExp | string | { match: RegExp }`
747
+
748
+ A route-parameter constraint accepted by `.where()`. A regex, a regex-source
749
+ string, or a `{ match }` wrapper (the shape the built-in `matchers` conform to).
750
+
751
+ ```ts
752
+ const a: Matcher = /\d+/;
753
+ const b: Matcher = "\\d+";
754
+ const c: Matcher = { match: /[a-z]+/ };
755
+ ```
756
+
757
+ #### `Method`
758
+
759
+ `type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD"`
760
+
761
+ The HTTP verbs. Passed to `route()`; also the type of `RouteDefinition.methods`.
762
+
763
+ ```ts
764
+ router.route(["GET", "POST"] as Method[], "/search", handler);
765
+ ```
766
+
767
+ #### `MiddlewareRef`
768
+
769
+ `type MiddlewareRef = MiddlewareHandler | string`
770
+
771
+ A middleware handler, or the name of one registered with `router.named()`.
772
+ Accepted by every `.middleware()` / `.use()`.
773
+
774
+ ```ts
775
+ router.get("/a", handler).use("auth"); // named
776
+ router.get("/b", handler).use(rateLimiterMw); // handler
777
+ ```
778
+
779
+ #### `RouteDefinition`
780
+
781
+ ```ts
782
+ interface RouteDefinition {
783
+ methods: Method[];
784
+ path: string;
785
+ handler: RouteHandler;
786
+ name?: string;
787
+ middleware: MiddlewareRef[];
788
+ wheres: Record<string, string>;
789
+ domain?: string;
790
+ }
791
+ ```
792
+
793
+ The compiled record for one route — what `all()` returns and the kernel reads.
794
+ You inspect these (e.g. to print a route table); you don't build them by hand.
795
+ `wheres` holds each param's regex *source* string, keyed by param name.
796
+
797
+ ```ts
798
+ for (const def of router.all()) {
799
+ console.log(def.methods.join("|"), def.path, def.name ?? "");
800
+ }
801
+ ```
802
+
803
+ > `UrlOptions` and `SignedUrlOptions` are exported from this module too, but they
804
+ > belong to URL generation — see the [URL builder](./url-builder.md).