UNPKG

22 kBMarkdownView Raw
1<div align="center">
2 <img src="https://github.com/lukeed/polka/raw/master/polka.png" alt="Polka" width="400" height="300" />
3</div>
4
5<h1 align="center">Polka</h1>
6
7<div align="center">
8 <a href="https://npmjs.org/package/polka">
9 <img src="https://badgen.now.sh/npm/v/polka" alt="version" />
10 </a>
11 <a href="https://travis-ci.org/lukeed/polka">
12 <img src="https://badgen.now.sh/travis/lukeed/polka" alt="travis" />
13 </a>
14 <a href="https://codecov.io/gh/lukeed/polka">
15 <img src="https://badgen.now.sh/codecov/c/github/lukeed/polka" alt="codecov" />
16 </a>
17 <a href="https://npmjs.org/package/polka">
18 <img src="https://badgen.now.sh/npm/dm/polka" alt="downloads" />
19 </a>
20 <a href="https://packagephobia.now.sh/result?p=polka">
21 <img src="https://packagephobia.now.sh/badge?p=polka" alt="install size" />
22 </a>
23</div>
24
25<div align="center">A micro web server so fast, it'll make you dance! :dancers:</div>
26
27<br />
28
29Polka is an extremely minimal, highly performant Express.js alternative. Yes, you're right, Express is _already_ super fast & not _that_ big :thinking: &mdash; but Polka shows that there was (somehow) room for improvement!
30
31Essentially, Polka is just a [native HTTP server](https://nodejs.org/dist/latest-v9.x/docs/api/http.html#http_class_http_server) with added support for routing, middleware, and sub-applications. That's it! :tada:
32
33And, of course, in mandatory bullet-point format:
34
35* 33-50% faster than Express for simple applications
36* Middleware support, including Express middleware you already know & love
37* Nearly identical application API & route pattern definitions
38* ~90 LOC for Polka, 120 including [its router](https://github.com/lukeed/trouter)
39
40
41## Install
42
43```
44$ npm install --save polka
45```
46
47## Usage
48
49```js
50const polka = require('polka');
51
52function one(req, res, next) {
53 req.hello = 'world';
54 next();
55}
56
57function two(req, res, next) {
58 req.foo = '...needs better demo 😔';
59 next();
60}
61
62polka()
63 .use(one, two)
64 .get('/users/:id', (req, res) => {
65 console.log(`~> Hello, ${req.hello}`);
66 res.end(`User: ${req.params.id}`);
67 })
68 .listen(3000, err => {
69 if (err) throw err;
70 console.log(`> Running on localhost:3000`);
71 });
72```
73
74## API
75
76Polka extends [Trouter](https://github.com/lukeed/trouter) which means it inherits its API, too!
77
78### polka(options)
79
80Returns an instance of Polka~!
81
82#### options.server
83Type: `Server`<br>
84
85A custom, instantiated server that the Polka instance should attach its [`handler`](#handlerreq-res-parsed) to. This is useful if you have initialized a server elsewhere in your application and want Polka to use _it_ instead of creating a new `http.Server`.
86
87Polka _only_ updates the server when [`polka.listen`](#listen) is called. At this time, Polka will create a [`http.Server`](https://nodejs.org/api/http.html#http_class_http_server) if a server was not already provided via `options.server`.
88
89> **Important:** The `server` key will be `undefined` until `polka.listen` is invoked, unless a server was provided.
90
91#### options.onError
92Type: `Function`
93
94A catch-all error handler; executed whenever a middleware throws an error. Change this if you don't like default behavior.
95
96Its signature is `(err, req, res, next)`, where `err` is the `String` or `Error` thrown by your middleware.
97
98> **Caution:** Use `next()` to bypass certain errors **at your own risk!** <br>You must be certain that the exception will be handled elsewhere or _can_ be safely ignored. <br>Otherwise your response will never terminate!
99
100#### options.onNoMatch
101Type: `Function`
102
103A handler when no route definitions were matched. Change this if you don't like default behavior, which sends a `404` status & `Not found` response.
104
105Its signature is `(req, res)` and requires that you terminate the response.
106
107
108### use(base, ...fn)
109
110Attach [middleware(s)](#middleware) and/or sub-application(s) to the server. These will execute _before_ your routes' [handlers](#handlers).
111
112**Important:** If a `base` pathname is provided, all functions within the same `use()` block will _only_ execute when the `req.path` matches the `base` path.
113
114#### base
115Type: `String`<br>
116Default: `undefined`
117
118The base path on which the following middleware(s) or sub-application should be mounted.
119
120#### fn
121Type: `Function|Array`
122
123You may pass one or more functions at a time. Each function must have the standardized `(req, res, next)` signature.
124
125You may also pass a sub-application, which _must_ be accompanied by a `base` pathname.
126
127Please see [`Middleware`](#middleware) and [Express' middleware examples](http://expressjs.com/en/4x/api.html#middleware-callback-function-examples) for more info.
128
129
130### parse(req)
131
132Returns: `Object` or `undefined`
133
134As of `v0.5.0`, this is an alias of the [`@polka/url`](/packages/url) module. For nearly all cases, you'll notice no changes.
135
136But, for whatever reason, you can quickly swap in [`parseurl`](https://github.com/pillarjs/parseurl) again:
137
138```js
139const app = polka();
140app.parse = require('parseurl');
141//=> Done!
142```
143
144### listen()
145
146Returns: `Polka`
147
148Boots (or creates) the underlying [`http.Server`](https://nodejs.org/dist/latest-v9.x/docs/api/http.html#http_class_http_server) for the first time. All arguments are passed to [`server.listen`](https://nodejs.org/dist/latest-v9.x/docs/api/net.html#net_server_listen) directly with no changes.
149
150As of `v0.5.0`, this method no longer returns a Promise. Instead, the current Polka instance is returned directly, allowing for chained operations.
151
152```js
153// Could not do this before 0.5.0
154const { server, handler } = polka().listen();
155
156// Or this!
157const app = polka().listen(PORT, onAppStart);
158
159app.use('users', require('./users'))
160 .get('/', (req, res) => {
161 res.end('Pretty cool!');
162 });
163```
164
165### handler(req, res, parsed)
166
167The main Polka [`IncomingMessage`](https://nodejs.org/api/http.html#http_class_http_incomingmessage) handler. It receives all requests and tries to match the incoming URL against known routes.
168
169If the `req.url` is not immediately matched, Polka will look for sub-applications or middleware groups matching the `req.url`'s [`base`](#base) path. If any are found, they are appended to the loop, running _after_ any global middleware.
170
171> **Note:** Any middleware defined within a sub-application is run _after_ the main app's (aka, global) middleware and _before_ the sub-application's route handler.
172
173At the end of the loop, if a middleware hasn't yet terminated the response (or thrown an error), the route handler will be executed, if found &mdash; otherwise a `(404) Not found` response is returned, configurable via [`options.onNoMatch`](#optionsonnomatch).
174
175#### req
176Type: `IncomingMessage`
177
178#### res
179Type: `ServerResponse`
180
181#### parsed
182Type: `Object`
183
184Optionally provide a parsed [URL](https://nodejs.org/dist/latest-v9.x/docs/api/url.html#url_class_url) object. Useful if you've already parsed the incoming path. Otherwise, [`app.parse`](#parsereq) (aka [`parseurl`](https://github.com/pillarjs/parseurl)) will run by default.
185
186
187## Routing
188
189Routes are used to define how an application responds to varying HTTP methods and endpoints.
190
191> If you're coming from Express, there's nothing new here!<br> However, do check out [Comparisons](#comparisons) for some pattern changes.
192
193### Basics
194
195Each route is comprised of a [path pattern](#patterns), a [HTTP method](#methods), and a [handler](#handlers) (aka, what you want to do).
196
197In code, this looks like:
198
199```js
200app.METHOD(pattern, handler);
201```
202
203wherein:
204
205* `app` is an instance of `polka`
206* [`METHOD`](#methods) is any valid HTTP/1.1 method, lowercased
207* [`pattern`](#patterns) is a routing pattern (string)
208* [`handler`](#handlers) is the function to execute when `pattern` is matched
209
210Also, a single pathname (or `pattern`) may be reused with multiple METHODs.
211
212The following example demonstrates some simple routes.
213
214```js
215const app = polka();
216
217app.get('/', (req, res) => {
218 res.end('Hello world!');
219});
220
221app.get('/users', (req, res) => {
222 res.end('Get all users!');
223});
224
225app.post('/users', (req, res) => {
226 res.end('Create a new User!');
227});
228
229app.put('/users/:id', (req, res) => {
230 res.end(`Update User with ID of ${req.params.id}`);
231});
232
233app.delete('/users/:id', (req, res) => {
234 res.end(`CY@ User ${req.params.id}!`);
235});
236```
237
238### Patterns
239
240Unlike the very popular [`path-to-regexp`](https://github.com/pillarjs/path-to-regexp), Polka uses string comparison to locate route matches. While [faster](https://github.com/lukeed/matchit#benchmarks) & more memory efficient, this does also prevent complex pattern matching.
241
242However, have no fear! :boom: All the basic and most commonly used patterns are supported. You probably only ever used these patterns in the first place. :wink:
243
244> See [Comparisons](#comparisons) for the list of `RegExp`-based patterns that Polka does not support.
245
246The supported pattern types are:
247
248* static (`/users`)
249* named parameters (`/users/:id`)
250* nested parameters (`/users/:id/books/:title`)
251* optional parameters (`/users/:id?/books/:title?`)
252* any match / wildcards (`/users/*`)
253
254### Parameters
255
256Any named parameters included within your route [`pattern`](#patterns) will be automatically added to your incoming `req` object. All parameters will be found within `req.params` under the same name they were given.
257
258> **Important:** Your parameter names should be unique, as shared names will overwrite each other!
259
260```js
261app.get('/users/:id/books/:title', (req, res) => {
262 let { id, title } = req.params;
263 res.end(`User: ${id} && Book: ${title}`);
264});
265```
266
267```sh
268$ curl /users/123/books/Narnia
269#=> User: 123 && Book: Narnia
270```
271
272### Methods
273
274Any valid HTTP/1.1 method is supported! However, only the most common methods are used throughout this documentation for demo purposes.
275
276> **Note:** For a full list of valid METHODs, please see [this list](https://github.com/lukeed/trouter#method).
277
278### Handlers
279
280Request handlers accept the incoming [`IncomingMessage`](https://nodejs.org/api/http.html#http_class_http_incomingmessage) and the formulating [`ServerResponse`](https://nodejs.org/dist/latest-v9.x/docs/api/http.html#http_class_http_serverresponse).
281
282Every route definition must contain a valid `handler` function, or else an error will be thrown at runtime.
283
284> **Important:** You must _always_ terminate a `ServerResponse`!
285
286It's a **very good** practice to _always_ terminate your response ([`res.end`](https://nodejs.org/api/http.html#http_request_end_data_encoding_callback)) inside a handler, even if you expect a [middleware](#middleware) to do it for you. In the event a response is/was not terminated, the server will hang & eventually exit with a `TIMEOUT` error.
287
288> **Note:** This is a native `http` behavior.
289
290#### Async Handlers
291
292If using Node 7.4 or later, you may leverage native `async` and `await` syntax! :heart_eyes_cat:
293
294No special preparation is needed &mdash; simply add the appropriate keywords.
295
296```js
297const app = polka();
298
299const sleep = ms => new Promise(r => setTimeout(r, ms));
300
301async function authenticate(req, res, next) {
302 let token = req.headers['authorization'];
303 if (!token) return (res.statusCode=401,res.end('No token!'));
304 req.user = await Users.find(token); // <== fake
305 next(); // done, woot!
306}
307
308app
309 .use(authenticate)
310 .get('/', async (req, res) => {
311 // log middleware's findings
312 console.log('~> current user', req.user);
313 // force sleep, because we can~!
314 await sleep(500);
315 // send greeting
316 res.end(`Hello, ${req.user.name}`);
317 });
318```
319
320
321## Middleware
322
323Middleware are functions that run in between (hence "middle") receiving the request & executing your route's [`handler`](#handlers) response.
324
325> Coming from Express? Use any middleware you already know & love! :tada:
326
327The middleware signature receives the request (`req`), the response (`res`), and a callback (`next`).
328
329These can apply mutations to the `req` and `res` objects, and unlike Express, have access to `req.params`, `req.path`, `req.search`, and `req.query`!
330
331Most importantly, a middleware ***must*** either call `next()` or terminate the response (`res.end`). Failure to do this will result in a never-ending response, which will eventually crash the `http.Server`.
332
333```js
334// Log every request
335function logger(req, res, next) {
336 console.log(`~> Received ${req.method} on ${req.url}`);
337 next(); // move on
338}
339
340function authorize(req, res, next) {
341 // mutate req; available later
342 req.token = req.headers['authorization'];
343 req.token ? next() : ((res.statusCode=401) && res.end('No token!'));
344}
345
346polka().use(logger, authorize).get('*', (req, res) => {
347 console.log(`~> user token: ${req.token}`);
348 res.end('Hello, valid user');
349});
350```
351
352```sh
353$ curl /
354# ~> Received GET on /
355#=> (401) No token!
356
357$ curl -H "authorization: secret" /foobar
358# ~> Received GET on /foobar
359# ~> user token: secret
360#=> (200) Hello, valid user
361```
362
363### Middleware Sequence
364
365In Polka, middleware functions are organized into tiers.
366
367Unlike Express, Polka middleware are tiered into "global", "filtered", and "route-specific" groups.
368
369* Global middleware are defined via `.use('/', ...fn)` or `.use(...fn)`, which are synonymous.<br>_Because_ every request's `pathname` begins with a `/`, this tier is always triggered.
370
371* Sub-group or "filtered" middleware are defined with a base `pathname` that's more specific than `'/'`. For example, defining `.use('/users', ...fn)` will run on any `/users/**/*` request.<br>These functions will execute _after_ "global" middleware but before the route-specific handler.
372
373* Route handlers match specific paths and execute last in the chain. They must also match the `method` action.
374
375Once the chain of middleware handler(s) has been composed, Polka will iterate through them sequentially until all functions have run, until a chain member has terminated the response early, or until a chain member has thrown an error.
376
377Contrast this with Express, which does not tier your middleware and instead iterates through your entire application in the sequence that you composed it.
378
379```js
380// Express
381express()
382 .get('/', get)
383 .use(foo)
384 .get('/users/123', user)
385 .use('/users', users)
386
387// Polka
388Polka()
389 .get('/', get)
390 .use(foo)
391 .get('/users/123', user)
392 .use('/users', users)
393```
394
395```sh
396$ curl {APP}/
397# Express :: [get]
398# Polka :: [foo, get]
399
400$ curl {APP}/users/123
401# Express :: [foo, user]
402# Polka :: [foo, users, user]
403```
404
405
406### Middleware Errors
407
408If an error arises within a middleware, the loop will be exited. This means that no other middleware will execute & neither will the route handler.
409
410Similarly, regardless of `statusCode`, an early response termination will also exit the loop & prevent the route handler from running.
411
412There are three ways to "throw" an error from within a middleware function.
413
414> **Hint:** None of them use `throw` :joy_cat:
415
4161. **Pass any string to `next()`**
417
418 This will exit the loop & send a `500` status code, with your error string as the response body.
419
420 ```js
421 polka()
422 .use((req, res, next) => next('💩'))
423 .get('*', (req, res) => res.end('wont run'));
424 ```
425
426 ```sh
427 $ curl /
428 #=> (500) 💩
429 ```
430
4312. **Pass an `Error` to `next()`**
432
433 This is similar to the above option, but gives you a window in changing the `statusCode` to something other than the `500` default.
434
435 ```js
436 function oopsies(req, res, next) {
437 let err = new Error('Try again');
438 err.code = 422;
439 next(err);
440 }
441 ```
442
443 ```sh
444 $ curl /
445 #=> (422) Try again
446 ```
447
4483. **Terminate the response early**
449
450 Once the response has been ended, there's no reason to continue the loop!
451
452 This approach is the most versatile as it allows to control every aspect of the outgoing `res`.
453
454 ```js
455 function oopsies(req, res, next) {
456 if (true) {
457 // something bad happened~
458 res.writeHead(400, {
459 'Content-Type': 'application/json',
460 'X-Error-Code': 'Please dont do this IRL'
461 });
462 let json = JSON.stringify({ error:'Missing CSRF token' });
463 res.end(json);
464 } else {
465 next(); // never called FYI
466 }
467 }
468 ```
469
470 ```sh
471 $ curl /
472 #=> (400) {"error":"Missing CSRF token"}
473 ```
474
475
476## Benchmarks
477
478Quick comparison between various frameworks using [`wrk`](https://github.com/wg/wrk) on `Node v10.4.0`.<br> Results are taken with the following command, after one warm-up run:
479
480```
481$ wrk -t4 -c4 -d10s http://localhost:3000/users/123
482```
483
484Additional benchmarks between Polka and Express (using various Node versions) can be [found here](/bench).
485
486> **Important:** Time is mostly spent in _your application code_ rather than Express or Polka code!<br> Switching from Express to Polka will (likely) not show such drastic performance gains.
487
488```
489Native
490 Thread Stats Avg Stdev Max +/- Stdev
491 Latency 1.96ms 119.06us 5.33ms 92.57%
492 Req/Sec 12.78k 287.46 13.13k 90.00%
493 508694 requests in 10.00s, 50.45MB read
494 Requests/sec: 50867.22
495 Transfer/sec: 5.05MB
496
497Polka
498 Thread Stats Avg Stdev Max +/- Stdev
499 Latency 1.98ms 119.26us 4.45ms 92.87%
500 Req/Sec 12.68k 287.74 13.05k 94.06%
501 509817 requests in 10.10s, 50.56MB read
502 Requests/sec: 50475.67
503 Transfer/sec: 5.01MB
504
505Rayo
506 Thread Stats Avg Stdev Max +/- Stdev
507 Latency 2.02ms 116.55us 6.66ms 92.55%
508 Req/Sec 12.43k 262.32 12.81k 91.58%
509 499795 requests in 10.10s, 49.57MB read
510 Requests/sec: 49481.55
511 Transfer/sec: 4.91MB
512
513Fastify
514 Thread Stats Avg Stdev Max +/- Stdev
515 Latency 2.10ms 138.04us 5.46ms 91.50%
516 Req/Sec 11.96k 414.14 15.82k 95.04%
517 479518 requests in 10.10s, 66.31MB read
518 Requests/sec: 47476.75
519 Transfer/sec: 6.57MB
520
521Koa
522 Thread Stats Avg Stdev Max +/- Stdev
523 Latency 2.95ms 247.10us 6.91ms 72.18%
524 Req/Sec 8.52k 277.12 9.09k 70.30%
525 342518 requests in 10.10s, 47.36MB read
526 Requests/sec: 33909.82
527 Transfer/sec: 4.69MB
528
529Express
530 Thread Stats Avg Stdev Max +/- Stdev
531 Latency 4.91ms 484.52us 10.65ms 89.71%
532 Req/Sec 5.11k 350.75 9.69k 98.51%
533 204520 requests in 10.10s, 40.57MB read
534 Requests/sec: 20249.80
535 Transfer/sec: 4.02MB
536```
537
538
539## Comparisons
540
541Polka's API aims to be _very_ similar to Express since most Node.js developers are already familiar with it. If you know Express, you already know Polka! :dancer:
542
543There are, however, a few main differences. Polka does not support or offer:
544
5451) **Polka uses a tiered middleware system.**
546
547 Express maintains the sequence of your route & middleware declarations during its runtime, which can pose a problem when composing sub-applications. Typically, this forces you to duplicate groups of logic.
548
549 Please see [Middleware Sequence](#middleware-sequence) for an example and additional details.
550
5512) **Any built-in view/rendering engines.**
552
553 Most templating engines can be incorporated into middleware functions or used directly within a route handler.
554
5553) **The ability to `throw` from within middleware.**
556
557 However, all other forms of middleware-errors are supported. (See [Middleware Errors](#middleware-errors).)
558
559 ```js
560 function middleware(res, res, next) {
561 // pass an error message to next()
562 next('uh oh');
563
564 // pass an Error to next()
565 next(new Error('🙀'));
566
567 // send an early, customized error response
568 res.statusCode = 401;
569 res.end('Who are you?');
570 }
571 ```
572
5734) **Express-like response helpers... yet! (#14)**
574
575 Express has a nice set of [response helpers](http://expressjs.com/en/4x/api.html#res.append). While Polka relies on the [native Node.js response methods](https://nodejs.org/dist/latest-v9.x/docs/api/http.html#http_class_http_serverresponse), it would be very easy/possible to attach a global middleware that contained a similar set of helpers. (_TODO_)
576
5775) **`RegExp`-based route patterns.**
578
579 Polka's router uses string comparison to match paths against patterns. It's a lot quicker & more efficient.
580
581 The following routing patterns **are not** supported:
582
583 ```js
584 app.get('/ab?cd', _ => {});
585 app.get('/ab+cd', _ => {});
586 app.get('/ab*cd', _ => {});
587 app.get('/ab(cd)?e', _ => {});
588 app.get(/a/, _ => {});
589 app.get(/.*fly$/, _ => {});
590 ```
591
592 The following routing patterns **are** supported:
593
594 ```js
595 app.get('/users', _ => {});
596 app.get('/users/:id', _ => {});
597 app.get('/users/:id?', _ => {});
598 app.get('/users/:id/books/:title', _ => {});
599 app.get('/users/*', _ => {});
600 ```
601
6026) **Polka instances are not (directly) the request handler.**
603
604 Most packages in the Express ecosystem expect you to pass your `app` directly into the package. This is because `express()` returns a middleware signature directly.
605
606 In the Polka-sphere, this functionality lives in your application's [`handler`](#handlerreq-res-parsed) instead.
607
608 Here's an example with [`supertest`](https://github.com/visionmedia/supertest), a popular testing utility for Express apps.
609
610 ```js
611 const request = require('supertest');
612 const send = require('@polka/send-type');
613
614 const express = require('express')();
615 const polka = require('polka')();
616
617 express.get('/user', (req, res) => {
618 res.status(200).json({ name: 'john' });
619 });
620
621 polka.get('/user', (req, res) => {
622 send(res, 200, { name: 'john' });
623 });
624
625 function isExpected(app) {
626 request(app)
627 .get('/user')
628 .expect('Content-Type', /json/)
629 .expect('Content-Length', '15')
630 .expect(200);
631 }
632
633 // Express: Pass in the entire application directly
634 isExpected(express);
635
636 // Polka: Pass in the application `handler` instead
637 isExpected(polka.handler);
638 ```
639
640
641## License
642
643MIT © [Luke Edwards](https://lukeed.com)