Relevant source files
The following files were used as context for generating this wiki page:
- src/middleware/method-override/index.ts
- src/jsx/dom/intrinsic-element/components.ts
- src/middleware/trailing-slash/index.ts
- src/types.ts
- src/jsx/intrinsic-element/components.ts
- src/jsx/dom/hooks/index.ts
- src/request.ts
- src/utils/url.ts
- src/helper/route/index.ts
- src/context.ts
- src/router/linear-router/router.ts
- src/middleware/csrf/index.ts
- src/validator/validator.ts
- package.json
- src/router/reg-exp-router/prepared-router.ts
- src/hono-base.ts
- src/router/trie-router/router.ts
- src/utils/body.ts
- src/router/trie-router/node.ts
- src/helper/ssg/middleware.ts
- src/router/reg-exp-router/router.ts
- src/router/pattern-router/router.ts
- src/middleware/powered-by/index.ts
- src/middleware/pretty-json/index.ts
Overview
The trailing slash and method override middleware modules provide essential URL normalization and protocol adaptation capabilities for Hono applicationsTrailing Slash and Override. The trimTrailingSlash and appendTrailingSlash functions normalize incoming request paths by either stripping or appending trailing slashes, supporting both unconditional redirects and fallback 404 redirection with configurable skip filterssrc/middleware/trailing-slash/index.ts:44-157. Concurrently, the methodOverride middleware enables clients to emulate RESTful mutations via standard HTML forms using custom header detection, query parameters, or form body inspection with _method field resolutionsrc/middleware/method-override/index.ts:11-134. Together, these utilities streamline routing consistency and bridge browser form limitations across web standard runtimes.
Sources: src/middleware/method-override/index.ts:11-134, src/middleware/trailing-slash/index.ts:44-157
Trailing Slash Redirection and Rewriting
Normalization Mechanics
The trailing slash middleware implementations (trimTrailingSlash and appendTrailingSlash) enforce canonical URL structures by inspecting incoming request paths and issuing permanent HTTP 301 redirects. Both factories accept configuration options governing whether redirection occurs prior to handler execution or strictly as a fallback upon encountering a 404 Not Found response status. Redirections are restricted exclusively to GET and HEAD request methods, and root paths (/) are explicitly excluded from path trimming. When alwaysRedirect is disabled, the middleware awaits downstream handler execution (await next()), intercepting responses that yield a 404 status before modifying c.res with a redirected URL instance.
Sources: src/middleware/trailing-slash/index.ts:44-74, src/middleware/trailing-slash/index.ts:128-157
Note
Setting alwaysRedirect: true evaluates the normalization condition before calling downstream handlers, which is required when using wildcard (*) routes that would otherwise match unnormalized paths and prevent 404 fallback triggers.
Sources: src/middleware/trailing-slash/index.ts:8-16, src/middleware/trailing-slash/index.ts:76-84
Options and Skip Filters
The appendTrailingSlash middleware supports a skip callback function allowing specific paths to bypass redirection rules, such as requests targeting files with extensions (/\.\w+$/).
| Option | Type | Default | Description |
|---|---|---|---|
alwaysRedirect | boolean | false | Redirects requests before handler execution instead of waiting for a 404 response status. |
skip | (path: string) => boolean | undefined | Determines whether to bypass the trailing slash redirection for a specific request path. |
Sources: src/middleware/trailing-slash/index.ts:76-101
HTTP Method Override Execution Flow
Middleware Configuration Options
The methodOverride middleware factory accepts a configuration object defining the targeted application instance and the mechanism used to detect override values. The configuration type enforces mutual exclusivity across inspection strategies, ensuring that exactly one detection vector—form, header, or query—is specified per middleware instance. When no override key is explicitly provided for form submissions, the default field name _method is used. Requests arriving with the GET method bypass inspection entirely and immediately invoke next().
| Option | Type | Default | Description |
|---|---|---|---|
app | Hono<any, any, any> | (Required) | The Hono application instance used to dispatch the mutated request. |
form | string | '_method' | Form field name containing the override method name when inspecting request bodies. |
header | string | undefined | HTTP header name containing the override method name. |
query | string | undefined | Query parameter key containing the override method name. |
Sources: src/middleware/method-override/index.ts:11-33, src/middleware/method-override/index.ts:60-64
Inspection and Request Mutation Strategies
When an override condition is met, the middleware clones the incoming request, strips the override indicator from the payload, headers, or query string, and instantiates a new Web Standard Request object with the overridden HTTP method. This newly constructed request is then dispatched directly into the configured Hono application instance using app.fetch(request, c.env, getExecutionCtx(c)).
Warning
When processing multipart/form-data payloads, the middleware explicitly deletes both content-type and content-length headers from the cloned headers collection before constructing the new Request instance so the runtime can correctly re-calculate them for the boundary-delimited body.
Sources: src/middleware/method-override/index.ts:68-134, src/middleware/method-override/index.ts:138-146
For header-based overrides, the specified header name is extracted from the incoming request, deleted from a cloned headers collection, and supplied as the method parameter to the dispatched request. For query-based overrides, the target query parameter is removed from the URL search parameters while preserving the original request body and headers. If no override value is detected by the active inspection branch, the middleware falls back to calling next().
Sources: src/middleware/method-override/index.ts:106-136
Form Body Parsing and Resolution
Call-Chain Execution Walkflows
The form body parsing utility supports both flat form values and complex dot-notation structures. When resolving form data, the execution follows specific call chains depending on whether all values or nested structures are processed.
The first execution sequence handles parsing all values into arrays or flat fields:
methodOverrideinvokesparseBodywhen processingapplication/x-www-form-urlencodedormultipart/form-datarequests.parseBodyinspects the content type header and delegates toparseFormData.parseFormDatacalls(request as Request).formData()and passes the resultingFormDataobject toconvertFormDataToBodyData.convertFormDataToBodyDataiterates over entries, checking whetheroptions.allor a[]key suffix applies, and invokeshandleParsingAllValues.handleParsingAllValuesassigns or pushes multiple values into array structures on the target object.
Sources: src/middleware/method-override/index.ts:92-94, src/utils/body.ts:94-108, src/utils/body.ts:121-132, src/utils/body.ts:142-170, src/utils/body.ts:179-197
The second execution sequence handles nested keys when options.dot is enabled:
methodOverrideinvokesparseBodyfor form submissions.parseBodyforwards the request toparseFormData.parseFormDataretrieves form data and callsconvertFormDataToBodyData.convertFormDataToBodyDataidentifies keys containing dot characters whenoptions.dotis active and triggershandleParsingNestedValues.handleParsingNestedValuessplits the key string by dots, guards against prototype pollution, and instantiates nested objects for intermediate keys.
Sources: src/middleware/method-override/index.ts:92-94, src/utils/body.ts:94-108, src/utils/body.ts:121-132, src/utils/body.ts:142-170, src/utils/body.ts:206-233
Sources: src/middleware/method-override/index.ts:92-94, src/utils/body.ts:94-108, src/utils/body.ts:121-132, src/utils/body.ts:142-170, src/utils/body.ts:179-197, src/utils/body.ts:206-233
Body Parsing Options and Security Protections
The body parsing utility accepts configuration options that dictate how multi-value fields and dot-notation keys are structured in the returned data object.
| Option | Type | Default | Description |
|---|---|---|---|
all | boolean | false | Parses all fields with multiple values or trailing [] brackets as arrays. |
dot | boolean | false | Parses dot-notation keys into nested JavaScript objects. |
Sources: src/utils/body.ts:42-74
Caution
The handleParsingNestedValues function explicitly tests keys against the regular expression /(?:^|\.)__proto__\./ and immediately returns without assigning values if a prototype pollution attempt is detected.
Sources: src/utils/body.ts:206-213
JSX Form Action Integration
Form Component and Server Actions
Hono's JSX implementation supports progressive enhancement for forms through function-valued actions. The form functional component intercepts submission events when client-side DOM rendering is active, inspecting whether the event is trusted and evaluating the action handler. If the action is a function, form prevents default browser submission, extracts form entries into a FormData instance, updates the internal pending and dirty state via useState, and invokes the action function. If the action returns a Promise, it registers the asynchronous operation using registerAction and awaits its resolution.
export const form: FC<
PropsWithChildren<{
action?: Function | string
method?: 'get' | 'post'
ref?: RefObject<HTMLFormElement> | ((e: HTMLFormElement | null) => void | (() => void))
}>
> = (props) => {
const { action, ...restProps } = props
if (typeof action !== 'function') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(restProps as any).action = action
}
const [state, setState] = useState<[FormData | null, boolean]>([null, false]) // [FormData, isDirty]
const onSubmit = useCallback<(ev: SubmitEvent | CustomEvent) => void>(
async (ev: SubmitEvent | CustomEvent) => {
const currentAction = ev.isTrusted
? action
: (ev as CustomEvent).detail[customEventFormAction]
if (typeof currentAction !== 'function') {
return
}
ev.preventDefault()
const formData = new FormData(ev.target as HTMLFormElement)
setState([formData, true])
const actionRes = currentAction(formData)
if (actionRes instanceof Promise) {
registerAction(actionRes)
await actionRes
}
setState([null, true])
},
[]
)
const ref = composeRef(props.ref, (el: HTMLFormElement) => {
el.addEventListener('submit', onSubmit)
return () => {
el.removeEventListener('submit', onSubmit)
}
})
const [data, isDirty] = state
state[1] = false
return {
tag: FormContext as unknown as Function,
props: {
value: {
pending: data !== null,
data,
method: data ? 'post' : null,
action: data ? action : null,
},
children: {
tag: 'form',
props: {
...restProps,
ref,
},
type: 'form',
ref,
},
},
f: isDirty,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any
}Sources: src/jsx/dom/intrinsic-element/components.ts:319-386
Note
When server-side rendering is used instead of the client DOM renderer, the form component checks for a PERMALINK symbol on function actions to resolve string endpoints for non-JS environments.
Sources: src/jsx/intrinsic-element/components.ts:182-192
Form Actionable Elements
Interactive elements inside forms such as inputs and buttons can specify individual action handlers via formAction. The helper function formActionableElement intercepts clicks on elements carrying a function-valued formAction. When clicked, it prevents the default click behavior, locates the parent form element, and dispatches a custom submit event carrying the action function inside detail keyed by a private symbol.
const formActionableElement = (
tag: string,
{
formAction,
...props
}: {
formAction?: Function | string
ref?: RefObject<HTMLInputElement> | ((e: HTMLInputElement) => void | (() => void))
}
) => {
if (typeof formAction === 'function') {
const onClick = useCallback<(ev: MouseEvent) => void>((ev: MouseEvent) => {
ev.preventDefault()
;(ev.currentTarget! as HTMLInputElement).form!.dispatchEvent(
new CustomEvent('submit', { detail: { [customEventFormAction]: formAction } })
)
}, [])
props.ref = composeRef(props.ref, (el: HTMLInputElement) => {
el.addEventListener('click', onClick)
return () => {
el.removeEventListener('click', onClick)
}
})
}
return {
tag,
props,
type: tag,
ref: props.ref,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any
}
Sources: src/jsx/dom/intrinsic-element/components.ts:388-422
Important
The custom event mechanism relies on customEventFormAction symbol keys to securely pass function references across DOM boundaries without leaking private handlers into standard attribute serialization.
Sources: src/jsx/dom/intrinsic-element/components.ts:318-337, src/jsx/dom/intrinsic-element/components.ts:388-403
Edge Runtime and Middleware Packaging
Module Export Layout and Subpath Imports
Hono exposes its built-in utility middleware and runtime features through fine-grained subpath exports defined in package.json. Each subpath provides separate type definitions (types), ESM imports (import), and CommonJS requires (require) to support bundlers, Node.js, and edge runtimes seamlessly.
| Subpath Export | Types Entry | ESM Import Entry | CommonJS Entry |
|---|---|---|---|
./powered-by | ./dist/types/middleware/powered-by/index.d.ts | ./dist/middleware/powered-by/index.js | ./dist/cjs/middleware/powered-by/index.js |
./pretty-json | ./dist/types/middleware/pretty-json/index.d.ts | ./dist/middleware/pretty-json/index.js | ./dist/cjs/middleware/pretty-json/index.js |
./trailing-slash | ./dist/types/middleware/trailing-slash/index.d.ts | ./dist/middleware/trailing-slash/index.js | ./dist/cjs/middleware/trailing-slash/index.js |
./method-override | ./dist/types/middleware/method-override/index.d.ts | ./dist/middleware/method-override/index.js | ./dist/cjs/middleware/method-override/index.js |
Sources: package.json:138-142, package.json:233-247
Lightweight Utility Middleware Packaging
Utility middleware such as poweredBy and prettyJSON are implemented as zero-dependency factories returning standard MiddlewareHandler closures. The poweredBy middleware accepts an optional serverName configuration string defaulting to 'Hono' and appends the X-Powered-By response header after downstream handlers execute.
export const poweredBy = (options?: PoweredByOptions): MiddlewareHandler => {
return async function poweredBy(c, next) {
await next()
c.res.headers.set('X-Powered-By', options?.serverName ?? 'Hono')
}
}Sources: src/middleware/powered-by/index.ts:30-35
Note
The prettyJSON middleware inspects response content types and query parameters according to its PrettyOptions, allowing formatting overrides via a configurable query parameter or the force flag.
Sources: src/middleware/pretty-json/index.ts:8-26, src/middleware/pretty-json/index.ts:46-55
Related
On This Page
OverviewTrailing Slash Redirection and RewritingNormalization MechanicsOptions and Skip FiltersHTTP Method Override Execution FlowMiddleware Configuration OptionsInspection and Request Mutation StrategiesForm Body Parsing and ResolutionCall-Chain Execution WalkflowsBody Parsing Options and Security ProtectionsJSX Form Action IntegrationForm Component and Server ActionsForm Actionable ElementsEdge Runtime and Middleware PackagingModule Export Layout and Subpath ImportsLightweight Utility Middleware PackagingRelated