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 |
|
23 | 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!
|
24 |
|
25 | 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:
|
26 |
|
27 | And, 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
|
44 | const polka = require('polka');
|
45 |
|
46 | function one(req, res, next) {
|
47 | req.hello = 'world';
|
48 | next();
|
49 | }
|
50 |
|
51 | function two(req, res, next) {
|
52 | req.foo = '...needs better demo 😔';
|
53 | next();
|
54 | }
|
55 |
|
56 | polka()
|
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 |
|
69 | Polka extends [Trouter](https://github.com/lukeed/trouter) which means it inherits its API, too!
|
70 |
|
71 | ### polka(options)
|
72 |
|
73 | Returns an instance of Polka~!
|
74 |
|
75 | #### options.onError
|
76 | Type: `Function`
|
77 |
|
78 | A catch-all error handler; executed whenever a middleware throws an error. Change this if you don't like default behavior.
|
79 |
|
80 | Its 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
|
85 | Type: `Function`
|
86 |
|
87 | 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.
|
88 |
|
89 | Its signature is `(req, res)` and requires that you terminate the response.
|
90 |
|
91 |
|
92 | ### use(base, ...fn)
|
93 |
|
94 | Attach [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
|
99 | Type: `String`<br>
|
100 | Default: `undefined`
|
101 |
|
102 | The base path on which the following middleware(s) or sub-application should be mounted.
|
103 |
|
104 | #### fn
|
105 | Type: `Function|Array`
|
106 |
|
107 | You may pass one or more functions at a time. Each function must have the standardized `(req, res, next)` signature.
|
108 |
|
109 | You may also pass a sub-application, which _must_ be accompanied by a `base` pathname.
|
110 |
|
111 | Please 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 |
|
116 | Returns: `Object`
|
117 |
|
118 | This 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 |
|
122 | Returns: `Promise`
|
123 |
|
124 | Wraps 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 |
|
128 | The 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 |
|
130 | 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.
|
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 |
|
134 | 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).
|
135 |
|
136 | #### req
|
137 | Type: `ClientRequest`
|
138 |
|
139 | #### res
|
140 | Type: `ServerResponse`
|
141 |
|
142 | #### parsed
|
143 | Type: `Object`
|
144 |
|
145 | 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.
|
146 |
|
147 |
|
148 | ## Routing
|
149 |
|
150 | Routes 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 |
|
156 | Each route is comprised of a [path pattern](#patterns), a [HTTP method](#methods), and a [handler](#handlers) (aka, what you want to do).
|
157 |
|
158 | In code, this looks like:
|
159 |
|
160 | ```js
|
161 | app.METHOD(pattern, handler);
|
162 | ```
|
163 |
|
164 | wherein:
|
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 |
|
171 | Also, a single pathname (or `pattern`) may be reused with multiple METHODs.
|
172 |
|
173 | The following example demonstrates some simple routes.
|
174 |
|
175 | ```js
|
176 | const app = polka();
|
177 |
|
178 | app.get('/', (req, res) => {
|
179 | res.end('Hello world!');
|
180 | });
|
181 |
|
182 | app.get('/users', (req, res) => {
|
183 | res.end('Get all users!');
|
184 | });
|
185 |
|
186 | app.post('/users', (req, res) => {
|
187 | res.end('Create a new User!');
|
188 | });
|
189 |
|
190 | app.put('/users/:id', (req, res) => {
|
191 | res.end(`Update User with ID of ${req.params.id}`);
|
192 | });
|
193 |
|
194 | app.delete('/users/:id', (req, res) => {
|
195 | res.end(`CY@ User ${req.params.id}!`);
|
196 | });
|
197 | ```
|
198 |
|
199 | ### Patterns
|
200 |
|
201 | 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.
|
202 |
|
203 | 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:
|
204 |
|
205 | > See [Comparisons](#comparisons) for the list of `RegExp`-based patterns that Polka does not support.
|
206 |
|
207 | The 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 |
|
217 | 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.
|
218 |
|
219 | > **Important:** Your parameter names should be unique, as shared names will overwrite each other!
|
220 |
|
221 | ```js
|
222 | app.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 |
|
235 | Any 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 |
|
241 | Request 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 |
|
243 | Every 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 |
|
247 | 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.
|
248 |
|
249 | > **Note:** This is a native `http` behavior.
|
250 |
|
251 | #### Async Handlers
|
252 |
|
253 | If using Node 7.4 or later, you may leverage native `async` and `await` syntax! :heart_eyes_cat:
|
254 |
|
255 | No special preparation is needed — simply add the appropriate keywords.
|
256 |
|
257 | ```js
|
258 | const app = polka();
|
259 |
|
260 | const sleep = ms => new Promise(r => setTimeout(r, ms));
|
261 |
|
262 | async 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 |
|
269 | app
|
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 |
|
284 | Middleware 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 |
|
288 | The middleware signature receives the request (`req`), the response (`res`), and a callback (`next`).
|
289 |
|
290 | These 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 |
|
292 | 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`.
|
293 |
|
294 | ```js
|
295 | // Log every request
|
296 | function logger(req, res, next) {
|
297 | console.log(`~> Received ${req.method} on ${req.url}`);
|
298 | next(); // move on
|
299 | }
|
300 |
|
301 | function 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 |
|
307 | polka().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 |
|
324 | In 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
|
329 | function foobar(req, res, next) {
|
330 | if (req.pathname.startsWith('/users')) {
|
331 | // do something magical
|
332 | }
|
333 | next();
|
334 | }
|
335 | ```
|
336 |
|
337 | ### Middleware Errors
|
338 |
|
339 | 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.
|
340 |
|
341 | Similarly, regardless of `statusCode`, an early response termination will also exit the loop & prevent the route handler from running.
|
342 |
|
343 | There are three ways to "throw" an error from within a middleware function.
|
344 |
|
345 | > **Hint:** None of them use `throw` :joy_cat:
|
346 |
|
347 | 1. **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 |
|
362 | 2. **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 |
|
379 | 3. **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 |
|
409 | A 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 | ```
|
414 | Node 8.9.0
|
415 |
|
416 | Native
|
417 | Thread Stats Avg Stdev Max +/- Stdev
|
418 | Latency 2.24ms 112.34us 5.57ms 92.15%
|
419 | Req/Sec 5.38k 99.48 5.57k 81.81%
|
420 | 432562 requests in 10.10s, 42.90MB read
|
421 | Requests/sec: 42815.14
|
422 | Transfer/sec: 4.25MB
|
423 |
|
424 | Polka
|
425 | Thread Stats Avg Stdev Max +/- Stdev
|
426 | Latency 2.26ms 115.55us 5.19ms 87.16%
|
427 | Req/Sec 5.32k 97.34 5.55k 72.77%
|
428 | 428208 requests in 10.10s, 42.47MB read
|
429 | Requests/sec: 42388.92
|
430 | Transfer/sec: 4.20MB
|
431 |
|
432 | Express
|
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 |
|
440 | Fastify
|
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 |
|
448 | Koa
|
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 |
|
460 | 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:
|
461 |
|
462 | There are, however, a few main differences. Polka does not support or offer:
|
463 |
|
464 | 1) **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 |
|
468 | 2) **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 |
|
486 | 3) **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 |
|
490 | 4) **`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 |
|
518 | MIT © [Luke Edwards](https://lukeed.com)
|