Giter VIP home page Giter VIP logo

polka's Introduction

Polka

Polka

A micro web server so fast, it'll make you dance! ๐Ÿ‘ฏ


Black Lives Matter. โœŠ๐ŸฝโœŠ๐ŸพโœŠ๐Ÿฟ
Support the Equal Justice Initiative, Campaign Zero, or Educate Yourself.
ย 


Polka is an extremely minimal, highly performant Express.js alternative. Yes, you're right, Express is already super fast & not that big ๐Ÿค” โ€” but Polka shows that there was (somehow) room for improvement!

Essentially, Polka is just a native HTTP server with added support for routing, middleware, and sub-applications. That's it! ๐ŸŽ‰

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

Install

$ npm install --save polka

Usage

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, () => {
    console.log(`> Running on localhost:3000`);
  });

API

Polka extends Trouter which means it inherits its API, too!

polka(options)

Returns an instance of Polka~!

options.server

Type: Server

A custom, instantiated server that the Polka instance should attach its handler 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 is called. At this time, Polka will create a 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!
You must be certain that the exception will be handled elsewhere or can be safely ignored.
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) and/or sub-application(s) to the server. These will execute before your routes' 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
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 and Express' middleware examples for more info.

parse(req)

Returns: Object or undefined

As of v0.5.0, this is an alias of the @polka/url module. For nearly all cases, you'll notice no changes.

But, for whatever reason, you can quickly swap in parseurl again:

const app = polka();
app.parse = require('parseurl');
//=> Done!

listen()

Returns: Polka

Boots (or creates) the underlying http.Server for the first time. All arguments are passed to 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.

// 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 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 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.

req

Type: IncomingMessage

res

Type: ServerResponse

parsed

Type: Object

Optionally provide a parsed URL object. Useful if you've already parsed the incoming path. Otherwise, app.parse (aka 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!
However, do check out Comparisons for some pattern changes.

Basics

Each route is comprised of a path pattern, a HTTP method, and a handler (aka, what you want to do).

In code, this looks like:

app.METHOD(pattern, handler);

wherein:

  • app is an instance of polka
  • METHOD is any valid HTTP/1.1 method, lowercased
  • pattern is a routing pattern (string)
  • handler 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.

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, Polka uses string comparison to locate route matches. While faster & more memory efficient, this does also prevent complex pattern matching.

However, have no fear! ๐Ÿ’ฅ All the basic and most commonly used patterns are supported. You probably only ever used these patterns in the first place. ๐Ÿ˜‰

See 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 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!

app.get('/users/:id/books/:title', (req, res) => {
  let { id, title } = req.params;
  res.end(`User: ${id} && Book: ${title}`);
});
$ 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.

Handlers

Request handlers accept the incoming IncomingMessage and the formulating 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) inside a handler, even if you expect a 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! ๐Ÿ˜ป

No special preparation is needed โ€” simply add the appropriate keywords.

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 response.

Coming from Express? Use any middleware you already know & love! ๐ŸŽ‰

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.

// 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');
});
$ 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.
    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.
    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.

// 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)
$ 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 ๐Ÿ˜น

  1. Pass any string to next()

    This will exit the loop & send a 500 status code, with your error string as the response body.

    polka()
      .use((req, res, next) => next('๐Ÿ’ฉ'))
      .get('*', (req, res) => res.end('wont run'));
    $ 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.

    function oopsies(req, res, next) {
      let err = new Error('Try again');
      err.code = 422;
      next(err);
    }
    $ 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.

    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
      }
    }
    $ curl /
    #=> (400) {"error":"Missing CSRF token"}

Benchmarks

Quick comparison between various frameworks using wrk on Node v10.4.0.
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.

Important: Time is mostly spent in your application code rather than Express or Polka code!
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! ๐Ÿ’ƒ

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 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.)

    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. While Polka relies on the native Node.js response methods, 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:

    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:

    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 instead.

    Here's an example with supertest, a popular testing utility for Express apps.

    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

polka's People

Contributors

antoine-coulon avatar antoineneff avatar aral avatar danielruf avatar dependabot[bot] avatar icebob avatar irregularshed avatar jerolan avatar jimvandervoort avatar kukac7 avatar lukeed avatar yuler avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

polka's Issues

Compatibility with Express syntax

Currently the middleware serve-static as in the examples is implemented as

polka()
  .get('/', serve)
  .use('assets', serve)
  .get('/health', (req, res) => {
    res.end('OK');
  })
  .listen(PORT).then(_ => {
    console.log(`> Running on localhost:${PORT}`);
  });

A simple method, like in usage by express

polka()
  .use(serve)
  .listen(PORT).then(_ => {
    console.log(`> Running on localhost:${PORT}`);
  });

is not possible currently, because bwares and apps require a base(for subapp) and the URL's are being stripped off in favour for subapps, so serve-static fails to handle the requests (I might be doing something wrong here, if any please help).

And as in subapps, the URL's are being stripped untill the first slash(/) to obtain the base. For requests like /sw.js, the sw.js is considered the base, and thus the requests are redirected to /sw.js/. This behaviour is expected but, generally(might be personally) it feels proper to have all the requests managed by the middleware. Simply being able to register middlewares to handle requests (not like compression, body-parser etc. though) is helpful.

cors

cors...ALL the cors. It's the way of the future. Or it would be if it were documented :-)

Why uws's requests per second is less than native http.

Compare http and uws.

  1. use http module to create server.

    'use strict';
    
    const http = require('http');
    
    const server = http.createServer((req, res) => {
      res.end('OK');
    });
    server.listen(7000, () => console.log('http server started'));
  2. use uws module to create server.

    'use strict';
    
    const { http } = require('uws');
    
    const server = http.createServer((req, res) => {
      res.end('OK');
    });
    server.listen(7000, () => console.log('http server started'));

Then, I use the ab to test ab -n 10000 -c 50 http://127.0.0.1:7000/

Result:

  1. use http.

    Concurrency Level:      50
    Time taken for tests:   1.936 seconds
    Complete requests:      10000
    Failed requests:        0
    Total transferred:      770000 bytes
    HTML transferred:       20000 bytes
    Requests per second:    5165.36 [#/sec] (mean)
    Time per request:       9.680 [ms] (mean)
    Time per request:       0.194 [ms] (mean, across all concurrent requests)
    Transfer rate:          388.41 [Kbytes/sec] received
  2. use uws.

    Concurrency Level:      50
    Time taken for tests:   401.269 seconds
    Complete requests:      10000
    Failed requests:        0
    Total transferred:      400000 bytes
    HTML transferred:       20000 bytes
    Requests per second:    24.92 [#/sec] (mean)
    Time per request:       2006.346 [ms] (mean)
    Time per request:       40.127 [ms] (mean, across all concurrent requests)
    Transfer rate:          0.97 [Kbytes/sec] received

How to test Polka's middleware with supertest

Polka does not work well with supertest. Is there alternative yet simple way to test Polka's middleware or router handler in a similar fashion?

FYI, I'm using Polka + TypeScript + Jest.

healthcheck.js

module.exports = function healthcheck() {
  return (_, res) => res.end('OK');
};

healthcheck.spec.js

// With `supertest` in Express...

const express = require('express'); // const polka = require('polka');
const supertest = require('supertest');

const healthcheck = require('../healthcheck.js');

describe('test-route', () => {
  it('returns', async (done) => {
    try {
     const app = express()); // const app = polka();
     const rq = supertest(app.use('/healthcheck', healthcheck()); // throw error when using `polka`

     rq.get('/healthcheck')
        .expect(200)
        .end((err, res) => {
          if (err) throw err;
          
          expect(res).toStrictEqual('OK');
          done();
        });
    } catch (e) {
      throw e;
    }
  }, 10e3);

});

Handling errors in request handler

Not sure if this has been answered. I could find anything relevant here and I might have overlooked any similar issues.

This is something I encountered now.

const polka = require('polka');

polka()
  .get('/', async (req, res /** next is undefined */) => {
    // How do I handle this error? In Express, we have `next` in a request handler to forward the error, but in Polka, there is no `next` method available.
    throw new Error('test error'); // UnhandledPromiseRejectionWarning
  })
  .listen(3000);

This is what we normally would do in Express.

const express = require('express');

express()
  .get('/', async (req, res, next) => {
    try {
      const d = await doMoreStuff();
      return res.send(d);
    } catch (e) {
      return next(e); // Forward error to an error handler.
    }
  })
  .use((err, req, res, next) => {
    // Ultimately, handling error here...
  });
  .listen(3000);

Please advise on how we can deal with such situation.

Problem with passport-local

Thanks for nice project, have benchmark it and performance better compare to express

but stumble with passport-local which require res.redirect method which not available in polka
any workaround how to add res.redirect ?
still working but if auth problem will cause crash

Sub-Applications

  • Change handler signature to take info parameter again instead of next
  • Move 501 check to this.server initializer instead of handler
  • [Maybe] Rework use() to accept a pathname filter

Ability to get children of req object

I'm trying to set a variable as the value of either req.body.token, req.query.token, or req.headers['x-access-token'] in a middleware function, but I'm running into issues.

In Express, this works:

const app = express()

function apikey(req, res, next) {
  let userToken = req.body.token || req.query.token || req.headers['x-access-token']
  if (!token) return res.status(401).end('no token provided')
  next()
}

app.use(apikey)

If a token is present in the body, it uses that, and so on.

When I try the equivalent in Polka, I'm getting a TypeError: Cannot read property 'token' of null When I use the Async example expressed in the README I actually get the same error.

Comparison with Japronto?

Hey Luke, first of all awesome project.

Its not an issue but a comparison with a complete different framework of a different language. Since I want to cut down server cost & all & provide an API server for a SAAS but I work with Node JS & have only little experience with Python.

So I was looking at the project Japronto its freakishly fast. More info about it here. I've checked the benchmarks & I don't understand which ones to compare between Japronto & Polka.

Polka benchmarks says 41053.98 & then 414739 requests in 10.10s, which ones to compare โ“

Can Polka be as fast as Japronto like it says it gives 1 million requests/secondโ“

Polka always running onError handler

I am using polka v0..5.0 and here is my code

import polka from 'polka';

const httpserver = polka({
        onError: (e) => {
          console.log('Error handling request');
          console.log(e);
           }
      });
 httpserver.get('/', (req, res) => {
      console.log('Request received');
      res.end('Hello World!');
    })
 httpserver.listen(3000, err => {
   if(err) {
    console.log(err);
   }
  })

When I access the route '/', I can see "Request received" in terminal and "Hello world" in browser but Polka is also running onError handler each time I access '/' route with error 404. Why polka is running onError handler even there is no un-handled exception in route handler.

App mount path stripped to a of cardinality of 1

Hey, thanks for writing this. I'm a huge fan of Express/NodeJS web service alternatives.

It looks like the cardinality of the mounting path of each middleware/app is stripped to a limit of 1.

Example:

const app = require('polka')();

app
    .use('/foo/bar/baz', function(_, res) {
        res.end();
    })
    .listen(3000);

If I visit localhost:3000/foo/bar/baz, I will get a 404. I think this is because of the value function, it checks for the first slash after the leading. So, if I provide:

/foo/bar/baz, the first slash is the zeroeth position of the string and the string itself is stripped to foo.

Is this by design? If this is done in error, it can easily be fixed with some language around how the trailing slash should be considered, or by using lastIndexOf. It also works if I just don't consider the value function at all.

Additionally, it looks like there is a mountpath instance property, but it is not set from the constructor, perhaps this could be used in some way?

Polka.all() handler

Implement a app.all() handler similar to the express' that looks like

app.all(path, callback [, callback, ...]);

and

  • if path is * then run the callbacks as global middlewares
  • if path matches a base then run the callbacks as global middlewares (with base stripped) before calling the base handler

I think this might be of low priority and may degrade performance but might be helpful.

Issue with sapper

Hello!

So I was trying Sapper with Polka, and it was working great.

Until I found a weird behaviour, that I'm not sure if it's pokla or sapper. I changed pokla for express and everything works great again. Lets explain the issue:

I needed to serve static files form more than one directory, and sapper by default uses sirv and looks like it's not recursive, ie, wont serve subdirectories (didnt found any option about it either).

So I added a second "instance" .use('images', serve('assets/images')) and it didnt work. I was like WHAT? so i "unplugged" sapper from the pipeline and it served the static files like so /images/mysexypic.png.

I thought it was a sapper issue, and maybe it is, but it worked perfectly by using express with it. So by first attempt I think it's something related to polka.

My express config that works:

    var app = express();
    app
	.use('/images', serve('assets/images'))
	.use(
		compression({ threshold: 0 }), 
		sirv('assets'), 
		sapper({ routes, App })
	)
	.listen(process.env.PORT)
	.on('error', (error) => console.log(error));

Pokla version:

    polka() 
	.use('/images', serve('assets/images')) 
	.use(
		compression({ threshold: 0 })
		sirv('assets')
		sapper({ routes, App }) //commenting out sapper makes it work.
	)
	.listen(process.env.PORT)
	.catch(err => {
		console.log('error', err);
	})

Summoning @Rich-Harris just in case ๐Ÿ‘ป

Optionally mount middleware to `base` pathname

This would remove one of the breaking differences from Express, and from Fastify for that matter.

I realized that I used this pattern more than I thought I did; aka, I want to support it now ๐Ÿ˜†

By default, middleware will still run globally.

polka().use(foo, bar);

But if a middleware is meant for a specific resource or "base" pathname, you can specify one (or more) middleware to run only on that base path.

Base-specific middleware will run after global-level middleware.

polka()
  .use(foo, bar) // global, aka all routes
  .use('admin', authenticate) // on any /admin/* path

You can also mount sub-applications to a base path. This will auto-prefix all fo the sub-app's routes with the base.

You may also mount middleware for the sub-apps.

const sub = polka().post('/', noop);

polka()
  .use(foo, bar) // global
  .use('posts', authenticate, sub) // run auth before route handler(s)

Add example with Nuxt.js

Hi, I've been trying to get this working to render Nuxt.js as described here using a server.js in the root of the [example]https://github.com/nuxt-community/starter-template). At some point I could get the server to start with Nuxt as a render middleware, but visiting the site showed 'not found'. The app crashes running npm run start ("NODE_ENV=production node server.js"):

`const { Nuxt, Builder } = require('nuxt')

const app = require('polka')()
const isProd = (process.env.NODE_ENV === 'production')
const port = process.env.PORT || 3000

// We instantiate nuxt.js with the options
const config = require('./nuxt.config.js')
config.dev = !isProd
const nuxt = new Nuxt(config)

// Render every route with Nuxt.js
app.use(nuxt.render)

// Build only in dev mode with hot-reloading
if (config.dev) {
new Builder(nuxt).build()
.then(listen)
.catch((error) => {
console.error(error)
process.exit(1)
})
}
else {
listen()
}

function listen() {
// Listen the server
app.listen(port)
console.log('Server listening on localhost:' + port + '.')
}`

[Question] Any plan to open a Github org named Polka

First of all, Polka is an awesome take for being one of the fastest web servers written in Node.js.

Just curious to know if any plan to move everything to a Github org named Polka and scoped everything in @polka/<package_name>, but polka itself can choose not to be a scoped package as I noticed most of the packages are @polka scoped.

named parameters and req.query

I have a route defined with

app.put('/hash/:id', (req, res) => {

and when a do a PUT with a querystring as well as a named param, eg

http PUT "localhost:12227/hash/ab?ts=123132&ryan=3232"

the query is appended to the named param, eg

 console.log(req.params.id, req.query)
> ab?ts=123132&ryan=3232 ts=123132&ryan=3232

polka.json() When?

Express has it's own parser so you don't have to use 3rd party middleware like body-parser, when will polka be adopting this.

Thank you

This is: minimal, useful, and ruthlessly simple. Thanks! ๐Ÿš€

add json send middleware for response helpers

@lukeed I noticed the response helpers were marked as todo. I believe the most popular response helper is res.json.

I was able to make this work

const jsonSend = (req, res, next) => {
  res.json = (payload) => {
    res.setHeader('Content-Type', 'application/json');
    const jsonResponse = JSON.stringify(payload);
    res.end(jsonResponse);
  }

  next()
}

polka()
  .use(jsonSend)

If this looks good, please suggest how we can add this in polka

Middlewares run in unexpected order

Unexpected to me, anyway ๐Ÿ˜€ Wanted to raise an issue before attempting a PR, in case this is the desired behaviour.

Repro: https://github.com/Rich-Harris/polka-middleware-order-repro

Essentially, it seems that middlewares without a basepath run before middlewares with one, regardless of the order in which they're added to the app. This means that a generic app.use(foo) will take precedence over an earlier app.use(basepath, serve('assets')), which can prevent assets from being served.

Use `req.path` instead of `req.pathname`?

This would be breaking for Polka, but is probably okay considering we're still pre-1.0.

The "reason" for doing do is consistency / parity with Express --- a couple popular middleware use req.path for internal logic. If Polka is gunning for Express compatibility, it should probably do the same.

I'd also remove req.pathname since there's no point in having both keys.

Variadic Route Handler Assignment

Currently, Polka only allows 1 handler per route pattern. Express, on the other hand, allows you to assign as many as you want, constructing per-route middleware groups.

// no auth
app.get('/items', noop);

// require auth for posting
app.post('/items', authenticate, noop);

// no auth again
app.get('/items/:id', noop);

This is still a maybe as it would require changes to Trouter. Speed is still the #1 priority, and you can already achieve similar behavior by manually calling middleware from within the single handler.

Current "workaround"

function authenticate(req, res, next) {
  let token = req.headers.authorization;
  if (!token) return res.end('Token required!');
  // If used as middleware, iterate
  // But if not in loop, return "success" boolean
  return next ? next() : true;
}

// later, in app...

app.post('/items', (req, res) => {
  let isUser = authenticate(req, res); // <~ no next()
  if (!isUser) return; // already sent `res.end` before
  // Continue with route handler
  let body = req.body;
  // ...
});

Package: Express-like Response Helpers

Ported from #5:

Add a middleware package that attaches helpers directly to the `res` object?

This would mimic Express, potentially offering all/most of them.

This is a low(er) priority, but would definitely be helpful for those who can't live without the Express built-ins. ๐Ÿ˜†

Why no throwing from middleware?

I have four questions:

  • How much of a performance hit is it to catch errors in middleware?
  • Doesn't this mean that an error in a route will bring down the entire server?
  • Is there any way a user can re-introduce catching in middleware, short of wrapping every middleware function in a try/catch?
  • What about uncaught promises?

Examples

Should definitely include a directory of examples since a lot of people prefer to learn by doing. ๐Ÿ™‹โ€โ™‚๏ธ

Also is a nice way to ensure/validate that these things are possible.

  • Async
  • HTTPS
  • JSON API
  • Static Assets
  • Sub-applications
  • Body parser
  • Others?

Remove Promise around Listen

This seemed like a good initially just because of then/catch, but there are a few problems with this:

  1. Calling .listen() does not return the Polka instance directly, so it forces listen to always be called last.

  2. It also prevents one-line initializers for a server.

    const server = polka().listen(3000);
    //=> server is Promise, not Polka instance
  3. The top-level then and catch blocks make it seem like catch is the global catch-all handler.

Instead, .listen() would expose the underlying server.listen method directly, meaning that signature is the same as all other servers.

polka().listen(PORT, err => {
  if (err) throw err; // or whatever you want to do
  console.log(`> Ready on localhost:${PORT}`);
});

This would also remove 5 lines of code ๐Ÿ˜†

how to parse body data?

Hi, in express we use body-parser and then extract body data via req.body. How can we do that in polka?

Root Wildcard is too Greedy

Using a root wildcard (/*) is a little too greedy, meaning that it prevents any sub-apps or bware from running.

As mentioned here, this is problematic:

let noop = () => {};

polka()
  .use('foo', noop)
  .use('bar', noop)
  .get('*', (req, res) => {
    res.end('THIS ALWAYS RUNS')
  })
  .listen(PORT)

The simplest way around this is to move the get('*') into options.onNoMatch, but that's probably too different from Express. It also doesn't constrain to GET requests.

const onNoMatch = (req, res) => {
  res.end('THIS ALWAYS RUNS')
});

polka({ onNoMatch })
  .use('foo', noop)
  .use('bar', noop)

Response helpers

There are a few options here... ๐Ÿค”

  1. Add a standalone package that releases basic send, json, etc helpers

    send(res, code, data);

    This would mean removing app.send from the core server, which isn't too bad since it's only accessible if you define a app = polka() ahead of your route handler(s).

    A) Do we want to be explicit & require everything to be set?

    send(res, code=200, data='', headers={}) {
      res.writeHead(code, headers);
      res.end(data || STATUS_CODES[code]);
    }

    B) Or do we want to infer Content-Type based on the data type (zeit/micro does this)?

    const TYPE = 'Content-Type';
    const LENGTH = 'Content-Length';
    
    send(res, code=200, data='', headers={}) {
      let len, type=headers[TYPE];
      data = data || STATUS_CODES[code];
      // pseudo-code
      if (data instanceof Stream) {
        type = type || 'application/octet-stream';
      } else if (data instanceof Buffer) {
        len = data.length;
        type = type || 'application/octet-stream';
      } else if (typeof data === 'object') {
        data = JSON.stringify(data);
        len = data.length;
        type = 'application/json';
      } else {
        type = type || 'text/plain';
        len = data.length;
      }
     // update
     headers[TYPE] = type;
     headers[LENGTH] = len;
     // write
     res.writeHead(code, headers);
     res.end(data);
    }
  2. Add a middleware package that attaches helpers directly to the res object?

    This would mimic Express, potentially offering all/most of them.

Please vote ๐Ÿ™




Please know that all options would live as external packages and not inside Polka directly.

Calling Express 'super fast' is a bug

The readme clearly has issues. Express is clearly one of the most bloated and performance killing projects ever to see the light of dawn. It is up there among Socket.IO as top most overhyped pieces of garbage ever written by man kind. Don't use this marketing opportunity to spread misinformation, just say what you intended to say! It sucks and you know it.

Polka with Sapper and Apollo-server-express

I am trying to use graphql apollo-server-express with Sapper-template but I am getting 404 page it seems sapper is not allowing to add a route. Here is my code.

  graphQLServer.applyMiddleware({app, path:'/graphql'}); // app is from polka

Rich said on disord that it could be related to the way Polka runs middleware in a different order. Try using Express

@lukeed do you have any idea how to solve this issue.

Feature request: add .static() method

Express provided a nifty helper for defining a static folder for binary assets, such as styles, images, fonts, etc.

Would be great to have a similar feature, unless this was explicitly left out in order for folks to derive their own.

Parameters in sub-apps

Am I correct in assuming that using route parameters as part of the base in use are not supported?

It would be great if I could do the following, for example:

const polka = require('polka')

const clientHandler = require('./clientHandler')
const contactSubApp = require('./contactSubApp')

polka()
  .get('/:clientid', clientHandler)
  .use('/:clientid/contacts', contactSubApp)

Error while using multiple Sub Apps

/Users/**/Documents/Node.js/Bot List Final/node_modules/polka/index.js:99
                let loop = _ => res.finished || (i < len) ? arr[i++](req, res, next) : fn(req, res);
                                                                    ^

TypeError: arr[(i++)] is not a function
    at loop (/Users/**/Documents/Node.js/Bot List Final/node_modules/polka/index.js:99:55)
    at next (/Users/**/Documents/Node.js/Bot List Final/node_modules/polka/index.js:98:63)
    at Array.arr.push (/Users/**/Documents/Node.js/Bot List Final/node_modules/polka/index.js:54:69)
    at loop (/Users/**/Documents/Node.js/Bot List Final/node_modules/polka/index.js:99:55)
    at next (/Users/**/Documents/Node.js/Bot List Final/node_modules/polka/index.js:98:63)
    at Array.jsonParser (/Users/**/Documents/Node.js/Bot List Final/node_modules/body-parser/lib/types/json.js:110:7)
    at loop (/Users/**/Documents/Node.js/Bot List Final/node_modules/polka/index.js:99:55)
    at Polka.handler (/Users/**/Documents/Node.js/Bot List Final/node_modules/polka/index.js:100:3)
    at Server.emit (events.js:182:13)
    at parserOnIncoming (_http_server.js:658:12)
    at HTTPParser.parserOnHeadersComplete (_http_common.js:109:17)

Upon using multiple sub apps, this error is thrown. If only 1 sub app is used the functionality works as expected.

How to use it with PassportJS

For authentication PassportJS requires that something like this is used... but it looks like Polka doesn't support route-based middlewares

app.post(
  '/login',
  passport.authenticate('local', {
    successRedirect: '/',
    failureRedirect: '/login'
  }),
  (req, res) => {
    // something after the authentication
  }
);

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.