hono 1.1.0 → 1.2.1

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
@@ -6,12 +6,6 @@
6
6
 
7
7
  <hr />
8
8
 
9
- <p>
10
- <a href="https://github.com/honojs/hono/blob/master/README.md">English</a>
11
- &#x000B7;
12
- <a href="https://github.com/honojs/hono/blob/master/docs/README.ja.md">日本語</a>
13
- </p>
14
-
15
9
  [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/honojs/hono/ci)](https://github.com/honojs/hono/actions)
16
10
  [![GitHub](https://img.shields.io/github/license/honojs/hono)](https://github.com/honojs/hono/blob/master/LICENSE)
17
11
  [![npm](https://img.shields.io/npm/v/hono)](https://www.npmjs.com/package/hono)
@@ -20,9 +14,9 @@
20
14
  [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/honojs/hono)](https://github.com/honojs/hono/pulse)
21
15
  [![GitHub last commit](https://img.shields.io/github/last-commit/honojs/hono)](https://github.com/honojs/hono/commits/master)
22
16
 
23
- Hono - _**[炎] means flame🔥 in Japanese**_ - is a small, simple, and ultrafast web framework for Cloudflare Workers and Service Worker based serverless such as Fastly Compute@Edge.
17
+ Hono - _**[炎] means flame🔥 in Japanese**_ - is a small, simple, and ultrafast web framework for Cloudflare Workers or Service Worker based serverless such as Fastly Compute@Edge.
24
18
 
25
- ```js
19
+ ```ts
26
20
  import { Hono } from 'hono'
27
21
  const app = new Hono()
28
22
 
@@ -34,24 +28,32 @@ app.fire()
34
28
  ## Features
35
29
 
36
30
  - **Ultrafast** - the router does not use linear loops.
37
- - **Zero-dependencies** - using only Service Worker and Web standard API.
31
+ - **Zero-dependencies** - using only Service Worker and Web Standard API.
38
32
  - **Middleware** - built-in middleware and ability to extend with your own middleware.
39
33
  - **TypeScript** - first-class TypeScript support.
40
- - **Optimized** - for Cloudflare Workers and Fastly Compute@Edge.
34
+ - **Optimized** - for Cloudflare Workers.
41
35
 
42
36
  ## Benchmark
43
37
 
44
38
  **Hono is fastest**, compared to other routers for Cloudflare Workers.
45
39
 
46
40
  ```plain
47
- hono x 809,503 ops/sec ±6.94% (73 runs sampled)
48
- itty-router x 157,310 ops/sec ±4.31% (87 runs sampled)
49
- sunder x 328,350 ops/sec ±2.30% (95 runs sampled)
50
- worktop x 209,758 ops/sec ±4.28% (83 runs sampled)
51
- Fastest is hono
52
- ✨ Done in 60.66s.
41
+ hono - trie-router(default) x 737,602 ops/sec ±3.65% (67 runs sampled)
42
+ hono - regexp-router x 1,188,203 ops/sec ±6.42% (60 runs sampled)
43
+ itty-router x 163,970 ops/sec ±3.05% (91 runs sampled)
44
+ sunder x 344,468 ops/sec ±0.87% (97 runs sampled)
45
+ worktop x 222,044 ops/sec ±2.13% (85 runs sampled)
46
+ Fastest is hono - regexp-router
47
+ ✨ Done in 84.04s.
53
48
  ```
54
49
 
50
+ ## Why so fast?
51
+
52
+ Routers used in Hono are really smart.
53
+
54
+ - **TrieRouter**(default) - Implemented with Trie tree structure.
55
+ - **RegExpRouter** - Match routes with one big Regex made before dispatching at once.
56
+
55
57
  ## Hono in 1 minute
56
58
 
57
59
  A demonstration to create an application for Cloudflare Workers with Hono.
@@ -77,20 +79,47 @@ Built-in middleware make _"**Write Less, do more**"_ in reality. You can use a l
77
79
  - [JSON pretty printing](https://github.com/honojs/hono/tree/master/src/middleware/pretty-json/)
78
80
  - [Serving static files](https://github.com/honojs/hono/tree/master/src/middleware/serve-static/) (Only for Cloudflare Workers)
79
81
 
80
- You can enable logger and CORS middleware with just this code.
82
+ To enable logger and Etag middleware with just this code.
81
83
 
82
- ```js
84
+ ```ts
83
85
  import { Hono } from 'hono'
84
- import { cors } from 'hono/cors'
86
+ import { etag } from 'hono/etag'
85
87
  import { logger } from 'hono/logger'
86
88
 
87
89
  const app = new Hono()
88
- app.use('*', cors()).use(logger())
90
+ app.use('*', etag(), (logger())
91
+ ```
92
+
93
+ And, the routing of Hono is so flexible. It's easy to construct large web applications.
94
+
95
+ ```ts
96
+ import { Hono, Route } from 'hono'
97
+ import { cors } from 'hono/cors'
98
+
99
+ const app = new Hono()
100
+
101
+ const v1 = new Route()
102
+ v1.get('/posts', (c) => {
103
+ return c.text('list pots')
104
+ })
105
+ .post('/posts', cors(), (c) => {
106
+ return c.text('created!', 201)
107
+ })
108
+ .get('/posts/:id', (c) => {
109
+ const id = c.req.param('id')
110
+ return c.text(`your id is ${id}`)
111
+ })
112
+
113
+ app.route('/v1', v1)
89
114
  ```
90
115
 
116
+ ### Web Standard
117
+
118
+ Request and Response object used in Hono are extensions of the Web Standard [Fetch API](https://developer.mozilla.org/ja/docs/Web/API/Fetch_API). If you are familiar with that, you don't need to know more than that.
119
+
91
120
  ### Developer Experience
92
121
 
93
- And Hono provides fine _"**Developer Experience**"_. Easy access to Request/Response thanks to the `Context` object.
122
+ Hono provides fine _"**Developer Experience**"_. Easy access to Request/Response thanks to the `Context` object.
94
123
  Above all, Hono is written in TypeScript. So, Hono has _"**Types**"_!
95
124
 
96
125
  For example, the named path parameters will be literal types.
@@ -101,12 +130,6 @@ For example, the named path parameters will be literal types.
101
130
 
102
131
  You can install Hono from the npm registry.
103
132
 
104
- ```sh
105
- yarn add hono
106
- ```
107
-
108
- or
109
-
110
133
  ```sh
111
134
  npm install hono
112
135
  ```
@@ -115,21 +138,21 @@ npm install hono
115
138
 
116
139
  An instance of `Hono` has these methods.
117
140
 
118
- - app.**HTTP_METHOD**(\[path,\] handler)
119
- - app.**all**(\[path,\] handler)
120
- - app.**route**(path)
141
+ - app.**HTTP_METHOD**(\[path,\] handler|middleware...)
142
+ - app.**all**(\[path,\] handler|middleware...)
143
+ - app.**route**(path, \[Route\])
121
144
  - app.**use**(\[path,\] middleware)
122
145
  - app.**notFound**(handler)
123
146
  - app.**onError**(err, handler)
124
147
  - app.**fire**()
125
148
  - app.**fetch**(request, env, event)
126
- - app.**request**(path, option)
149
+ - app.**request**(path, options)
127
150
 
128
151
  ## Routing
129
152
 
130
153
  ### Basic
131
154
 
132
- ```js
155
+ ```ts
133
156
  // HTTP Methods
134
157
  app.get('/', (c) => c.text('GET /'))
135
158
  app.post('/', (c) => c.text('POST /'))
@@ -145,7 +168,7 @@ app.all('/hello', (c) => c.text('Any Method /hello'))
145
168
 
146
169
  ### Named Parameter
147
170
 
148
- ```js
171
+ ```ts
149
172
  app.get('/user/:name', (c) => {
150
173
  const name = c.req.param('name')
151
174
  ...
@@ -154,7 +177,7 @@ app.get('/user/:name', (c) => {
154
177
 
155
178
  ### Regexp
156
179
 
157
- ```js
180
+ ```ts
158
181
  app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {
159
182
  const date = c.req.param('date')
160
183
  const title = c.req.param('title')
@@ -164,7 +187,7 @@ app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {
164
187
 
165
188
  ### Chained route
166
189
 
167
- ```js
190
+ ```ts
168
191
  app
169
192
  .get('/endpoint', (c) => {
170
193
  return c.text('GET /endpoint')
@@ -177,24 +200,11 @@ app
177
200
  })
178
201
  ```
179
202
 
180
- ### Nested route
181
-
182
- ```js
183
- const book = app.route('/book')
184
- book.get('/', (c) => c.text('List Books')) // GET /book
185
- book.get('/:id', (c) => {
186
- // GET /book/:id
187
- const id = c.req.param('id')
188
- return c.text('Get Book: ' + id)
189
- })
190
- book.post('/', (c) => c.text('Create Book')) // POST /book
191
- ```
192
-
193
203
  ### no strict
194
204
 
195
205
  If `strict` is set false, `/hello`and`/hello/` are treated the same.
196
206
 
197
- ```js
207
+ ```ts
198
208
  const app = new Hono({ strict: false }) // Default is true
199
209
 
200
210
  app.get('/hello', (c) => c.text('/hello or /hello/'))
@@ -209,13 +219,38 @@ app.get('/fetch-url', async (c) => {
209
219
  })
210
220
  ```
211
221
 
222
+ ## Route
223
+
224
+ `Route` object enables Nested route.
225
+
226
+ ```ts
227
+ const book = new Route()
228
+
229
+ book.get('/', (c) => c.text('List Books')) // GET /book
230
+ book.get('/:id', (c) => {
231
+ // GET /book/:id
232
+ const id = c.req.param('id')
233
+ return c.text('Get Book: ' + id)
234
+ })
235
+ book.post('/', (c) => c.text('Create Book')) // POST /book
236
+
237
+ app.route('/book', book)
238
+ ```
239
+
212
240
  ## Middleware
213
241
 
242
+ Middleware operate after/before executing Handler. We can get `Response` before dispatching or manipulate `Response` after dispatching.
243
+
244
+ ### Definition of Middleware
245
+
246
+ - Handler - should return `Response` object.
247
+ - Middleware - should return nothing, do `await next()`
248
+
214
249
  ### Built-in Middleware
215
250
 
216
251
  Hono has built-in middleware.
217
252
 
218
- ```js
253
+ ```ts
219
254
  import { Hono } from 'hono'
220
255
  import { poweredBy } from 'hono/powered-by'
221
256
  import { logger } from 'hono/logger'
@@ -225,8 +260,6 @@ const app = new Hono()
225
260
 
226
261
  app.use('*', poweredBy())
227
262
  app.use('*', logger())
228
- // Or you can write:
229
- // app.use('*', poweredBy()).use(logger())
230
263
 
231
264
  app.use(
232
265
  '/auth/*',
@@ -243,7 +276,7 @@ Available built-in middleware is listed on [src/middleware](https://github.com/h
243
276
 
244
277
  You can write your own middleware.
245
278
 
246
- ```js
279
+ ```ts
247
280
  // Custom logger
248
281
  app.use('*', async (c, next) => {
249
282
  console.log(`[${c.req.method}] ${c.req.url}`)
@@ -282,11 +315,11 @@ app.onError((err, c) => {
282
315
 
283
316
  ## Context
284
317
 
285
- To handle Request and Reponse, you can use `Context` object.
318
+ To handle Request and Response, you can use `Context` object.
286
319
 
287
320
  ### c.req
288
321
 
289
- ```js
322
+ ```ts
290
323
  // Get Request object
291
324
  app.get('/hello', (c) => {
292
325
  const userAgent = c.req.headers.get('User-Agent')
@@ -314,13 +347,15 @@ app.get('/entry/:id', (c) => {
314
347
 
315
348
  ### Shortcuts for Response
316
349
 
317
- ```js
350
+ ```ts
318
351
  app.get('/welcome', (c) => {
319
352
  // Set headers
320
353
  c.header('X-Message', 'Hello!')
321
354
  c.header('Content-Type', 'text/plain')
355
+
322
356
  // Set HTTP status code
323
357
  c.status(201)
358
+
324
359
  // Return the response body
325
360
  return c.body('Thank you for comming')
326
361
  })
@@ -328,14 +363,13 @@ app.get('/welcome', (c) => {
328
363
 
329
364
  The Response is the same as below.
330
365
 
331
- ```js
366
+ ```ts
332
367
  new Response('Thank you for comming', {
333
368
  status: 201,
334
369
  statusText: 'Created',
335
370
  headers: {
336
371
  'X-Message': 'Hello',
337
372
  'Content-Type': 'text/plain',
338
- 'Content-Length': '22',
339
373
  },
340
374
  })
341
375
  ```
@@ -344,7 +378,7 @@ new Response('Thank you for comming', {
344
378
 
345
379
  Render text as `Content-Type:text/plain`.
346
380
 
347
- ```js
381
+ ```ts
348
382
  app.get('/say', (c) => {
349
383
  return c.text('Hello!')
350
384
  })
@@ -354,7 +388,7 @@ app.get('/say', (c) => {
354
388
 
355
389
  Render JSON as `Content-Type:application/json`.
356
390
 
357
- ```js
391
+ ```ts
358
392
  app.get('/api', (c) => {
359
393
  return c.json({ message: 'Hello!' })
360
394
  })
@@ -364,7 +398,7 @@ app.get('/api', (c) => {
364
398
 
365
399
  Render HTML as `Content-Type:text/html`.
366
400
 
367
- ```js
401
+ ```ts
368
402
  app.get('/', (c) => {
369
403
  return c.html('<h1>Hello! Hono!</h1>')
370
404
  })
@@ -374,7 +408,7 @@ app.get('/', (c) => {
374
408
 
375
409
  Return the `Not Found` Response.
376
410
 
377
- ```js
411
+ ```ts
378
412
  app.get('/notfound', (c) => {
379
413
  return c.notFound()
380
414
  })
@@ -384,14 +418,14 @@ app.get('/notfound', (c) => {
384
418
 
385
419
  Redirect, default status code is `302`.
386
420
 
387
- ```js
421
+ ```ts
388
422
  app.get('/redirect', (c) => c.redirect('/'))
389
423
  app.get('/redirect-permanently', (c) => c.redirect('/', 301))
390
424
  ```
391
425
 
392
426
  ### c.res
393
427
 
394
- ```js
428
+ ```ts
395
429
  // Response object
396
430
  app.use('/', (c, next) => {
397
431
  next()
@@ -401,7 +435,7 @@ app.use('/', (c, next) => {
401
435
 
402
436
  ### c.event
403
437
 
404
- ```js
438
+ ```ts
405
439
  // FetchEvent object
406
440
  app.use('*', async (c, next) => {
407
441
  c.event.waitUntil(
@@ -413,7 +447,7 @@ app.use('*', async (c, next) => {
413
447
 
414
448
  ### c.env
415
449
 
416
- ```js
450
+ ```ts
417
451
  // Environment object for Cloudflare Workers
418
452
  app.get('*', async c => {
419
453
  const counter = c.env.COUNTER
@@ -425,7 +459,7 @@ app.get('*', async c => {
425
459
 
426
460
  `app.fire()` do this.
427
461
 
428
- ```js
462
+ ```ts
429
463
  addEventListener('fetch', (event) => {
430
464
  event.respondWith(this.handleEvent(event))
431
465
  })
@@ -435,17 +469,18 @@ addEventListener('fetch', (event) => {
435
469
 
436
470
  `app.fetch` for Cloudflare Module Worker syntax.
437
471
 
438
- ```js
472
+ ```ts
439
473
  export default {
440
474
  fetch(request: Request, env: Env, event: FetchEvent) {
441
475
  return app.fetch(request, env, event)
442
476
  },
443
477
  }
478
+ ```
444
479
 
445
- /*
446
480
  or just do:
481
+
482
+ ```ts
447
483
  export default app
448
- */
449
484
  ```
450
485
 
451
486
  ## request
@@ -461,61 +496,35 @@ test('GET /hello is ok', async () => {
461
496
 
462
497
  ## Cloudflare Workers with Hono
463
498
 
464
- Using [Wrangler](https://developers.cloudflare.com/workers/cli-wrangler/) or [Miniflare](https://miniflare.dev), you can develop the application locally and publish it with few commands.
499
+ Using [Wrangler](https://developers.cloudflare.com/workers/cli-wrangler/), you can develop the application locally and publish it with few commands.
465
500
 
466
501
  Let's write your first code for Cloudflare Workers with Hono.
467
502
 
468
- ---
469
-
470
- ### Caution
471
-
472
- **Wrangler 1.x** does not support importing middleware. We recommend two ways:
473
-
474
- 1. Use [Wragler 2.0 Beta](https://github.com/cloudflare/wrangler2).
475
- 2. Build without webpack 4.x. For example, you can use esbuild. See [the starter template](https://github.com/honojs/hono-minimal).
476
-
477
- ---
478
-
479
- ### 1. `npm init`
480
-
481
- Make a npm skeleton directory.
482
-
483
- ```sh
484
- mkdir hono-example
485
- cd hono-example
486
- npm init -y
487
- ```
488
-
489
- ### 2. `wrangler init`
503
+ ### 1. `wrangler init`
490
504
 
491
505
  Initialize as a wrangler project.
492
506
 
493
- ```sh
494
- npx wrangler@beta init
495
- ```
496
-
497
- Answer the questions. If you want, you can answer `y`.
498
-
499
507
  ```
500
- Would you like to install wrangler into your package.json? (y/n) <--- n
501
- Would you like to use TypeScript? (y/n) <--- n
502
- Would you like to create a Worker at src/index.js? (y/n) <--- n
508
+ mkdir hono-example
509
+ cd hono-example
510
+ npx wrangler init -y
503
511
  ```
504
512
 
505
- ### 3. `npm install hono`
513
+ ### 2. `npm install hono`
506
514
 
507
515
  Install `hono` from the npm registry.
508
516
 
509
- ```sh
517
+ ```
518
+ npm init -y
510
519
  npm i hono
511
520
  ```
512
521
 
513
- ### 4. Write your app
522
+ ### 3. Write your app
514
523
 
515
- Only 4 lines!!
524
+ Edit `src/index.ts`. Only 4 lines!!
516
525
 
517
- ```js
518
- // index.js
526
+ ```ts
527
+ // src/index.ts
519
528
  import { Hono } from 'hono'
520
529
  const app = new Hono()
521
530
 
@@ -524,20 +533,20 @@ app.get('/', (c) => c.text('Hello! Hono!'))
524
533
  app.fire()
525
534
  ```
526
535
 
527
- ### 5. Run
536
+ ### 4. Run
528
537
 
529
538
  Run the development server locally. Then, access `http://127.0.0.1:8787/` in your Web browser.
530
539
 
531
- ```sh
532
- npx wrangler@beta dev index.js
540
+ ```
541
+ npx wrangler dev
533
542
  ```
534
543
 
535
- ### 6. Publish
544
+ ### 5. Publish
536
545
 
537
546
  Deploy to Cloudflare. That's all!
538
547
 
539
- ```sh
540
- npx wrangler@beta publish index.js
548
+ ```
549
+ npx wrangler publish ./src/index.ts
541
550
  ```
542
551
 
543
552
  ## Starter template
@@ -546,8 +555,8 @@ You can start making your Cloudflare Workers application with [the starter templ
546
555
 
547
556
  To generate a project skelton, run this command.
548
557
 
549
- ```sh
550
- wrangler generate my-app https://github.com/honojs/hono-minimal
558
+ ```
559
+ npx create-cloudflare my-app https://github.com/honojs/hono-minimal
551
560
  ```
552
561
 
553
562
  ## Examples
@@ -578,7 +587,7 @@ Contributions Welcome! You can contribute in the following ways.
578
587
 
579
588
  ## Contributors
580
589
 
581
- Thanks to [all contributors](https://github.com/honojs/hono/graphs/contributors)!
590
+ Thanks to [all contributors](https://github.com/honojs/hono/graphs/contributors)! Especially, [@metrue](https://github.com/metrue) and [@usualoma](https://github.com/usualoma)!
582
591
 
583
592
  ## Author
584
593
 
package/dist/compose.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import type { ErrorHandler } from './hono';
2
- export declare const compose: <C>(middleware: Function[], onError?: ErrorHandler) => (context: C) => Promise<C>;
1
+ import type { ErrorHandler, NotFoundHandler } from './hono';
2
+ export declare const compose: <C>(middleware: Function[], onError?: ErrorHandler, onNotFound?: NotFoundHandler) => (context: C, next?: Function) => Promise<C>;
package/dist/compose.js CHANGED
@@ -3,26 +3,38 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.compose = void 0;
4
4
  const context_1 = require("./context");
5
5
  // Based on the code in the MIT licensed `koa-compose` package.
6
- const compose = (middleware, onError) => {
7
- return function (context) {
6
+ const compose = (middleware, onError, onNotFound) => {
7
+ return async (context, next) => {
8
8
  let index = -1;
9
9
  return dispatch(0);
10
10
  async function dispatch(i) {
11
- if (i === middleware.length) {
12
- return context;
13
- }
14
11
  if (i <= index) {
15
12
  return Promise.reject(new Error('next() called multiple times'));
16
13
  }
17
- const handler = middleware[i];
14
+ let handler = middleware[i];
18
15
  index = i;
16
+ if (i === middleware.length)
17
+ handler = next;
18
+ if (handler === undefined) {
19
+ if (context instanceof context_1.Context && context.res === undefined) {
20
+ context.res = onNotFound(context);
21
+ }
22
+ return Promise.resolve(context);
23
+ }
19
24
  return Promise.resolve(handler(context, dispatch.bind(null, i + 1)))
20
- .then(() => {
25
+ .then(async (res) => {
26
+ // If handler return Response like `return c.text('foo')`
27
+ if (res && context instanceof context_1.Context) {
28
+ context.res = res;
29
+ dispatch(i + 1); // <--- Call next()
30
+ }
21
31
  return context;
22
32
  })
23
33
  .catch((err) => {
24
34
  if (onError && context instanceof context_1.Context) {
25
- context.res = onError(err, context);
35
+ if (err instanceof Error) {
36
+ context.res = onError(err, context);
37
+ }
26
38
  return context;
27
39
  }
28
40
  else {
package/dist/context.d.ts CHANGED
@@ -2,24 +2,30 @@
2
2
  import type { StatusCode } from './utils/http-status';
3
3
  declare type Headers = Record<string, string>;
4
4
  declare type Data = string | ArrayBuffer | ReadableStream;
5
- export interface Env {
6
- }
7
- export declare class Context<RequestParamKeyType = string> {
8
- #private;
5
+ export declare type Env = Record<string, any>;
6
+ export declare class Context<RequestParamKeyType = string, E = Env> {
9
7
  req: Request<RequestParamKeyType>;
10
8
  res: Response;
11
- env: Env;
9
+ env: E;
12
10
  event: FetchEvent;
11
+ private _headers;
12
+ private _status;
13
+ private _statusText;
14
+ private _pretty;
15
+ private _prettySpace;
16
+ private _map;
13
17
  render: (template: string, params?: object, options?: object) => Promise<Response>;
14
18
  notFound: () => Response | Promise<Response>;
15
19
  constructor(req: Request<RequestParamKeyType>, opts?: {
16
20
  res: Response;
17
- env: Env;
21
+ env: E;
18
22
  event: FetchEvent;
19
23
  });
20
24
  private initRequest;
21
25
  header(name: string, value: string): void;
22
26
  status(status: StatusCode): void;
27
+ set(key: string, value: any): void;
28
+ get(key: string): any;
23
29
  pretty(prettyJSON: boolean, space?: number): void;
24
30
  newResponse(data: Data, init?: ResponseInit): Response;
25
31
  body(data: Data, status?: StatusCode, headers?: Headers): Response;