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 |
|
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 (_TODO_). 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 | * 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
|
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 | ### use(...fn)
|
72 |
|
73 | Attach [middleware(s)](#middleware) and/or sub-application(s) (_TODO_) to the server. These will execute _before_ your routes' [handlers](#handlers).
|
74 |
|
75 | #### fn
|
76 | Type: `Function|Array`
|
77 |
|
78 | You may pass one or more functions at a time. Each function must have the standardized `(req, res, next)` signature.
|
79 |
|
80 | Please 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 |
|
84 | Returns: `Object`
|
85 |
|
86 | This 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 |
|
90 | Returns: `Promise`
|
91 |
|
92 | 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.
|
93 |
|
94 | ### listen(port, hostname)
|
95 |
|
96 | Returns: `Promise`
|
97 |
|
98 | This is an alias of [`start`](#start).
|
99 |
|
100 | ### send(res, code, body, type)
|
101 |
|
102 | A 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
|
105 | Type: `ServerResponse`
|
106 |
|
107 | #### code
|
108 | Type: `Number`<br>
|
109 | Default: `200`
|
110 |
|
111 | #### body
|
112 | Type: `String`<br>
|
113 | Default: `http.STATUS_CODES[code]`
|
114 |
|
115 | Returns the default `statusText` for a given [`code`](#code).
|
116 |
|
117 | #### type
|
118 | Type: `String`<br>
|
119 | Default: `'text/plain'`
|
120 |
|
121 | The `Content-Type` header value for the response.
|
122 |
|
123 | ### handler(req, res, parsed)
|
124 |
|
125 | 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.
|
126 |
|
127 | If 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 — assuming that a middleware hasn't already returned a response or thrown an error!
|
128 |
|
129 | #### req
|
130 | Type: `ClientRequest`
|
131 |
|
132 | #### res
|
133 | Type: `ServerResponse`
|
134 |
|
135 | #### parsed
|
136 | Type: `Object`
|
137 |
|
138 | 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.
|
139 |
|
140 |
|
141 | ## Routing
|
142 |
|
143 | Routes 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 |
|
149 | Each route is comprised of a [path pattern](#patterns), a [HTTP method](#methods), and a [handler](#handlers) (aka, what you want to do).
|
150 |
|
151 | In code, this looks like:
|
152 |
|
153 | ```js
|
154 | app.METHOD(pattern, handler);
|
155 | ```
|
156 |
|
157 | wherein:
|
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 |
|
164 | Also, a single pathname (or `pattern`) may be reused with multiple METHODs.
|
165 |
|
166 | The following example demonstrates some simple routes.
|
167 |
|
168 | ```js
|
169 | const app = polka();
|
170 |
|
171 | app.get('/', (req, res) => {
|
172 | res.end('Hello world!');
|
173 | });
|
174 |
|
175 | app.get('/users', (req, res) => {
|
176 | res.end('Get all users!');
|
177 | });
|
178 |
|
179 | app.post('/users', (req, res) => {
|
180 | res.end('Create a new User!');
|
181 | });
|
182 |
|
183 | app.put('/users/:id', (req, res) => {
|
184 | res.end(`Update User with ID of ${req.params.id}`);
|
185 | });
|
186 |
|
187 | app.delete('/users/:id', (req, res) => {
|
188 | res.end(`CY@ User ${req.params.id}!`);
|
189 | });
|
190 | ```
|
191 |
|
192 | ### Patterns
|
193 |
|
194 | 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.
|
195 |
|
196 | 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:
|
197 |
|
198 | > See [Comparisons](#comparisons) for the list of `RegExp`-based patterns that Polka does not support.
|
199 |
|
200 | The 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 |
|
210 | 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.
|
211 |
|
212 | > **Important:** Your parameter names should be unique, as shared names will overwrite each other!
|
213 |
|
214 | ```js
|
215 | app.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 |
|
228 | Any 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 |
|
234 | 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).
|
235 |
|
236 | Every 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 |
|
240 | 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.
|
241 |
|
242 | > **Note:** This is a native `http` behavior.
|
243 |
|
244 | #### Async Handlers
|
245 |
|
246 | If using Node 7.4 or later, you may leverage native `async` and `await` syntax! :heart_eyes_cat:
|
247 |
|
248 | No special preparation is needed — simply add the appropriate keywords.
|
249 |
|
250 | ```js
|
251 | const app = polka();
|
252 |
|
253 | const sleep = ms => new Promise(r => setTimeout(r, ms));
|
254 |
|
255 | async 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 |
|
262 | app
|
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 |
|
277 | Middleware 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 |
|
281 | The middleware signature receives the request (`req`), the response (`res`), and a callback (`next`).
|
282 |
|
283 | 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`!
|
284 |
|
285 | 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`.
|
286 |
|
287 | ```js
|
288 | // Log every request
|
289 | function logger(req, res, next) {
|
290 | console.log(`~> Received ${req.method} on ${req.url}`);
|
291 | next(); // move on
|
292 | }
|
293 |
|
294 | function 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 |
|
300 | polka().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 |
|
317 | 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.
|
318 |
|
319 | > **Note:** This might change in Polka 1.0 :thinking:
|
320 |
|
321 | ```js
|
322 | function foobar(req, res, next) {
|
323 | if (req.pathname.startsWith('/users')) {
|
324 | // do something magical
|
325 | }
|
326 | next();
|
327 | }
|
328 | ```
|
329 |
|
330 | ### Middleware Errors
|
331 |
|
332 | 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.
|
333 |
|
334 | Similarly, regardless of `statusCode`, an early response termination will also exit the loop & prevent the route handler from running.
|
335 |
|
336 | There are three ways to "throw" an error from within a middleware function.
|
337 |
|
338 | > **Hint:** None of them use `throw` :joy_cat:
|
339 |
|
340 | 1. **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 |
|
355 | 2. **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 |
|
372 | 3. **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 |
|
402 | A 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 | ```
|
407 | Node 8.9.0
|
408 |
|
409 | Native
|
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 |
|
417 | Polka
|
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 |
|
425 | Express
|
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 |
|
433 | Fastify
|
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 |
|
445 | 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:
|
446 |
|
447 | There are, however, a few main differences. Polka does not support or offer:
|
448 |
|
449 | 1) **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 |
|
453 | 2) **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 |
|
471 | 3) **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 |
|
475 | 4) **The `.use()` method does not accept a `pathname` filter.**
|
476 |
|
477 | ...This might change before a 1.0 release :thinking:
|
478 |
|
479 | 5) **`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 |
|
504 | 6) **Sub-applications ...yet!**
|
505 |
|
506 | This will definitely be done for 1.0 :+1:
|
507 |
|
508 |
|
509 | ## License
|
510 |
|
511 | MIT © [Luke Edwards](https://lukeed.com)
|