UNPKG

17.2 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).then(_ => {
63 console.log(`> Running on localhost:3000`);
64 });
65```
66
67## API
68
69Polka extends [Trouter](https://github.com/lukeed/trouter) which means it inherits its API, too!
70
71### polka(options)
72
73Returns an instance of Polka~!
74
75#### options.onError
76Type: `Function`
77
78A catch-all error handler; executed whenever a middleware throws an error. Change this if you don't like default behavior.
79
80Its signature is `(err, req, res, next)`, where `err` is the `String` or `Error` thrown by your middleware.
81
82> **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!
83
84#### options.onNoMatch
85Type: `Function`
86
87A handler when no route definitions were matched. Change this if you don't like default behavior, which sends a `404` status & `Not found` response.
88
89Its signature is `(req, res)` and requires that you terminate the response.
90
91
92### use(base, ...fn)
93
94Attach [middleware(s)](#middleware) and/or sub-application(s) to the server. These will execute _before_ your routes' [handlers](#handlers).
95
96**Important:** If a `base` pathname is provided, all functions within the same `use()` block will _only_ execute when the `req.pathname` matches the `base` path.
97
98#### base
99Type: `String`<br>
100Default: `undefined`
101
102The base path on which the following middleware(s) or sub-application should be mounted.
103
104#### fn
105Type: `Function|Array`
106
107You may pass one or more functions at a time. Each function must have the standardized `(req, res, next)` signature.
108
109You may also pass a sub-application, which _must_ be accompanied by a `base` pathname.
110
111Please see [`Middleware`](#middleware) and [Express' middleware examples](http://expressjs.com/en/4x/api.html#middleware-callback-function-examples) for more info.
112
113
114### parse(req)
115
116Returns: `Object`
117
118This is an alias of the awesome [`parseurl`](https://github.com/pillarjs/parseurl#api) module. There are no Polka-specific changes.
119
120### listen(port, hostname)
121
122Returns: `Promise`
123
124Wraps the native [`server.listen`](https://nodejs.org/dist/latest-v9.x/docs/api/http.html#http_server_listen) with a Promise, rejecting on any error.
125
126### handler(req, res, parsed)
127
128The main Polka [`ClientRequest`](https://nodejs.org/dist/latest-v9.x/docs/api/http.html#http_class_http_clientrequest) handler. It receives all requests and tries to match the incoming URL against known routes.
129
130If 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.
131
132> **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.
133
134At 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).
135
136#### req
137Type: `ClientRequest`
138
139#### res
140Type: `ServerResponse`
141
142#### parsed
143Type: `Object`
144
145Optionally 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.
146
147
148## Routing
149
150Routes are used to define how an application responds to varying HTTP methods and endpoints.
151
152> If you're coming from Express, there's nothing new here!<br> However, do check out [Comparisons](#comparisons) for some pattern changes.
153
154### Basics
155
156Each route is comprised of a [path pattern](#patterns), a [HTTP method](#methods), and a [handler](#handlers) (aka, what you want to do).
157
158In code, this looks like:
159
160```js
161app.METHOD(pattern, handler);
162```
163
164wherein:
165
166* `app` is an instance of `polka`
167* [`METHOD`](#methods) is any valid HTTP method, lowercased
168* [`pattern`](#patterns) is a routing pattern (string)
169* [`handler`](#handlers) is the function to execute when `pattern` is matched
170
171Also, a single pathname (or `pattern`) may be reused with multiple METHODs.
172
173The following example demonstrates some simple routes.
174
175```js
176const app = polka();
177
178app.get('/', (req, res) => {
179 res.end('Hello world!');
180});
181
182app.get('/users', (req, res) => {
183 res.end('Get all users!');
184});
185
186app.post('/users', (req, res) => {
187 res.end('Create a new User!');
188});
189
190app.put('/users/:id', (req, res) => {
191 res.end(`Update User with ID of ${req.params.id}`);
192});
193
194app.delete('/users/:id', (req, res) => {
195 res.end(`CY@ User ${req.params.id}!`);
196});
197```
198
199### Patterns
200
201Unlike 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.
202
203However, 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:
204
205> See [Comparisons](#comparisons) for the list of `RegExp`-based patterns that Polka does not support.
206
207The supported pattern types are:
208
209* static (`/users`)
210* named parameters (`/users/:id`)
211* nested parameters (`/users/:id/books/:title`)
212* optional parameters (`/users/:id?/books/:title?`)
213* any match / wildcards (`/users/*`)
214
215### Parameters
216
217Any 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.
218
219> **Important:** Your parameter names should be unique, as shared names will overwrite each other!
220
221```js
222app.get('/users/:id/books/:title', (req, res) => {
223 let { id, title } = req.params;
224 res.end(`User: ${id} && Book: ${title}`);
225});
226```
227
228```sh
229$ curl /users/123/books/Narnia
230#=> User: 123 && Book: Narnia
231```
232
233### Methods
234
235Any valid HTTP method is supported! However, only the most common methods are used throughout this documentation for demo purposes.
236
237> **Note:** For a full list of valid METHODs, please see [this list](http://expressjs.com/en/4x/api.html#routing-methods).
238
239### Handlers
240
241Request handlers accept the incoming [`ClientRequest`](https://nodejs.org/dist/latest-v9.x/docs/api/http.html#http_class_http_clientrequest) and the formulating [`ServerResponse`](https://nodejs.org/dist/latest-v9.x/docs/api/http.html#http_class_http_serverresponse).
242
243Every route definition must contain a valid `handler` function, or else an error will be thrown at runtime.
244
245> **Important:** You must _always_ terminate a `ServerResponse`!
246
247It'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.
248
249> **Note:** This is a native `http` behavior.
250
251#### Async Handlers
252
253If using Node 7.4 or later, you may leverage native `async` and `await` syntax! :heart_eyes_cat:
254
255No special preparation is needed &mdash; simply add the appropriate keywords.
256
257```js
258const app = polka();
259
260const sleep = ms => new Promise(r => setTimeout(r, ms));
261
262async function authenticate(req, res, next) {
263 let token = req.getHeader('authorization');
264 if (!token) return app.send(res, 401);
265 req.user = await Users.find(token); // <== fake
266 next(); // done, woot!
267}
268
269app
270 .use(authenticate)
271 .get('/', async (req, res) => {
272 // log middleware's findings
273 console.log('~> current user', req.user);
274 // force sleep, because we can~!
275 await sleep(500);
276 // send greeting
277 res.end(`Hello, ${req.user.name}`);
278 });
279```
280
281
282## Middleware
283
284Middleware are functions that run in between (hence "middle") receiving the request & executing your route's [`handler`](#handlers) response.
285
286> Coming from Express? Use any middleware you already know & love! :tada:
287
288The middleware signature receives the request (`req`), the response (`res`), and a callback (`next`).
289
290These can apply mutations to the `req` and `res` objects, and unlike Express, have access to `req.params`, `req.pathname`, `req.search`, and `req.query`!
291
292Most 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`.
293
294```js
295// Log every request
296function logger(req, res, next) {
297 console.log(`~> Received ${req.method} on ${req.url}`);
298 next(); // move on
299}
300
301function authorize(req, res, next) {
302 // mutate req; available later
303 req.token = req.getHeader('authorization');
304 req.token ? next() : ((res.statusCode=401) && res.end('No token!'));
305}
306
307polka().use(logger, authorize).get('*', (req, res) => {
308 console.log(`~> user token: ${req.token}`);
309 res.end('Hello, valid user');
310});
311```
312
313```sh
314$ curl /
315# ~> Received GET on /
316#=> (401) No token!
317
318$ curl -H "authorization: secret" /foobar
319# ~> Received GET on /foobar
320# ~> user token: secret
321#=> (200) Hello, valid user
322```
323
324In Polka, middleware functions are mounted globally, which means that they'll run on every request (see [Comparisons](#comparisons)). Instead, you'll have to apply internal filters to determine when & where your middleware should run.
325
326> **Note:** This might change in Polka 1.0 :thinking:
327
328```js
329function foobar(req, res, next) {
330 if (req.pathname.startsWith('/users')) {
331 // do something magical
332 }
333 next();
334}
335```
336
337### Middleware Errors
338
339If an error arises within a middleware, the loop will be exited. This means that no other middleware will execute & neither will the route handler.
340
341Similarly, regardless of `statusCode`, an early response termination will also exit the loop & prevent the route handler from running.
342
343There are three ways to "throw" an error from within a middleware function.
344
345> **Hint:** None of them use `throw` :joy_cat:
346
3471. **Pass any string to `next()`**
348
349 This will exit the loop & send a `500` status code, with your error string as the response body.
350
351 ```js
352 polka()
353 .use((req, res, next) => next('💩'))
354 .get('*', (req, res) => res.end('wont run'));
355 ```
356
357 ```sh
358 $ curl /
359 #=> (500) 💩
360 ```
361
3622. **Pass an `Error` to `next()`**
363
364 This is similar to the above option, but gives you a window in changing the `statusCode` to something other than the `500` default.
365
366 ```js
367 function oopsies(req, res, next) {
368 let err = new Error('Try again');
369 err.code = 422;
370 next(err);
371 }
372 ```
373
374 ```sh
375 $ curl /
376 #=> (422) Try again
377 ```
378
3793. **Terminate the response early**
380
381 Once the response has been ended, there's no reason to continue the loop!
382
383 This approach is the most versatile as it allows to control every aspect of the outgoing `res`.
384
385 ```js
386 function oopsies(req, res, next) {
387 if (true) {
388 // something bad happened~
389 res.writeHead(400, {
390 'Content-Type': 'application/json',
391 'X-Error-Code': 'Please dont do this IRL'
392 });
393 let json = JSON.stringify({ error:'Missing CSRF token' });
394 res.end(json);
395 } else {
396 next(); // never called FYI
397 }
398 }
399 ```
400
401 ```sh
402 $ curl /
403 #=> (400) {"error":"Missing CSRF token"}
404 ```
405
406
407## Benchmarks
408
409A round of Polka-vs-Express benchmarks across varying Node versions can be [found here](/bench).
410
411> **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.
412
413```
414Node 8.9.0
415
416Native
417 Thread Stats Avg Stdev Max +/- Stdev
418 Latency 2.23ms 143.16us 6.95ms 91.55%
419 Req/Sec 5.41k 164.89 6.33k 94.05%
420 434278 requests in 10.10s, 43.07MB read
421 Requests/sec: 42989.17
422 Transfer/sec: 4.26MB
423
424Polka
425 Thread Stats Avg Stdev Max +/- Stdev
426 Latency 2.26ms 123.53us 6.77ms 89.52%
427 Req/Sec 5.33k 113.32 6.16k 89.47%
428 428480 requests in 10.10s, 42.50MB read
429 Requests/sec: 42410.23
430 Transfer/sec: 4.21MB
431
432Express
433 Thread Stats Avg Stdev Max +/- Stdev
434 Latency 5.15ms 421.69us 8.51ms 77.95%
435 Req/Sec 2.34k 77.06 2.55k 72.12%
436 186390 requests in 10.01s, 36.97MB read
437 Requests/sec: 18628.36
438 Transfer/sec: 3.70MB
439
440Fastify
441 Thread Stats Avg Stdev Max +/- Stdev
442 Latency 2.91ms 201.13us 7.51ms 58.07%
443 Req/Sec 4.14k 130.04 4.48k 65.59%
444 333158 requests in 10.10s, 41.30MB read
445 Requests/sec: 32979.84
446 Transfer/sec: 4.09MB
447
448Koa
449 Thread Stats Avg Stdev Max +/- Stdev
450 Latency 3.43ms 369.96us 8.67ms 87.30%
451 Req/Sec 3.51k 114.78 4.12k 69.76%
452 281808 requests in 10.10s, 38.97MB read
453 Requests/sec: 27892.99
454 Transfer/sec: 3.86MB
455```
456
457
458## Comparisons
459
460Polka'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:
461
462There are, however, a few main differences. Polka does not support or offer:
463
4641) **Any built-in view/rendering engines.**
465
466 Most templating engines can be incorporated into middleware functions or used directly within a route handler.
467
4682) **The ability to `throw` from within middleware.**
469
470 However, all other forms of middleware-errors are supported. (See [Middleware Errors](#middleware-errors).)
471
472 ```js
473 function middleware(res, res, next) {
474 // pass an error message to next()
475 next('uh oh');
476
477 // pass an Error to next()
478 next(new Error('🙀'));
479
480 // send an early, customized error response
481 res.statusCode = 401;
482 res.end('Who are you?');
483 }
484 ```
485
4863) **Express-like response helpers... yet! (#14)**
487
488 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_)
489
4904) **`RegExp`-based route patterns.**
491
492 Polka's router uses string comparison to match paths against patterns. It's a lot quicker & more efficient.
493
494 The following routing patterns **are not** supported:
495
496 ```js
497 app.get('/ab?cd', _ => {});
498 app.get('/ab+cd', _ => {});
499 app.get('/ab*cd', _ => {});
500 app.get('/ab(cd)?e', _ => {});
501 app.get(/a/, _ => {});
502 app.get(/.*fly$/, _ => {});
503 ```
504
505 The following routing patterns **are** supported:
506
507 ```js
508 app.get('/users', _ => {});
509 app.get('/users/:id', _ => {});
510 app.get('/users/:id?', _ => {});
511 app.get('/users/:id/books/:title', _ => {});
512 app.get('/users/*', _ => {});
513 ```
514
515
516## License
517
518MIT © [Luke Edwards](https://lukeed.com)