coiso
v1.0.0-beta.7
Published
Opinionated browser facing HTTP API template
Downloads
4
Readme
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 asresources/about.js
. - A file called
resources/blog/[slug].js
corresponds to the/blog/:slug
route in which caseparams.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 caseparams.slug
andparams.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:
- core - coiso's own configuration directives
- 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: