0
0
mirror of https://github.com/honojs/hono.git synced 2024-11-22 11:17:33 +01:00

docs: Add JSDoc (#1916)

* docs: Add JSDoc for App

* docs: Add JSDoc for Routing

* docs: Add JSDoc for Context

* docs: Add JSDoc for HonoRequest

* docs: Add JSDoc for Exception

* add .header JSDoc & getPath JSDoc (`nabeken5:feat/add-jsdoc#1`)

* add .header JSDoc & getPath JSDoc

* fix: Apply the format to JSDoc

---------

Thanks @EdamAme-x 
Co-authored-by: Kengo Watanabe <121782456+nabeken5@users.noreply.github.com>

* refactor: format

* feat: run denoify

---------

Co-authored-by: Ame_x <121654029+EdamAme-x@users.noreply.github.com>
This commit is contained in:
Kengo Watanabe 2024-01-21 23:32:30 +09:00 committed by GitHub
parent 64b98b842d
commit 68011665fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 870 additions and 0 deletions

View File

@ -95,10 +95,37 @@ export class Context<
P extends string = any,
I extends Input = {}
> {
/**
* `.req` is the instance of {@link HonoRequest}.
*/
req: HonoRequest<P, I['out']>
/**
* `.env` can get bindings (environment variables, secrets, KV namespaces, D1 database, R2 bucket etc.) in Cloudflare Workers.
* @example
* ```ts
* // Environment object for Cloudflare Workers
* app.get('*', async c => {
* const counter = c.env.COUNTER
* })
* ```
* @see https://hono.dev/api/context#env
*/
env: E['Bindings'] = {}
private _var: E['Variables'] = {}
finalized: boolean = false
/**
* `.error` can get the error object from the middleware if the Handler throws an error.
* @example
* ```ts
* app.use('*', async (c, next) => {
* await next()
* if (c.error) {
* // do something...
* }
* })
* ```
* @see https://hono.dev/api/context#error
*/
error: Error | undefined = undefined
#status: StatusCode = 200
@ -122,6 +149,9 @@ export class Context<
}
}
/**
* @see https://hono.dev/api/context#event
*/
get event(): FetchEventLike {
if (this.#executionCtx && 'respondWith' in this.#executionCtx) {
return this.#executionCtx
@ -130,6 +160,9 @@ export class Context<
}
}
/**
* @see https://hono.dev/api/context#executionctx
*/
get executionCtx(): ExecutionContext {
if (this.#executionCtx) {
return this.#executionCtx as ExecutionContext
@ -138,6 +171,9 @@ export class Context<
}
}
/**
* @see https://hono.dev/api/context#res
*/
get res(): Response {
this.#isFresh = false
return (this.#res ||= new Response('404 Not Found', { status: 404 }))
@ -163,6 +199,40 @@ export class Context<
this.finalized = true
}
/**
* `.render()` can create a response within a layout.
* @example
* ```ts
* app.get('/', (c) => {
* return c.render('Hello!')
* })
* ```
* @see https://hono.dev/api/context#render-setrenderer
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-explicit-any
render: Renderer = (...args: any[]) => this.renderer(...args)
/**
* `.setRenderer()` can set the layout in the custom middleware.
* @example
* ```tsx
* app.use('*', async (c, next) => {
* c.setRenderer((content) => {
* return c.html(
* <html>
* <body>
* <p>{content}</p>
* </body>
* </html>
* )
* })
* await next()
* })
* ```
* @see https://hono.dev/api/context#render-setrenderer
*/
// @ts-expect-error It is unknown how many arguments the renderer will receive.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
render: Renderer = (...args: any[]) => this.renderer(...args)
@ -174,6 +244,20 @@ export class Context<
this.renderer = renderer
}
/**
* `.header()` can set headers.
* @example
* ```ts
* app.get('/welcome', (c) => {
* // Set headers
* c.header('X-Message', 'Hello!')
* c.header('Content-Type', 'text/plain')
*
* return c.body('Thank you for coming')
* })
* ```
* @see https://hono.dev/api/context#body
*/
header = (name: string, value: string | undefined, options?: { append?: boolean }): void => {
// Clear the header
if (value === undefined) {
@ -218,15 +302,46 @@ export class Context<
this.#status = status
}
/**
* `.set()` can set the value specified by the key.
* @example
* ```ts
* app.use('*', async (c, next) => {
* c.set('message', 'Hono is cool!!')
* await next()
* })
* ```
* @see https://hono.dev/api/context#set-get
```
*/
set: Set<E> = (key: string, value: unknown) => {
this._var ??= {}
this._var[key as string] = value
}
/**
* `.get()` can use the value specified by the key.
* @example
* ```ts
* app.get('/', (c) => {
* const message = c.get('message')
* return c.text(`The message is "${message}"`)
* })
* ```
* @see https://hono.dev/api/context#set-get
*/
get: Get<E> = (key: string) => {
return this._var ? this._var[key] : undefined
}
/**
* `.var` can access the value of a variable.
* @example
* ```ts
* const result = c.var.client.oneMethod()
* ```
* @see https://hono.dev/api/context#var
*/
// c.var.propName is a read-only
get var(): Readonly<E['Variables'] & ContextVariableMap> {
return { ...this._var } as never
@ -283,6 +398,25 @@ export class Context<
})
}
/**
* `.body()` can return the HTTP response.
* You can set headers with `.header()` and set HTTP status code with `.status`.
* This can also be set in `.text()`, `.json()` and so on.
* @example
* ```ts
* app.get('/welcome', (c) => {
* // Set headers
* c.header('X-Message', 'Hello!')
* c.header('Content-Type', 'text/plain')
* // Set HTTP status code
* c.status(201)
*
* // Return the response body
* return c.body('Thank you for coming')
* })
* ```
* @see https://hono.dev/api/context#body
*/
body: BodyRespond = (
data: Data | null,
arg?: StatusCode | ResponseInit,
@ -293,6 +427,16 @@ export class Context<
: this.newResponse(data, arg)
}
/**
* `.text()` can render text as `Content-Type:text/plain`.
* @example
* ```ts
* app.get('/say', (c) => {
* return c.text('Hello!')
* })
* ```
* @see https://hono.dev/api/context#text
*/
text: TextRespond = (
text: string,
arg?: StatusCode | ResponseInit,
@ -312,6 +456,16 @@ export class Context<
: this.newResponse(text, arg)
}
/**
* `.json()` can render JSON as `Content-Type:application/json`.
* @example
* ```ts
* app.get('/api', (c) => {
* return c.json({ message: 'Hello!' })
* })
* ```
* @see https://hono.dev/api/context#json
*/
json: JSONRespond = <T>(
object: InterfaceToType<T> extends JSONValue ? T : JSONValue,
arg?: StatusCode | ResponseInit,
@ -361,12 +515,35 @@ export class Context<
: this.newResponse(html as string, arg)
}
/**
* `.redirect()` can Redirect, default status code is 302.
* @example
* ```ts
* app.get('/redirect', (c) => {
* return c.redirect('/')
* })
* app.get('/redirect-permanently', (c) => {
* return c.redirect('/', 301)
* })
* ```
* @see https://hono.dev/api/context#redirect
*/
redirect = (location: string, status: StatusCode = 302): Response => {
this.#headers ??= new Headers()
this.#headers.set('Location', location)
return this.newResponse(null, status)
}
/**
* `.notFound()` can return the Not Found Response.
* @example
* ```ts
* app.get('/notfound', (c) => {
* return c.notFound()
* })
* ```
* @see https://hono.dev/api/context#notfound
*/
notFound = (): Response | Promise<Response> => {
return this.notFoundHandler(this)
}

View File

@ -55,8 +55,38 @@ const errorHandler = (err: Error, c: Context) => {
type GetPath<E extends Env> = (request: Request, options?: { env?: E['Bindings'] }) => string
export type HonoOptions<E extends Env> = {
/**
* `strict` option specifies whether to distinguish whether the last path is a directory or not.
* @default true
* @see https://hono.dev/api/hono#strict-mode
*/
strict?: boolean
/**
* `router` option specifices which router to use.
* ```ts
* const app = new Hono({ router: new RegExpRouter() })
* ```
* @see https://hono.dev/api/hono#router-option
*/
router?: Router<[H, RouterRoute]>
/**
* `getPath` can handle the host header value.
* @example
* ```ts
* const app = new Hono({
* getPath: (req) =>
* '/' + req.headers.get('host') + req.url.replace(/^https?:\/\/[^/]+(\/[^?]*)/, '$1'),
* })
*
* app.get('/www1.example.com/hello', () => c.text('hello www1'))
*
* // A following request will match the route:
* // new Request('http://www1.example.com/hello', {
* // headers: { host: 'www1.example.com' },
* // })
* ```
* @see https://hono.dev/api/routing#routing-with-host-header-value
*/
getPath?: GetPath<E>
}
@ -179,17 +209,43 @@ class Hono<
return this
}
/**
* `.basePath()` allows base paths to be specified.
* @example
* ```ts
* const api = new Hono().basePath('/api')
* ```
* @see https://hono.dev/api/routing#base-path
*/
basePath<SubPath extends string>(path: SubPath): Hono<E, S, MergePath<BasePath, SubPath>> {
const subApp = this.clone()
subApp._basePath = mergePath(this._basePath, path)
return subApp
}
/**
* `.onError()` handles an error and returns a customized Response.
* ```ts
* app.onError((err, c) => {
* console.error(`${err}`)
* return c.text('Custom Error Message', 500)
* })
* ```
*/
onError = (handler: ErrorHandler<E>) => {
this.errorHandler = handler
return this
}
/**
* `.notFound()` allows you to customize a Not Found Response.
* ```ts
* app.notFound((c) => {
* return c.text('Custom 404 Message', 404)
* })
* ```
* @see https://hono.dev/api/hono#not-found
*/
notFound = (handler: NotFoundHandler<E>) => {
this.notFoundHandler = handler
return this
@ -314,10 +370,26 @@ class Hono<
})()
}
/**
* `.fetch()` will be entry point of your app.
* @see https://hono.dev/api/hono#fetch
*/
fetch = (request: Request, Env?: E['Bindings'] | {}, executionCtx?: ExecutionContext) => {
return this.dispatch(request, executionCtx, Env, request.method)
}
/**
* `.request()` is a useful method for testing.
* You can pass a URL or pathname to send a GET request.
* app will return a Response object.
* ```ts
* test('GET /hello is ok', async () => {
* const res = await app.request('/hello')
* expect(res.status).toBe(200)
* })
* ```
* @see https://hono.dev/api/hono#request
*/
request = (
input: RequestInfo | URL,
requestInit?: RequestInit,
@ -336,6 +408,13 @@ class Hono<
return this.fetch(req, Env, executionCtx)
}
/**
* `.fire()` automatically adds a global fetch event listener.
* This can be useful for environments that adhere to the Service Worker API, such as non-ES module Cloudflare Workers.
* @see https://hono.dev/api/hono#fire
* @see https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API
* @see https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/
*/
fire = () => {
// @ts-expect-error `event` is not the type expected by addEventListener
addEventListener('fetch', (event: FetchEventLike): void => {

View File

@ -5,6 +5,24 @@ type HTTPExceptionOptions = {
message?: string
}
/**
* `HTTPException` must be used when a fatal error such as authentication failure occurs.
* @example
* ```ts
* import { HTTPException } from 'hono/http-exception'
*
* // ...
*
* app.post('/auth', async (c, next) => {
* // authentication
* if (authorized === false) {
* throw new HTTPException(401, { message: 'Custom error message' })
* }
* await next()
* })
* ```
* @see https://hono.dev/api/exception
*/
export class HTTPException extends Error {
readonly res?: Response
readonly status: StatusCode

View File

@ -25,11 +25,33 @@ type Body = {
type BodyCache = Partial<Body & { parsedBody: BodyData }>
export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
/**
* `.raw` can get the raw Request object.
* @example
* ```ts
* // For Cloudflare Workers
* app.post('/', async (c) => {
* const metadata = c.req.raw.cf?.hostMetadata?
* ...
* })
* ```
* @see https://hono.dev/api/request#raw
*/
raw: Request
#validatedData: { [K in keyof ValidationTargets]?: {} } // Short name of validatedData
#matchResult: Result<[unknown, RouterRoute]>
routeIndex: number = 0
/**
* `.path` can get the pathname of the request.
* @example
* ```ts
* app.get('/about/me', (c) => {
* const pathname = c.req.path // `/about/me`
* })
* ```
* @see https://hono.dev/api/request#path
*/
path: string
bodyCache: BodyCache = {}
@ -44,6 +66,16 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
this.#validatedData = {}
}
/**
* `.req.param()` gets the path parameters.
* @example
* ```ts
* const name = c.req.param('name')
* // or all parameters at once
* const { id, comment_id } = c.req.param()
* ```
* @see https://hono.dev/api/routing#path-parameter
*/
param<P2 extends string = P>(
key: RemoveQuestion<ParamKeys<P2>>
): UndefinedIfHavingQuestion<ParamKeys<P2>>
@ -76,18 +108,55 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
}
}
/**
* `.query()` can get querystring parameters.
* @example
* ```ts
* // Query params
* app.get('/search', (c) => {
* const query = c.req.query('q')
* })
*
* // Get all params at once
* app.get('/search', (c) => {
* const { q, limit, offset } = c.req.query()
* })
* ```
* @see https://hono.dev/api/request#query
*/
query(key: string): string | undefined
query(): Record<string, string>
query(key?: string) {
return getQueryParam(this.url, key)
}
/**
* `.queries()` can get multiple querystring parameter values, e.g. /search?tags=A&tags=B
* @example
* ```ts
* app.get('/search', (c) => {
* // tags will be string[]
* const tags = c.req.queries('tags')
* })
* ```
* @see https://hono.dev/api/request#queries
*/
queries(key: string): string[] | undefined
queries(): Record<string, string[]>
queries(key?: string) {
return getQueryParams(this.url, key)
}
/**
* `.header()` can get the request header value.
* @example
* ```ts
* app.get('/', (c) => {
* const userAgent = c.req.header('User-Agent')
* })
* ```
* @see https://hono.dev/api/request#header
*/
header(name: string): string | undefined
header(): Record<string, string>
header(name?: string) {
@ -100,6 +169,16 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
return headerData
}
/**
* `.parseBody()` can parse Request body of type `multipart/form-data` or `application/x-www-form-urlencoded`
* @example
* ```ts
* app.post('/entry', async (c) => {
* const body = await c.req.parseBody()
* })
* ```
* @see https://hono.dev/api/request#parsebody
*/
async parseBody<T extends BodyData = BodyData>(options?: ParseBodyOptions): Promise<T> {
if (this.bodyCache.parsedBody) return this.bodyCache.parsedBody as T
const parsedBody = await parseBody<T>(this, options)
@ -123,14 +202,44 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
return (bodyCache[key] = raw[key]())
}
/**
* `.json()` can parse Request body of type `application/json`
* @example
* ```ts
* app.post('/entry', async (c) => {
* const body = await c.req.json()
* })
* ```
* @see https://hono.dev/api/request#json
*/
json<T = any>(): Promise<T> {
return this.cachedBody('json')
}
/**
* `.text()` can parse Request body of type `text/plain`
* @example
* ```ts
* app.post('/entry', async (c) => {
* const body = await c.req.text()
* })
* ```
* @see https://hono.dev/api/request#text
*/
text(): Promise<string> {
return this.cachedBody('text')
}
/**
* `.arrayBuffer()` parse Request body as an `ArrayBuffer`
* @example
* ```ts
* app.post('/entry', async (c) => {
* const body = await c.req.arrayBuffer()
* })
* ```
* @see https://hono.dev/api/request#arraybuffer
*/
arrayBuffer(): Promise<ArrayBuffer> {
return this.cachedBody('arrayBuffer')
}
@ -152,18 +261,70 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
return this.#validatedData[target] as unknown
}
/**
* `.url()` can get the request url strings.
* @example
* ```ts
* app.get('/about/me', (c) => {
* const url = c.req.url // `http://localhost:8787/about/me`
* ...
* })
* ```
* @see https://hono.dev/api/request#url
*/
get url() {
return this.raw.url
}
/**
* `.method()` can get the method name of the request.
* @example
* ```ts
* app.get('/about/me', (c) => {
* const method = c.req.method // `GET`
* })
* ```
* @see https://hono.dev/api/request#method
*/
get method() {
return this.raw.method
}
/**
* `.matchedRoutes()` can return a matched route in the handler
* @example
* ```ts
* app.use('*', async function logger(c, next) {
* await next()
* c.req.matchedRoutes.forEach(({ handler, method, path }, i) => {
* const name = handler.name || (handler.length < 2 ? '[handler]' : '[middleware]')
* console.log(
* method,
* ' ',
* path,
* ' '.repeat(Math.max(10 - path.length, 0)),
* name,
* i === c.req.routeIndex ? '<- respond from here' : ''
* )
* })
* })
* ```
* @see https://hono.dev/api/request#matchedroutes
*/
get matchedRoutes(): RouterRoute[] {
return this.#matchResult[0].map(([[, route]]) => route)
}
/**
* `routePath()` can retrieve the path registered within the handler
* @example
* ```ts
* app.get('/posts/:id', (c) => {
* return c.json({ path: c.req.routePath })
* })
* ```
* @see https://hono.dev/api/request#routepath
*/
get routePath(): string {
return this.#matchResult[0].map(([[, route]]) => route)[this.routeIndex].path
}

View File

@ -95,10 +95,37 @@ export class Context<
P extends string = any,
I extends Input = {}
> {
/**
* `.req` is the instance of {@link HonoRequest}.
*/
req: HonoRequest<P, I['out']>
/**
* `.env` can get bindings (environment variables, secrets, KV namespaces, D1 database, R2 bucket etc.) in Cloudflare Workers.
* @example
* ```ts
* // Environment object for Cloudflare Workers
* app.get('*', async c => {
* const counter = c.env.COUNTER
* })
* ```
* @see https://hono.dev/api/context#env
*/
env: E['Bindings'] = {}
private _var: E['Variables'] = {}
finalized: boolean = false
/**
* `.error` can get the error object from the middleware if the Handler throws an error.
* @example
* ```ts
* app.use('*', async (c, next) => {
* await next()
* if (c.error) {
* // do something...
* }
* })
* ```
* @see https://hono.dev/api/context#error
*/
error: Error | undefined = undefined
#status: StatusCode = 200
@ -122,6 +149,9 @@ export class Context<
}
}
/**
* @see https://hono.dev/api/context#event
*/
get event(): FetchEventLike {
if (this.#executionCtx && 'respondWith' in this.#executionCtx) {
return this.#executionCtx
@ -130,6 +160,9 @@ export class Context<
}
}
/**
* @see https://hono.dev/api/context#executionctx
*/
get executionCtx(): ExecutionContext {
if (this.#executionCtx) {
return this.#executionCtx as ExecutionContext
@ -138,6 +171,9 @@ export class Context<
}
}
/**
* @see https://hono.dev/api/context#res
*/
get res(): Response {
this.#isFresh = false
return (this.#res ||= new Response('404 Not Found', { status: 404 }))
@ -163,6 +199,40 @@ export class Context<
this.finalized = true
}
/**
* `.render()` can create a response within a layout.
* @example
* ```ts
* app.get('/', (c) => {
* return c.render('Hello!')
* })
* ```
* @see https://hono.dev/api/context#render-setrenderer
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-explicit-any
render: Renderer = (...args: any[]) => this.renderer(...args)
/**
* `.setRenderer()` can set the layout in the custom middleware.
* @example
* ```tsx
* app.use('*', async (c, next) => {
* c.setRenderer((content) => {
* return c.html(
* <html>
* <body>
* <p>{content}</p>
* </body>
* </html>
* )
* })
* await next()
* })
* ```
* @see https://hono.dev/api/context#render-setrenderer
*/
// @ts-expect-error It is unknown how many arguments the renderer will receive.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
render: Renderer = (...args: any[]) => this.renderer(...args)
@ -174,6 +244,20 @@ export class Context<
this.renderer = renderer
}
/**
* `.header()` can set headers.
* @example
* ```ts
* app.get('/welcome', (c) => {
* // Set headers
* c.header('X-Message', 'Hello!')
* c.header('Content-Type', 'text/plain')
*
* return c.body('Thank you for coming')
* })
* ```
* @see https://hono.dev/api/context#body
*/
header = (name: string, value: string | undefined, options?: { append?: boolean }): void => {
// Clear the header
if (value === undefined) {
@ -218,15 +302,46 @@ export class Context<
this.#status = status
}
/**
* `.set()` can set the value specified by the key.
* @example
* ```ts
* app.use('*', async (c, next) => {
* c.set('message', 'Hono is cool!!')
* await next()
* })
* ```
* @see https://hono.dev/api/context#set-get
```
*/
set: Set<E> = (key: string, value: unknown) => {
this._var ??= {}
this._var[key as string] = value
}
/**
* `.get()` can use the value specified by the key.
* @example
* ```ts
* app.get('/', (c) => {
* const message = c.get('message')
* return c.text(`The message is "${message}"`)
* })
* ```
* @see https://hono.dev/api/context#set-get
*/
get: Get<E> = (key: string) => {
return this._var ? this._var[key] : undefined
}
/**
* `.var` can access the value of a variable.
* @example
* ```ts
* const result = c.var.client.oneMethod()
* ```
* @see https://hono.dev/api/context#var
*/
// c.var.propName is a read-only
get var(): Readonly<E['Variables'] & ContextVariableMap> {
return { ...this._var } as never
@ -283,6 +398,25 @@ export class Context<
})
}
/**
* `.body()` can return the HTTP response.
* You can set headers with `.header()` and set HTTP status code with `.status`.
* This can also be set in `.text()`, `.json()` and so on.
* @example
* ```ts
* app.get('/welcome', (c) => {
* // Set headers
* c.header('X-Message', 'Hello!')
* c.header('Content-Type', 'text/plain')
* // Set HTTP status code
* c.status(201)
*
* // Return the response body
* return c.body('Thank you for coming')
* })
* ```
* @see https://hono.dev/api/context#body
*/
body: BodyRespond = (
data: Data | null,
arg?: StatusCode | ResponseInit,
@ -293,6 +427,16 @@ export class Context<
: this.newResponse(data, arg)
}
/**
* `.text()` can render text as `Content-Type:text/plain`.
* @example
* ```ts
* app.get('/say', (c) => {
* return c.text('Hello!')
* })
* ```
* @see https://hono.dev/api/context#text
*/
text: TextRespond = (
text: string,
arg?: StatusCode | ResponseInit,
@ -312,6 +456,16 @@ export class Context<
: this.newResponse(text, arg)
}
/**
* `.json()` can render JSON as `Content-Type:application/json`.
* @example
* ```ts
* app.get('/api', (c) => {
* return c.json({ message: 'Hello!' })
* })
* ```
* @see https://hono.dev/api/context#json
*/
json: JSONRespond = <T>(
object: InterfaceToType<T> extends JSONValue ? T : JSONValue,
arg?: StatusCode | ResponseInit,
@ -361,12 +515,35 @@ export class Context<
: this.newResponse(html as string, arg)
}
/**
* `.redirect()` can Redirect, default status code is 302.
* @example
* ```ts
* app.get('/redirect', (c) => {
* return c.redirect('/')
* })
* app.get('/redirect-permanently', (c) => {
* return c.redirect('/', 301)
* })
* ```
* @see https://hono.dev/api/context#redirect
*/
redirect = (location: string, status: StatusCode = 302): Response => {
this.#headers ??= new Headers()
this.#headers.set('Location', location)
return this.newResponse(null, status)
}
/**
* `.notFound()` can return the Not Found Response.
* @example
* ```ts
* app.get('/notfound', (c) => {
* return c.notFound()
* })
* ```
* @see https://hono.dev/api/context#notfound
*/
notFound = (): Response | Promise<Response> => {
return this.notFoundHandler(this)
}

View File

@ -55,8 +55,38 @@ const errorHandler = (err: Error, c: Context) => {
type GetPath<E extends Env> = (request: Request, options?: { env?: E['Bindings'] }) => string
export type HonoOptions<E extends Env> = {
/**
* `strict` option specifies whether to distinguish whether the last path is a directory or not.
* @default true
* @see https://hono.dev/api/hono#strict-mode
*/
strict?: boolean
/**
* `router` option specifices which router to use.
* ```ts
* const app = new Hono({ router: new RegExpRouter() })
* ```
* @see https://hono.dev/api/hono#router-option
*/
router?: Router<[H, RouterRoute]>
/**
* `getPath` can handle the host header value.
* @example
* ```ts
* const app = new Hono({
* getPath: (req) =>
* '/' + req.headers.get('host') + req.url.replace(/^https?:\/\/[^/]+(\/[^?]*)/, '$1'),
* })
*
* app.get('/www1.example.com/hello', () => c.text('hello www1'))
*
* // A following request will match the route:
* // new Request('http://www1.example.com/hello', {
* // headers: { host: 'www1.example.com' },
* // })
* ```
* @see https://hono.dev/api/routing#routing-with-host-header-value
*/
getPath?: GetPath<E>
}
@ -179,17 +209,43 @@ class Hono<
return this
}
/**
* `.basePath()` allows base paths to be specified.
* @example
* ```ts
* const api = new Hono().basePath('/api')
* ```
* @see https://hono.dev/api/routing#base-path
*/
basePath<SubPath extends string>(path: SubPath): Hono<E, S, MergePath<BasePath, SubPath>> {
const subApp = this.clone()
subApp._basePath = mergePath(this._basePath, path)
return subApp
}
/**
* `.onError()` handles an error and returns a customized Response.
* ```ts
* app.onError((err, c) => {
* console.error(`${err}`)
* return c.text('Custom Error Message', 500)
* })
* ```
*/
onError = (handler: ErrorHandler<E>) => {
this.errorHandler = handler
return this
}
/**
* `.notFound()` allows you to customize a Not Found Response.
* ```ts
* app.notFound((c) => {
* return c.text('Custom 404 Message', 404)
* })
* ```
* @see https://hono.dev/api/hono#not-found
*/
notFound = (handler: NotFoundHandler<E>) => {
this.notFoundHandler = handler
return this
@ -314,10 +370,26 @@ class Hono<
})()
}
/**
* `.fetch()` will be entry point of your app.
* @see https://hono.dev/api/hono#fetch
*/
fetch = (request: Request, Env?: E['Bindings'] | {}, executionCtx?: ExecutionContext) => {
return this.dispatch(request, executionCtx, Env, request.method)
}
/**
* `.request()` is a useful method for testing.
* You can pass a URL or pathname to send a GET request.
* app will return a Response object.
* ```ts
* test('GET /hello is ok', async () => {
* const res = await app.request('/hello')
* expect(res.status).toBe(200)
* })
* ```
* @see https://hono.dev/api/hono#request
*/
request = (
input: RequestInfo | URL,
requestInit?: RequestInit,
@ -336,6 +408,13 @@ class Hono<
return this.fetch(req, Env, executionCtx)
}
/**
* `.fire()` automatically adds a global fetch event listener.
* This can be useful for environments that adhere to the Service Worker API, such as non-ES module Cloudflare Workers.
* @see https://hono.dev/api/hono#fire
* @see https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API
* @see https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/
*/
fire = () => {
// @ts-expect-error `event` is not the type expected by addEventListener
addEventListener('fetch', (event: FetchEventLike): void => {

View File

@ -5,6 +5,24 @@ type HTTPExceptionOptions = {
message?: string
}
/**
* `HTTPException` must be used when a fatal error such as authentication failure occurs.
* @example
* ```ts
* import { HTTPException } from 'hono/http-exception'
*
* // ...
*
* app.post('/auth', async (c, next) => {
* // authentication
* if (authorized === false) {
* throw new HTTPException(401, { message: 'Custom error message' })
* }
* await next()
* })
* ```
* @see https://hono.dev/api/exception
*/
export class HTTPException extends Error {
readonly res?: Response
readonly status: StatusCode

View File

@ -25,11 +25,33 @@ type Body = {
type BodyCache = Partial<Body & { parsedBody: BodyData }>
export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
/**
* `.raw` can get the raw Request object.
* @example
* ```ts
* // For Cloudflare Workers
* app.post('/', async (c) => {
* const metadata = c.req.raw.cf?.hostMetadata?
* ...
* })
* ```
* @see https://hono.dev/api/request#raw
*/
raw: Request
#validatedData: { [K in keyof ValidationTargets]?: {} } // Short name of validatedData
#matchResult: Result<[unknown, RouterRoute]>
routeIndex: number = 0
/**
* `.path` can get the pathname of the request.
* @example
* ```ts
* app.get('/about/me', (c) => {
* const pathname = c.req.path // `/about/me`
* })
* ```
* @see https://hono.dev/api/request#path
*/
path: string
bodyCache: BodyCache = {}
@ -44,6 +66,16 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
this.#validatedData = {}
}
/**
* `.req.param()` gets the path parameters.
* @example
* ```ts
* const name = c.req.param('name')
* // or all parameters at once
* const { id, comment_id } = c.req.param()
* ```
* @see https://hono.dev/api/routing#path-parameter
*/
param<P2 extends string = P>(
key: RemoveQuestion<ParamKeys<P2>>
): UndefinedIfHavingQuestion<ParamKeys<P2>>
@ -76,18 +108,55 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
}
}
/**
* `.query()` can get querystring parameters.
* @example
* ```ts
* // Query params
* app.get('/search', (c) => {
* const query = c.req.query('q')
* })
*
* // Get all params at once
* app.get('/search', (c) => {
* const { q, limit, offset } = c.req.query()
* })
* ```
* @see https://hono.dev/api/request#query
*/
query(key: string): string | undefined
query(): Record<string, string>
query(key?: string) {
return getQueryParam(this.url, key)
}
/**
* `.queries()` can get multiple querystring parameter values, e.g. /search?tags=A&tags=B
* @example
* ```ts
* app.get('/search', (c) => {
* // tags will be string[]
* const tags = c.req.queries('tags')
* })
* ```
* @see https://hono.dev/api/request#queries
*/
queries(key: string): string[] | undefined
queries(): Record<string, string[]>
queries(key?: string) {
return getQueryParams(this.url, key)
}
/**
* `.header()` can get the request header value.
* @example
* ```ts
* app.get('/', (c) => {
* const userAgent = c.req.header('User-Agent')
* })
* ```
* @see https://hono.dev/api/request#header
*/
header(name: string): string | undefined
header(): Record<string, string>
header(name?: string) {
@ -100,6 +169,16 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
return headerData
}
/**
* `.parseBody()` can parse Request body of type `multipart/form-data` or `application/x-www-form-urlencoded`
* @example
* ```ts
* app.post('/entry', async (c) => {
* const body = await c.req.parseBody()
* })
* ```
* @see https://hono.dev/api/request#parsebody
*/
async parseBody<T extends BodyData = BodyData>(options?: ParseBodyOptions): Promise<T> {
if (this.bodyCache.parsedBody) return this.bodyCache.parsedBody as T
const parsedBody = await parseBody<T>(this, options)
@ -123,14 +202,44 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
return (bodyCache[key] = raw[key]())
}
/**
* `.json()` can parse Request body of type `application/json`
* @example
* ```ts
* app.post('/entry', async (c) => {
* const body = await c.req.json()
* })
* ```
* @see https://hono.dev/api/request#json
*/
json<T = any>(): Promise<T> {
return this.cachedBody('json')
}
/**
* `.text()` can parse Request body of type `text/plain`
* @example
* ```ts
* app.post('/entry', async (c) => {
* const body = await c.req.text()
* })
* ```
* @see https://hono.dev/api/request#text
*/
text(): Promise<string> {
return this.cachedBody('text')
}
/**
* `.arrayBuffer()` parse Request body as an `ArrayBuffer`
* @example
* ```ts
* app.post('/entry', async (c) => {
* const body = await c.req.arrayBuffer()
* })
* ```
* @see https://hono.dev/api/request#arraybuffer
*/
arrayBuffer(): Promise<ArrayBuffer> {
return this.cachedBody('arrayBuffer')
}
@ -152,18 +261,70 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
return this.#validatedData[target] as unknown
}
/**
* `.url()` can get the request url strings.
* @example
* ```ts
* app.get('/about/me', (c) => {
* const url = c.req.url // `http://localhost:8787/about/me`
* ...
* })
* ```
* @see https://hono.dev/api/request#url
*/
get url() {
return this.raw.url
}
/**
* `.method()` can get the method name of the request.
* @example
* ```ts
* app.get('/about/me', (c) => {
* const method = c.req.method // `GET`
* })
* ```
* @see https://hono.dev/api/request#method
*/
get method() {
return this.raw.method
}
/**
* `.matchedRoutes()` can return a matched route in the handler
* @example
* ```ts
* app.use('*', async function logger(c, next) {
* await next()
* c.req.matchedRoutes.forEach(({ handler, method, path }, i) => {
* const name = handler.name || (handler.length < 2 ? '[handler]' : '[middleware]')
* console.log(
* method,
* ' ',
* path,
* ' '.repeat(Math.max(10 - path.length, 0)),
* name,
* i === c.req.routeIndex ? '<- respond from here' : ''
* )
* })
* })
* ```
* @see https://hono.dev/api/request#matchedroutes
*/
get matchedRoutes(): RouterRoute[] {
return this.#matchResult[0].map(([[, route]]) => route)
}
/**
* `routePath()` can retrieve the path registered within the handler
* @example
* ```ts
* app.get('/posts/:id', (c) => {
* return c.json({ path: c.req.routePath })
* })
* ```
* @see https://hono.dev/api/request#routepath
*/
get routePath(): string {
return this.#matchResult[0].map(([[, route]]) => route)[this.routeIndex].path
}