1.0.0-beta.7 • Published 6 years ago

coiso v1.0.0-beta.7

Weekly downloads
3
License
ISC
Repository
github
Last release
6 years ago

coiso

Versatile HTTP API template built out of a need to have predictable patterns when building HTTP services. It can be used both as a command line utility and a library.

Install

npm i --save coiso

Use

Create a resources folder in your project's root and enter it:

$ mkdir resources && cd resources

Then, create an index.js file and export a function that accepts the standard NodeJS HTTP handler signature:

module.exports = (req, res) => {
    res.end('Hello World!');
}

Next, add a start script in your package.json file:

{
    "scripts": {
        "start": "coiso"
    }
}

Finally, the server can be started like this:

$ npm start

You can now consume http://localhost:8080/.

From this moment on, you can create as many routes as you want, both Request/Response or WebSocket style.

CLI

coiso's command line interface wrap around it's API to provide a convenient scaffolding to help create projects faster and in a consistent fashion.

coiso expects to find one directory in the root of your project — resources.

Resources are Javascript modules whose the resources-relative path map to a HTTP route. If the file name ends in .ws.js it will be considered a WebSocket handler, otherwise it is a request handler.

  • A request handler exports a function thats receives unaltered NodeJS request and response objects as arguments.
module.exports = (req, res) => {
    res.end('Hello World!');
}
  • A WebSocket handler exports a function that receives unaltered websocket and NodeJS request objects as arguments.
module.exports = (ws, req) => {
    // Echo back the received messages
    ws.on('message', ws.send);
    
    // On connect
    ws.send('Hello websocket');
};

Both handler types receive an optional request context object as the third argument.

There are some simple rules for naming the files that define your routes:

  • The file resources/index.js corresponds to the root (/) of your app.
  • A file called resources/about.js corresponds to the /about route. resources/about/index.js is treated the same as resources/about.js.
  • A file called resources/blog/[slug].js corresponds to the /blog/:slug route in which case params.slug is available to the route via the request context object. This is called parametric routing.
  • A file called resources/[blog_name]/[slug].js corresponds to the /:blog_name/:slug route, in which case params.slug and params.blog_name are available to the route via the request context object. This is called multi-parametric routing.
  • Files and directories with a leading underscore do not create routes. This allows you to co-locate helper modules and components with the routes that depend on them — for example you could have a file called resources/_helpers/datetime.js and it would not create a /_helpers/datetime route

Configuration

coiso looks for a coisoconfig.toml file in the project root and if it finds one loads and transforms it to a plain javascript object for consumption. This file is split in 2 sections:

  1. core - coiso's own configuration directives
  2. the rest - your own configuration directives

Initialization

Frequently, APIs need to perform some tasks before they start accepting requests. For this purpose coiso looks into your module package.json main field, loads it and treats it as an async function with the signature:

module.exports = async function setup(server /*CoisoServer*/, config/*configuration object*/) {
    // Implement your logic here
}

Examples include:

  • load an in memory cache
  • fetch runtime configurations
  • execute an API call

If this function throws coiso will log the error and exit. If it succeeds, it moves on to setup the HTTP server and start accepting requests.

The return value of this function is ignored.

More Info

Run the help command to get to know what else the CLI offers:

$ npx coiso --help

Deploy to Production

Make sure you run through this checklist before deploying to production:

  • set environment variable NODE_ENV=production
  • set log level to a level higher than warn
  • run with node --abort-on-uncaught-exception

TODO

  • Codebase cleanup
  • Add log support (pino, logpp) (Done)
  • Unit tests
  • Integration tests
  • Improve documentation
  • http/2 support?
  • windows support?
  • Server metrics (performance hooks; can be done by an external library)
  • cors support (can be done as middleware)
  • circuit breaker (can be done as middleware)
  • cluster support (outside intended scope)
  • websocket support? (done)

Benchmarks

The bulk of time is spent in your application code rather than in coiso's code, so don't expect coiso to solve your performance problems. Nevertheless, coiso aims to be as lightweight as possible and performs similarly to a NodeJS raw HTTP server, latency-wise.

Synthetic Hello World Benchmark

This benchmark is not representative of a real world situation but it asserts the claims made above.

The test consists in a brief 15 seconds warmup:

$ echo "GET http://localhost:8080" | vegeta attack \
    -duration=15s \
    -rate 500 \
    -keepalive false \
    -timeout 0.3s \
    -workers 200 \
  | vegeta report

and then exercises the endpoint for 10 minutes at a rate of 1000 requests/seconds using 200 workers. A request is considered to timeout if it takes more than 100 milliseconds to reply. Keep-alive is disabled by default:

$ echo "GET http://localhost:8080/" | vegeta attack \
    -duration=600s \
    -rate 1000 \
    -keepalive false \
    -timeout 0.1s \
    -workers 200 \
  | vegeta report
Raw NodeJS Server (see file benchmark/raw-node-server.js)
   $ NODE_ENV=production node benchmark/raw-node-server.js

   Requests      [total, rate]            600000, 1000.06
   Duration      [total, attack, wait]    9m59.965012258s, 9m59.964663988s, 348.27µs
   Latencies     [mean, 50, 95, 99, max]  239.989µs, 206.255µs, 354.381µs, 555.784µs, 107.924215ms
   Bytes In      [total, mean]            7200000, 12.00
   Bytes Out     [total, mean]            0, 0.00
   Success       [ratio]                  100.00%
   Status Codes  [code:count]             200:600000
   Error Set:

coiso (see examples/hello-world)
   $ NODE_ENV=production npx coiso

   Requests      [total, rate]            600000, 1000.06
   Duration      [total, attack, wait]    9m59.964622632s, 9m59.96438675s, 235.882µs
   Latencies     [mean, 50, 95, 99, max]  270.008µs, 202.969µs, 472.339µs, 798.797µs, 87.146503ms
   Bytes In      [total, mean]            7200000, 12.00
   Bytes Out     [total, mean]            0, 0.00
   Success       [ratio]                  100.00%
   Status Codes  [code:count]             200:600000
   Error Set:

Frequently Asked Questions

Q: Why not ExpressJS or KoaJS?

A: Because I believe development time should be spent building business logic instead of focusing on details such as transport, logging, routing, etc. I strongly believe in repeatable patterns and love the idea to treat NFR's as a dependency as opposed to having to think about them everytime one builds something new.

coiso's opinionated filesystem layout was designed for companies that build and maintain HTTP API's as an everyday task. While not perfect, it scales well for a multi-team environment where ownership is spread amongst a large number of people.

In any case, ExpressJS and KoaJS (and many others) are exceptional tools to build HTTP API's.

Q: What does coiso offer?

A: coiso is just a wrapper around the raw NodeJS HTTP server. It makes opinionated decisions around libraries to handle common requirements such as routing, logging, url parsing, etc.... It's designed to be:

  • Easy: Designed for usage with async/await
  • Complete: Features initialization routine, logging, routing, websockets, webping and request body parsing
  • Agile: Built for easy development, easy deployment and containerization
  • Simple: Small and explicit API surface
  • Standard: Just HTTP. Optional lock in to the RequestContext object
  • Explicit: No middleware - modules declare all dependencies
  • Operations-friendly: Open to metric agents and designed with post-mortem analysis in mind

Prior Art

All inspired this package:

License

MIT