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