/polkadot

The tiny HTTP server that gets out of your way! ・

Primary LanguageJavaScriptMIT LicenseMIT

polkadot

Polkadot

version travis codecov downloads install size
The tiny HTTP server that gets out of your way~!

Features

  • Intentionally Minimal
    Build your own stack! Polkadot doesn't even include a router.
    Polkadot can accommodate any & all of your preferences. This is your app.

  • Extremely Lightweight
    Even with all dependencies, Polkadot weighs less than 15kB!
    It's the perfect candidate for serverless environments.

  • Simple yet Flexible
    You can learn the "framework" during lunch and deploy before the day's end.
    There's no limit to what you can do – nor how you can do it.

  • Highly Performant
    Because Polkadot does so little, it's as "blazing fast" as they come.

  • Async Support
    It's 2019 – time to await all the things~!

Install

$ npm install --save polkadot

Usage: CLI

Just specify an entry file – that's it! 🎉
If you do not provide one, then index.js is assumed.

Customize the port by setting the PORT environment variable.
The PORT will default to 3000 if left undeclared.

Important: An error will be thrown if the PORT is in use.

# Examples:

$ polkadot
$ polkadot app.js
$ PORT=8080 polkadot app.js
// index.js
const { get } = require('httpie');

module.exports = async (req, res) => {
  let uri = `https://jsonplaceholder.typicode.com/posts`;
  if (req.query.id) {
    uri += `/${req.query.id}`;
  }
  let res = await get(uri);
  return res.data;
}

Usage: Programmatic

For those who need to control the lifecycle and/or lifespan of their server(s), a programmatic API is available.
Similarly, this is the only way to customize the underlying server itself (see the with-https example).

const { get } = require('httpie');
const polkadot = require('polkadot');

const app = polkadot(async (req, res) => {
  let uri = `https://jsonplaceholder.typicode.com/posts`;
  if (req.query.id) {
    uri += `/${req.query.id}`;
  }
  let res = await get(uri);
  return res.data;
});

app.listen(3000, err => {
  if (err) throw err;
  console.log('> Running on localhost:3000');
});

API

Note: The following pertains to Programmatic Usage only

polkadot.handler(req, res)

Returns: Function

The main polkadot handler.

It parses the req (see IncomingMessage) and assigns value to the path, query, and search keys1.
It also waits until the res (see ServerResponse) has been terminated or until data is returned.2

1 See @polka/url for further details.
2 See Handlers for varying return types.

polkadot.listen()

Returns: http.Server

Boots (or creates) the underlying http.Server for the first time.
All arguments are passed directly to server.listen with no changes.

Handlers

Every handler receives a req (IncomingMessage) and a res (ServerResponse) pair of arguments.
These are the true, mostly unfettered1 instances that the http.Server created, so you have full access to all native APIs.

1 Before calling your handler(s), @polka/url assigns value to req.path, req.query, and req.search keys.

There are multiple ways to format or return the server response. You may mix and match them, as you are not restricted to a particular format in your application(s).

1. Use Native APIs

Because you have direct access to res (see ServerResponse), you can set headers, the statusCode, and/or write response data with the core Node.js methods:

module.exports = function (req, res) {
  res.statusCode = 400;
  res.setHeader('Content-Type', 'application/json');
  res.end('{"error":"Bad Request"}');
}

2. The @polka/send-type library

The @polka/send-type library is a utility function that composes your response through a simple API. It also inspects your response data and will auto-set its Content-Type (if unspecified) and Content-Length headers for you. Additionally, it will stringify Objects into JSON on your behalf!

Note: Check out its Data Detections documentation.

Because the @polka/send-type library is already a dependency of polkadot, using it comes at no extra cost!

const send = require('@polka/send-type');

module.exports = function (req, res) {
  send(res, 400, {
    error: 'Bad Request'
  });
}

3. Return data

Polka uses the @polka/send-type library internally, which allows you to return data directly from your function handler instead of using the native APIs to format the response manually.

Because of this, @polka/send-type can inspect your outgoing data and determine its Content-Type (if unspecified) and Content-Length response headers on your behalf. Similarly, it will automatically convert Objects into JSON strings.

Note: Check out its Data Detections documentation.

module.exports = function (req, res) {
  res.statusCode = 400;
  return {
    error: 'Bad Request'
  };
};

4. Async Returns

Polkadot works great with asynchronous functions!
You can certainly fetch data from external APIs, interact with databases, ...etc without any problems.

Of course, your asynchronous chain(s) may also use native res APIs, the @polka/send-type helper, or may return data directly. All options are always available!

The only rule is that if your handler ends in a Promise or AsyncFunction, that function must be returned so that Polkadot can resolve it on your behalf.

Important: The use of AsyncFunction is only supported in Node versions 7.4 and above.

// For demo, not required
const send = require('@polka/send-type');

// Using Promises
module.exports = function (req, res) {
  // must `return` the Promise
  return isUser(req).then(user => {
    if (user) {
      send(res, 200, { user });
    } else {
      send(res, 401, 'You must be logged in');
    }
  });
};

// Using AsyncFunctions
module.exports = async function (req, res) {
  const user = await isUser(req);
  if (user) {
    send(res, 200, { user });
  } else {
    send(res, 401, 'You must be logged in');
  }
}

Routing

Before your handler is called, Polkadot will parse the request to provide you with some core information.
It will use @polka/url to make req.path, req.search, and req.query available.

While polkadot does not include a router, you can create your own or import any router of your choosing!
Please check out the with-router example that uses Trouter to build a full JSON resource.

Below is a simple example that serves images & video files based on the incoming path:

const fs = require('fs');
const { join } = require('path');
const mime = require('mime/lite');

const assets = join(__dirname, 'assets');

function sendfile(res, dir, filename) {
  const file = join(assets, dir, filename);
  if (fs.existsSync(file)) {
    res.setHeader('Content-Type', mime.getType(file));
    fs.createReadStream(file).pipe(res);
  } else {
    res.statusCode = 404;
    res.end('File not found');
  }
}

// Supports: /images?filename=foobar.jpg
// Supports: /videos?filename=foobar.mp4
module.exports = function (req, res) {
  const { filename } = req.query;
  if (req.path === '/images') {
    sendfile(res, 'images', filename);
  } else if (req.path === '/videos') {
    sendfile(res, 'videos', filename);
  } else {
    res.statusCode = 404;
    res.end('Unknown filetype');
  }
}

Error Handling

You must handle your own errors. This is because Polkadot will not dictate your application design nor its behavior – and how an application responds to and handles errors is a large, important part of its design!

Please visit the with-middleware or the with-router examples.
They are both more complex demonstrations that handle errors while chaining or composing functions together.

For simple endpoints, it's very straightforward (as it should be):

// Send 404 if unknown path
module.exports = function (req, res) {
  if (req.path !== '/') {
    res.statusCode = 404;
    return 'Page not found';
  }
  return 'OK';
}

// ---

const { get } = require('httpie');

// Send 404 if ID unknown to external API
module.exports = async function (req, res) {
  try {
    const ID = req.query.id;
    let { data } = await get(`https://example.com/users/${ID}`);
    send(res, 200, { user:data });
  } catch (err) {
    const code = err.statusCode || 404;
    const message = err.data || 'User not found';
    console.error('Error: ', req.query.id, code, message);
    send(res, code, message);
  }
}

Benchmarks

For performance results and comparisons, please check out the bench directory.

Prior Art

Polkadot is the "little sibling" to Polka. It's effectively the core of Polka, stripped of all routing, middleware sequencing, and Express compatibility layers. While Polka is already leaner than most everything else, there was an opportunity to further satisfy the minimalists and make a microscopic version of Polka – hence, polka•dot.

Additionally, Polkadot follows in the footsteps of micro, which was the first HTTP framework of our kind (to my knowledge).

License

MIT © Luke Edwards