UNPKG

15.9 kBMarkdownView Raw
1<div align="center">
2 <img src="/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 (_TODO_). 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* 70 LOC for Polka, 105 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### use(...fn)
72
73Attach [middleware(s)](#middleware) and/or sub-application(s) (_TODO_) to the server. These will execute _before_ your routes' [handlers](#handlers).
74
75#### fn
76Type: `Function|Array`
77
78You may pass one or more functions at a time. Each function must have the standardized `(req, res, next)` signature.
79
80Please see [`Middleware`](#middleware) and [Express' middleware examples](http://expressjs.com/en/4x/api.html#middleware-callback-function-examples) for more info.
81
82### parse(req)
83
84Returns: `Object`
85
86This is an alias of the awesome [`parseurl`](https://github.com/pillarjs/parseurl#api) module. There are no Polka-specific changes.
87
88### start(port, hostname)
89
90Returns: `Promise`
91
92Wraps 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.
93
94### listen(port, hostname)
95
96Returns: `Promise`
97
98This is an alias of [`start`](#start).
99
100### send(res, code, body, type)
101
102A minimal helper that terminates the [`ServerResponse`](https://nodejs.org/dist/latest-v9.x/docs/api/http.html#http_class_http_serverresponse) with desired values.
103
104#### res
105Type: `ServerResponse`
106
107#### code
108Type: `Number`<br>
109Default: `200`
110
111#### body
112Type: `String`<br>
113Default: `http.STATUS_CODES[code]`
114
115Returns the default `statusText` for a given [`code`](#code).
116
117#### type
118Type: `String`<br>
119Default: `'text/plain'`
120
121The `Content-Type` header value for the response.
122
123### handler(req, res, next)
124
125The 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.
126
127If the `req.url` is not matched, a `(501) Not Implemented` response is returned. Otherwise, all middleware will be called. At the end of the loop, the (user-defined) route handler will be executed &mdash; assuming that a middleware hasn't already returned a response or thrown an error!
128
129#### req
130Type: `ClientRequest`
131
132#### res
133Type: `ServerResponse`
134
135#### next
136Type: `Function`
137
138An optional function (with the same signature) to call at the end of the response cycle.
139
140
141## Routing
142
143Routes are used to define how an application responds to varying HTTP methods and endpoints.
144
145> If you're coming from Express, there's nothing new here!<br> However, do check out [Comparisons](#comparisons) for some pattern changes.
146
147### Basics
148
149Each route is comprised of a [path pattern](#patterns), a [HTTP method](#methods), and a [handler](#handlers) (aka, what you want to do).
150
151In code, this looks like:
152
153```js
154app.METHOD(pattern, handler);
155```
156
157wherein:
158
159* `app` is an instance of `polka`
160* [`METHOD`](#methods) is any valid HTTP method, lowercased
161* [`pattern`](#patterns) is a routing pattern (string)
162* [`handler`](#handlers) is the function to execute when `pattern` is matched
163
164Also, a single pathname (or `pattern`) may be reused with multiple METHODs.
165
166The following example demonstrates some simple routes.
167
168```js
169const app = polka();
170
171app.get('/', (req, res) => {
172 res.end('Hello world!');
173});
174
175app.get('/users', (req, res) => {
176 res.end('Get all users!');
177});
178
179app.post('/users', (req, res) => {
180 res.end('Create a new User!');
181});
182
183app.put('/users/:id', (req, res) => {
184 res.end(`Update User with ID of ${req.params.id}`);
185});
186
187app.delete('/users/:id', (req, res) => {
188 res.end(`CY@ User ${req.params.id}!`);
189});
190```
191
192### Patterns
193
194Unlike 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.
195
196However, 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:
197
198> See [Comparisons](#comparisons) for the list of `RegExp`-based patterns that Polka does not support.
199
200The supported pattern types are:
201
202* static (`/users`)
203* named parameters (`/users/:id`)
204* nested parameters (`/users/:id/books/:title`)
205* optional parameters (`/users/:id?/books/:title?`)
206* any match / wildcards (`/users/*`)
207
208### Parameters
209
210Any 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.
211
212> **Important:** Your parameter names should be unique, as shared names will overwrite each other!
213
214```js
215app.get('/users/:id/books/:title', (req, res) => {
216 let { id, title } = req.params;
217 res.end(`User: ${id} && Book: ${title}`);
218});
219```
220
221```sh
222$ curl /users/123/books/Narnia
223#=> User: 123 && Book: Narnia
224```
225
226### Methods
227
228Any valid HTTP method is supported! However, only the most common methods are used throughout this documentation for demo purposes.
229
230> **Note:** For a full list of valid METHODs, please see [this list](http://expressjs.com/en/4x/api.html#routing-methods).
231
232### Handlers
233
234Request 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).
235
236Every route definition must contain a valid `handler` function, or else an error will be thrown at runtime.
237
238> **Important:** You must _always_ terminate a `ServerResponse`!
239
240It'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.
241
242> **Note:** This is a native `http` behavior.
243
244#### Async Handlers
245
246If using Node 7.4 or later, you may leverage native `async` and `await` syntax! :heart_eyes_cat:
247
248No special preparation is needed &mdash; simply add the appropriate keywords.
249
250```js
251const app = polka();
252
253const sleep = ms => new Promise(r, setTimeout(r, ms));
254
255async function authenticate(req, res, next) {
256 let token = req.getHeader('authorization');
257 if (!token) return app.send(res, 401);
258 req.user = await Users.find(token); // <== fake
259 next(); // done, woot!
260}
261
262app
263 .use(authenticate)
264 .get('/', async (req, res) => {
265 // log middleware's findings
266 console.log('~> current user', req.user);
267 // force sleep, because we can~!
268 await sleep(500);
269 // send greeting
270 res.end(`Hello, ${req.user.name}`);
271 });
272```
273
274
275## Middleware
276
277Middleware are functions that run in between (hence "middle") receiving the request & executing your route's [`handler`](#handlers) response.
278
279> Coming from Express? Use any middleware you already know & love! :tada:
280
281The middleware signature receives the request (`req`), the response (`res`), and a callback (`next`).
282
283These can apply mutations to the `req` and `res` objects, and unlike Express, have access to `req.params`, `req.pathname`, `req.search`, and `req.query`!
284
285Most 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`.
286
287```js
288// Log every request
289function logger(req, res, next) {
290 console.log(`~> Received ${req.method} on ${req.url}`);
291 next(); // move on
292}
293
294function authorize(req, res, next) {
295 // mutate req; available later
296 req.token = req.getHeader('authorization');
297 req.token ? next() : ((res.statusCode=401) && res.end('No token!'));
298}
299
300polka().use(logger, authorize).get('*', (req, res) => {
301 console.log(`~> user token: ${req.token}`);
302 res.end('Hello, valid user');
303});
304```
305
306```sh
307$ curl /
308# ~> Received GET on /
309#=> (401) No token!
310
311$ curl -H "authorization: secret" /foobar
312# ~> Received GET on /foobar
313# ~> user token: secret
314#=> (200) Hello, valid user
315```
316
317In 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.
318
319> **Note:** This might change in Polka 1.0 :thinking:
320
321```js
322function foobar(req, res, next) {
323 if (req.pathname.startsWith('/users')) {
324 // do something magical
325 }
326 next();
327}
328```
329
330### Middleware Errors
331
332If an error arises within a middleware, the loop will be exited. This means that no other middleware will execute & neither will the route handler.
333
334Similarly, regardless of `statusCode`, an early response termination will also exit the loop & prevent the route handler from running.
335
336There are three ways to "throw" an error from within a middleware function.
337
338> **Hint:** None of them use `throw` :joy_cat:
339
3401. **Pass any string to `next()`**
341
342 This will exit the loop & send a `500` status code, with your error string as the response body.
343
344 ```js
345 polka()
346 .use((req, res, next) => next('💩'))
347 .get('*', (req, res) => res.end('wont run'));
348 ```
349
350 ```sh
351 $ curl /
352 #=> (500) 💩
353 ```
354
3552. **Pass an `Error` to `next()`**
356
357 This is similar to the above option, but gives you a window in changing the `statusCode` to something other than the `500` default.
358
359 ```js
360 function oopsies(req, res, next) {
361 let err = new Error('Try again');
362 err.code = 422;
363 next(err);
364 }
365 ```
366
367 ```sh
368 $ curl /
369 #=> (422) Try again
370 ```
371
3723. **Terminate the response early**
373
374 Once the response has been ended, there's no reason to continue the loop!
375
376 This approach is the most versatile as it allows to control every aspect of the outgoing `res`.
377
378 ```js
379 function oopsies(req, res, next) {
380 if (true) {
381 // something bad happened~
382 res.writeHead(400, {
383 'Content-Type': 'application/json',
384 'X-Error-Code': 'Please dont do this IRL'
385 });
386 let json = JSON.stringify({ error:'Missing CSRF token' });
387 res.end(json);
388 } else {
389 next(); // never called FYI
390 }
391 }
392 ```
393
394 ```sh
395 $ curl /
396 #=> (400) {"error":"Missing CSRF token"}
397 ```
398
399
400## Benchmarks
401
402A round of Polka-vs-Express benchmarks across varying Node versions can be [found here](/bench).
403
404> **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.
405
406```
407Node 8.9.0
408
409Native
410 Thread Stats Avg Stdev Max +/- Stdev
411 Latency 2.25ms 198.89us 6.39ms 93.49%
412 Req/Sec 5.36k 545.77 13.90k 97.63%
413 428295 requests in 10.10s, 42.48MB read
414 Requests/sec: 42405.68
415 Transfer/sec: 4.21MB
416
417Polka
418 Thread Stats Avg Stdev Max +/- Stdev
419 Latency 2.29ms 254.76us 6.82ms 92.38%
420 Req/Sec 5.26k 1.26k 40.56k 99.88%
421 419118 requests in 10.10s, 41.57MB read
422 Requests/sec: 41499.08
423 Transfer/sec: 4.12MB
424
425Express
426 Thread Stats Avg Stdev Max +/- Stdev
427 Latency 3.11ms 438.20us 10.17ms 89.85%
428 Req/Sec 3.88k 0.93k 29.98k 99.88%
429 308988 requests in 10.10s, 37.42MB read
430 Requests/sec: 30594.98
431 Transfer/sec: 3.71MB
432
433Fastify
434 Thread Stats Avg Stdev Max +/- Stdev
435 Latency 2.92ms 310.96us 10.29ms 89.60%
436 Req/Sec 4.12k 335.51 8.16k 95.52%
437 329986 requests in 10.10s, 40.91MB read
438 Requests/sec: 32665.12
439 Transfer/sec: 4.05MB
440```
441
442
443## Comparisons
444
445Polka'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:
446
447There are, however, a few main differences. Polka does not support or offer:
448
4491) **Any built-in view/rendering engines.**
450
451 Most templating engines can be incorporated into middleware functions or used directly within a route handler.
452
4532) **The ability to `throw` from within middleware.**
454
455 However, all other forms of middleware-errors are supported. (See [Middleware Errors](#middleware-errors).)
456
457 ```js
458 function middleware(res, res, next) {
459 // pass an error message to next()
460 next('uh oh');
461
462 // pass an Error to next()
463 next(new Error('🙀'));
464
465 // send an early, customized error response
466 res.statusCode = 401;
467 res.end('Who are you?');
468 }
469 ```
470
4713) **Response helpers... yet!**
472
473 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_)
474
4754) **The `.use()` method does not accept a `pathname` filter.**
476
477 ...This might change before a 1.0 release :thinking:
478
4795) **`RegExp`-based route patterns.**
480
481 Polka's router uses string comparison to match paths against patterns. It's a lot quicker & more efficient.
482
483 The following routing patterns **are not** supported:
484
485 ```js
486 app.get('/ab?cd', _ => {});
487 app.get('/ab+cd', _ => {});
488 app.get('/ab*cd', _ => {});
489 app.get('/ab(cd)?e', _ => {});
490 app.get(/a/, _ => {});
491 app.get(/.*fly$/, _ => {});
492 ```
493
494 The following routing patterns **are** supported:
495
496 ```js
497 app.get('/users', _ => {});
498 app.get('/users/:id', _ => {});
499 app.get('/users/:id?', _ => {});
500 app.get('/users/:id/books/:title', _ => {});
501 app.get('/users/*', _ => {});
502 ```
503
5046) **Sub-applications ...yet!**
505
506 This will definitely be done for 1.0 :+1:
507
508
509## License
510
511MIT © [Luke Edwards](https://lukeed.com)