hono 0.4.0 → 0.5.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.
package/README.md CHANGED
@@ -1,6 +1,14 @@
1
1
  # Hono
2
2
 
3
- Hono[炎] - _**means flame🔥 in Japanese**_ - is small, simple, and ultrafast web framework for a Service Workers API based serverless such as Cloudflare Workers and Fastly Compute@Edge.
3
+ [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/yusukebe/hono/ci)](https://github.com/yusukebe/hono/actions)
4
+ [![GitHub](https://img.shields.io/github/license/yusukebe/hono)](https://github.com/yusukebe/hono/blob/master/LICENSE)
5
+ [![npm](https://img.shields.io/npm/v/hono)](https://www.npmjs.com/package/hono)
6
+ [![npm](https://img.shields.io/npm/dm/hono)](https://www.npmjs.com/package/hono)
7
+ [![npm type definitions](https://img.shields.io/npm/types/hono)](https://www.npmjs.com/package/hono)
8
+ [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/yusukebe/hono)](https://github.com/yusukebe/hono/pulse)
9
+ [![GitHub last commit](https://img.shields.io/github/last-commit/yusukebe/hono)](https://github.com/yusukebe/hono/commits/master)
10
+
11
+ Hono[炎] - _**means flame🔥 in Japanese**_ - is small, simple, and ultrafast web framework for Service Worker based serverless applications like Cloudflare Workers and Fastly Compute@Edge.
4
12
 
5
13
  ```js
6
14
  import { Hono } from 'hono'
@@ -13,9 +21,9 @@ app.fire()
13
21
 
14
22
  ## Features
15
23
 
16
- - **Ultra fast** - the router is implemented with Trie-Tree structure. Not use loops.
17
- - **Zero dependencies** - using only Web standard API.
18
- - **Middleware** - builtin middleware, and you can make your own middleware.
24
+ - **Ultrafast** - the router does not use linear loops.
25
+ - **Zero-dependencies** - using only Web standard API.
26
+ - **Middleware** - builtin middleware and your own middleware.
19
27
  - **Optimized** - for Cloudflare Workers.
20
28
 
21
29
  ## Benchmark
@@ -23,17 +31,17 @@ app.fire()
23
31
  **Hono is fastest** compared to other routers for Cloudflare Workers.
24
32
 
25
33
  ```plain
26
- hono x 708,671 ops/sec ±2.58% (58 runs sampled)
27
- itty-router x 159,610 ops/sec ±2.86% (87 runs sampled)
28
- sunder x 322,846 ops/sec ±2.24% (86 runs sampled)
29
- worktop x 223,625 ops/sec ±2.01% (95 runs sampled)
34
+ hono x 779,197 ops/sec ±6.55% (78 runs sampled)
35
+ itty-router x 161,813 ops/sec ±3.87% (87 runs sampled)
36
+ sunder x 334,096 ops/sec ±1.33% (93 runs sampled)
37
+ worktop x 212,661 ops/sec ±4.40% (81 runs sampled)
30
38
  Fastest is hono
31
- ✨ Done in 57.83s.
39
+ ✨ Done in 58.29s.
32
40
  ```
33
41
 
34
42
  ## Hono in 1 minute
35
43
 
36
- Below is a demonstration to create an application of Cloudflare Workers with Hono.
44
+ A demonstration to create an application of Cloudflare Workers with Hono.
37
45
 
38
46
  ![Demo](https://user-images.githubusercontent.com/10682/151973526-342644f9-71c5-4fee-81f4-64a7558bb192.gif)
39
47
 
@@ -43,26 +51,28 @@ Now, the named path parameter has types.
43
51
 
44
52
  ## Install
45
53
 
46
- You can install Hono from npm registry:
54
+ You can install Hono from the npm registry.
47
55
 
48
56
  ```sh
49
- yarn add hono
57
+ $ yarn add hono
50
58
  ```
51
59
 
52
60
  or
53
61
 
54
62
  ```sh
55
- npm install hono
63
+ $ npm install hono
56
64
  ```
57
65
 
58
66
  ## Methods
59
67
 
60
- An instance of `Hono` has these methods:
68
+ An instance of `Hono` has these methods.
61
69
 
62
70
  - app.**HTTP_METHOD**(path, handler)
63
71
  - app.**all**(path, handler)
64
72
  - app.**route**(path)
65
73
  - app.**use**(path, middleware)
74
+ - app.**notFound**(handler)
75
+ - app.**onError**(err, handler)
66
76
  - app.**fire**()
67
77
  - app.**fetch**(request, env, event)
68
78
 
@@ -70,8 +80,6 @@ An instance of `Hono` has these methods:
70
80
 
71
81
  ### Basic
72
82
 
73
- `app.HTTP_METHOD`
74
-
75
83
  ```js
76
84
  // HTTP Methods
77
85
  app.get('/', (c) => c.text('GET /'))
@@ -81,11 +89,7 @@ app.post('/', (c) => c.text('POST /'))
81
89
  app.get('/wild/*/card', (c) => {
82
90
  return c.text('GET /wild/*/card')
83
91
  })
84
- ```
85
92
 
86
- `app.all`
87
-
88
- ```js
89
93
  // Any HTTP methods
90
94
  app.all('/hello', (c) => c.text('Any Method /hello'))
91
95
  ```
@@ -106,44 +110,33 @@ app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {
106
110
  const date = c.req.param('date')
107
111
  const title = c.req.param('title')
108
112
  ...
113
+ })
109
114
  ```
110
115
 
111
116
  ### Nested route
112
117
 
113
118
  ```js
114
119
  const book = app.route('/book')
115
- book.get('/', (c) => c.text('List Books')) // => GET /book
120
+ book.get('/', (c) => c.text('List Books')) // GET /book
116
121
  book.get('/:id', (c) => {
117
- // => GET /book/:id
122
+ // GET /book/:id
118
123
  const id = c.req.param('id')
119
124
  return c.text('Get Book: ' + id)
120
125
  })
121
- book.post('/', (c) => c.text('Create Book')) // => POST /book
122
- ```
123
-
124
- ### Custom 404 Response
125
-
126
- You can customize 404 Not Found response:
127
-
128
- ```js
129
- app.get('*', (c) => {
130
- return c.text('Custom 404 Error', 404)
131
- })
126
+ book.post('/', (c) => c.text('Create Book')) // POST /book
132
127
  ```
133
128
 
134
129
  ### no strict
135
130
 
136
- If `strict` is set `false`, `/hello`and`/hello/` are treated the same:
131
+ If `strict` is set false, `/hello`and`/hello/` are treated the same.
137
132
 
138
133
  ```js
139
- const app = new Hono({ strict: false })
134
+ const app = new Hono({ strict: false }) // Default is true
140
135
 
141
136
  app.get('/hello', (c) => c.text('/hello or /hello/'))
142
137
  ```
143
138
 
144
- Default is `true`.
145
-
146
- ## async/await
139
+ ### async/await
147
140
 
148
141
  ```js
149
142
  app.get('/fetch-url', async (c) => {
@@ -166,14 +159,20 @@ const app = new Hono()
166
159
 
167
160
  app.use('*', poweredBy())
168
161
  app.use('*', logger())
169
- app.use('/auth/*', basicAuth({ username: 'hono', password: 'acoolproject' }))
162
+ app.use(
163
+ '/auth/*',
164
+ basicAuth({
165
+ username: 'hono',
166
+ password: 'acoolproject',
167
+ })
168
+ )
170
169
  ```
171
170
 
172
171
  Available builtin middleware are listed on [src/middleware](https://github.com/yusukebe/hono/tree/master/src/middleware).
173
172
 
174
173
  ### Custom Middleware
175
174
 
176
- You can write your own middleware:
175
+ You can write your own middleware.
177
176
 
178
177
  ```js
179
178
  // Custom logger
@@ -191,22 +190,32 @@ app.use('/message/*', async (c, next) => {
191
190
  app.get('/message/hello', (c) => c.text('Hello Middleware!'))
192
191
  ```
193
192
 
194
- ### Handling Error
193
+ ## Error
194
+
195
+ ### Not Found
196
+
197
+ `app.notFound` for customizing Not Found Response.
195
198
 
196
199
  ```js
197
- app.use('*', async (c, next) => {
198
- try {
199
- await next()
200
- } catch (err) {
201
- console.error(`${err}`)
202
- c.res = c.text('Custom Error Message', { status: 500 })
203
- }
200
+ app.notFound((c) => {
201
+ return c.text('Custom 404 Message', 404)
202
+ })
203
+ ```
204
+
205
+ ### Error Handling
206
+
207
+ `app.onError` handle the error and return the customized Response.
208
+
209
+ ```js
210
+ app.onError((err, c) => {
211
+ console.error(`${err}`)
212
+ return c.text('Custom Error Message', 500)
204
213
  })
205
214
  ```
206
215
 
207
216
  ## Context
208
217
 
209
- To handle Request and Reponse, you can use Context object:
218
+ To handle Request and Reponse, you can use Context object.
210
219
 
211
220
  ### c.req
212
221
 
@@ -240,30 +249,33 @@ app.get('/entry/:id', (c) => {
240
249
 
241
250
  ```js
242
251
  app.get('/welcome', (c) => {
252
+ // Set headers
243
253
  c.header('X-Message', 'Hello!')
244
254
  c.header('Content-Type', 'text/plain')
255
+ // Set HTTP status code
245
256
  c.status(201)
246
-
257
+ // Return the response body
247
258
  return c.body('Thank you for comming')
259
+ })
260
+ ```
248
261
 
249
- /*
250
- Same as:
251
- return new Response('Thank you for comming', {
252
- status: 201,
253
- statusText: 'Created',
254
- headers: {
255
- 'X-Message': 'Hello',
256
- 'Content-Type': 'text/plain',
257
- 'Content-Length: '22'
258
- }
259
- })
260
- */
262
+ The Response is the same as below.
263
+
264
+ ```js
265
+ new Response('Thank you for comming', {
266
+ status: 201,
267
+ statusText: 'Created',
268
+ headers: {
269
+ 'X-Message': 'Hello',
270
+ 'Content-Type': 'text/plain',
271
+ 'Content-Length': '22',
272
+ },
261
273
  })
262
274
  ```
263
275
 
264
276
  ### c.text()
265
277
 
266
- Render text as `Content-Type:text/plain`:
278
+ Render texts as `Content-Type:text/plain`.
267
279
 
268
280
  ```js
269
281
  app.get('/say', (c) => {
@@ -273,7 +285,7 @@ app.get('/say', (c) => {
273
285
 
274
286
  ### c.json()
275
287
 
276
- Render JSON as `Content-Type:application/json`:
288
+ Render JSON as `Content-Type:application/json`.
277
289
 
278
290
  ```js
279
291
  app.get('/api', (c) => {
@@ -283,7 +295,7 @@ app.get('/api', (c) => {
283
295
 
284
296
  ### c.html()
285
297
 
286
- Render HTML as `Content-Type:text/html`:
298
+ Render HTML as `Content-Type:text/html`.
287
299
 
288
300
  ```js
289
301
  app.get('/', (c) => {
@@ -291,9 +303,19 @@ app.get('/', (c) => {
291
303
  })
292
304
  ```
293
305
 
306
+ ### c.notFound()
307
+
308
+ Return the `404 Not Found` Response.
309
+
310
+ ```js
311
+ app.get('/notfound', (c) => {
312
+ return c.notFound()
313
+ })
314
+ ```
315
+
294
316
  ### c.redirect()
295
317
 
296
- Redirect, default status code is `302`:
318
+ Redirect, default status code is `302`.
297
319
 
298
320
  ```js
299
321
  app.get('/redirect', (c) => c.redirect('/'))
@@ -334,7 +356,7 @@ app.get('*', async c => {
334
356
 
335
357
  ## fire
336
358
 
337
- `app.fire()` do:
359
+ `app.fire()` do this.
338
360
 
339
361
  ```js
340
362
  addEventListener('fetch', (event) => {
@@ -344,7 +366,7 @@ addEventListener('fetch', (event) => {
344
366
 
345
367
  ## fetch
346
368
 
347
- `app.fetch()` is for Cloudflare Module Worker syntax.
369
+ `app.fetch` for Cloudflare Module Worker syntax.
348
370
 
349
371
  ```js
350
372
  export default {
@@ -354,7 +376,7 @@ export default {
354
376
  }
355
377
 
356
378
  /*
357
- or just do this:
379
+ or just do:
358
380
  export default app
359
381
  */
360
382
  ```
@@ -367,15 +389,15 @@ Let's write your first code for Cloudflare Workers with Hono.
367
389
 
368
390
  ### 1. Install Wrangler
369
391
 
370
- Install Cloudflare Command Line "[Wrangler](https://github.com/cloudflare/wrangler)"
392
+ Install Cloudflare Command Line "[Wrangler](https://github.com/cloudflare/wrangler)".
371
393
 
372
394
  ```sh
373
- npm i @cloudflare/wrangler -g
395
+ $ npm i @cloudflare/wrangler -g
374
396
  ```
375
397
 
376
398
  ### 2. `npm init`
377
399
 
378
- Make npm skeleton directory.
400
+ Make a npm skeleton directory.
379
401
 
380
402
  ```sh
381
403
  mkdir hono-example
@@ -388,15 +410,15 @@ npm init -y
388
410
  Init as a wrangler project.
389
411
 
390
412
  ```sh
391
- wrangler init
413
+ $ wrangler init
392
414
  ```
393
415
 
394
416
  ### 4. `npm install hono`
395
417
 
396
- Install `hono` from npm registry.
418
+ Install `hono` from the npm registry.
397
419
 
398
420
  ```sh
399
- npm i hono
421
+ $ npm i hono
400
422
  ```
401
423
 
402
424
  ### 5. Write your app
@@ -414,10 +436,10 @@ app.fire()
414
436
 
415
437
  ### 6. Run
416
438
 
417
- Run the development server locally. Then, access like `http://127.0.0.1:8787/` in your Web browser.
439
+ Run the development server locally. Then, access `http://127.0.0.1:8787/` in your Web browser.
418
440
 
419
441
  ```sh
420
- wrangler dev
442
+ $ wrangler dev
421
443
  ```
422
444
 
423
445
  ### 7. Publish
@@ -425,12 +447,22 @@ wrangler dev
425
447
  Deploy to Cloudflare. That's all!
426
448
 
427
449
  ```sh
428
- wrangler publish
450
+ $ wrangler publish
451
+ ```
452
+
453
+ ## Starter template
454
+
455
+ You can start making your application of Cloudflare Workers with [the starter template](https://github.com/yusukebe/hono-minimal). It is a realy minimal using TypeScript, esbuild, and Miniflare.
456
+
457
+ To generate a project skelton, run this command.
458
+
459
+ ```
460
+ $ wrangler generate my-app https://github.com/yusukebe/hono-minimal
429
461
  ```
430
462
 
431
463
  ## Related projects
432
464
 
433
- Implementation of the router is inspired by [goblin](https://github.com/bmf-san/goblin). API design is inspired by [express](https://github.com/expressjs/express) and [koa](https://github.com/koajs/koa). [itty-router](https://github.com/kwhitley/itty-router), [Sunder](https://github.com/SunderJS/sunder), and [worktop](https://github.com/lukeed/worktop) are the other routers or frameworks for Cloudflare Workers.
465
+ Implementation of the original router `TrieRouter` is inspired by [goblin](https://github.com/bmf-san/goblin). `RegExpRouter` is inspired by [Router::Boom](https://github.com/tokuhirom/Router-Boom). API design is inspired by [express](https://github.com/expressjs/express) and [koa](https://github.com/koajs/koa). [itty-router](https://github.com/kwhitley/itty-router), [Sunder](https://github.com/SunderJS/sunder), and [worktop](https://github.com/lukeed/worktop) are the other routers or frameworks for Cloudflare Workers.
434
466
 
435
467
  - express <https://github.com/expressjs/express>
436
468
  - koa <https://github.com/koajs/koa>
@@ -438,10 +470,11 @@ Implementation of the router is inspired by [goblin](https://github.com/bmf-san/
438
470
  - Sunder <https://github.com/SunderJS/sunder>
439
471
  - goblin <https://github.com/bmf-san/goblin>
440
472
  - worktop <https://github.com/lukeed/worktop>
473
+ - Router::Boom <https://github.com/tokuhirom/Router-Boom>
441
474
 
442
475
  ## Contributing
443
476
 
444
- Contributions Welcome! You can contribute by the following way:
477
+ Contributions Welcome! You can contribute by the following way.
445
478
 
446
479
  - Write or fix documents
447
480
  - Write code of middleware
@@ -449,7 +482,7 @@ Contributions Welcome! You can contribute by the following way:
449
482
  - Refactor the code
450
483
  - etc.
451
484
 
452
- If you can, let's make Hono together!
485
+ Let's make Hono together!
453
486
 
454
487
  ## Contributors
455
488
 
package/dist/compose.d.ts CHANGED
@@ -1 +1,2 @@
1
- export declare const compose: <T>(middleware: Function[]) => (context: T, next?: Function) => Promise<void | object>;
1
+ import type { ErrorHandler } from './hono';
2
+ export declare const compose: <T>(middleware: Function[], onError?: ErrorHandler) => (context: T, next?: Function) => Promise<T>;
package/dist/compose.js CHANGED
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.compose = void 0;
4
+ const context_1 = require("./context");
4
5
  // Based on the code in the MIT licensed `koa-compose` package.
5
- const compose = (middleware) => {
6
- const errors = [];
6
+ const compose = (middleware, onError) => {
7
7
  return function (context, next) {
8
8
  let index = -1;
9
9
  return dispatch(0);
@@ -15,11 +15,20 @@ const compose = (middleware) => {
15
15
  if (i === middleware.length)
16
16
  fn = next;
17
17
  if (!fn)
18
- return Promise.resolve();
18
+ return context;
19
19
  try {
20
- return Promise.resolve(fn(context, dispatch.bind(null, i + 1))).catch((e) => {
21
- errors.push(e);
22
- throw errors[0]; // XXX
20
+ return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
21
+ .then(() => {
22
+ return context;
23
+ })
24
+ .catch((err) => {
25
+ if (onError && context instanceof context_1.Context) {
26
+ context.res = onError(err, context);
27
+ return context;
28
+ }
29
+ else {
30
+ throw err;
31
+ }
23
32
  });
24
33
  }
25
34
  catch (err) {
package/dist/context.d.ts CHANGED
@@ -12,6 +12,7 @@ export declare class Context<RequestParamKeyType = string> {
12
12
  private _status;
13
13
  private _statusText;
14
14
  render: (template: string, params?: object, options?: object) => Promise<Response>;
15
+ notFound: () => Response;
15
16
  constructor(req: Request<RequestParamKeyType>, opts?: {
16
17
  res: Response;
17
18
  env: Env;
package/dist/hono.d.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  /// <reference types="@cloudflare/workers-types" />
2
- import type { Result } from './node';
3
- import { Node } from './node';
4
2
  import { Context } from './context';
5
3
  import type { Env } from './context';
4
+ import type { Result, Router } from './router';
6
5
  declare global {
7
6
  interface Request<ParamKeyType = string> {
8
7
  param: (key: ParamKeyType) => string;
@@ -13,41 +12,39 @@ declare global {
13
12
  }
14
13
  export declare type Handler<RequestParamKeyType = string> = (c: Context<RequestParamKeyType>, next?: Function) => Response | Promise<Response>;
15
14
  export declare type MiddlewareHandler = (c: Context, next: Function) => Promise<void>;
15
+ export declare type NotFoundHandler = (c: Context) => Response;
16
+ export declare type ErrorHandler = (err: Error, c: Context) => Response;
16
17
  declare type ParamKeyName<NameWithPattern> = NameWithPattern extends `${infer Name}{${infer _Pattern}` ? Name : NameWithPattern;
17
18
  declare type ParamKey<Component> = Component extends `:${infer NameWithPattern}` ? ParamKeyName<NameWithPattern> : never;
18
19
  declare type ParamKeys<Path> = Path extends `${infer Component}/${infer Rest}` ? ParamKey<Component> | ParamKeys<Rest> : ParamKey<Path>;
19
- export declare class Router<T> {
20
- node: Node<T>;
21
- constructor();
22
- add(method: string, path: string, handler: T): void;
23
- match(method: string, path: string): Result<T> | null;
24
- }
25
- declare type Init = {
26
- strict?: boolean;
27
- };
28
20
  export declare class Hono {
29
- router: Router<Handler[]>;
21
+ routerClass: {
22
+ new (): Router<any>;
23
+ };
24
+ strict: boolean;
25
+ router: Router<Handler>;
30
26
  middlewareRouters: Router<MiddlewareHandler>[];
31
27
  tempPath: string;
32
- strict: boolean;
33
- constructor(init?: Init);
34
- get<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono;
35
- post<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono;
36
- put<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono;
37
- head<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono;
38
- delete<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono;
39
- options<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono;
40
- patch<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono;
41
- all<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono;
28
+ constructor(init?: Partial<Pick<Hono, 'routerClass' | 'strict'>>);
29
+ notFoundHandler: NotFoundHandler;
30
+ errorHandler: ErrorHandler;
31
+ get<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
32
+ post<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
33
+ put<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
34
+ head<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
35
+ delete<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
36
+ options<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
37
+ patch<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
38
+ all<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
42
39
  route(path: string): Hono;
43
40
  use(path: string, middleware: MiddlewareHandler): void;
44
- addRoute(method: string, path: string, ...args: Handler[]): Hono;
45
- matchRoute(method: string, path: string): Promise<Result<Handler[]>>;
41
+ onError(handler: ErrorHandler): Hono;
42
+ notFound(handler: NotFoundHandler): Hono;
43
+ addRoute(method: string, path: string, handler: Handler): Hono;
44
+ matchRoute(method: string, path: string): Promise<Result<Handler>>;
46
45
  dispatch(request: Request, env?: Env, event?: FetchEvent): Promise<Response>;
47
46
  handleEvent(event: FetchEvent): Promise<Response>;
48
47
  fetch(request: Request, env?: Env, event?: FetchEvent): Promise<Response>;
49
48
  fire(): void;
50
- onError(err: Error): Response;
51
- notFound(): Response;
52
49
  }
53
50
  export {};