UNPKG

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