@ashnazg/atst
v0.4.0
Published
Ashnazg Tiny Server Template -- a koa2-based toolkit for RAD of versioned APIs
Downloads
22
Readme
title: @ashnazg/ATST (Ashnazg Tiny Server Template) sidebar_label: ATST
This is a koa wrapper I use to...
- make the conventions I use to build RESTful APIs more terse and
- add add utilities and middleware that I find useful in all API design
port
You can set port programmatically or through $PORT
const atst = require('@ashnazg/atst'); // ~/projects/ashnazg-npm/atst/atst.js
const service = atst({port: 1234});
basic public routes
The public instance of @koa/router is exposed at service.public, but for syntactic sugar, that router's get/post/delete/put methods are also available on service itself:
service.get('/status', async ctx => {
return {status: 'running'};
});
Promisy responses instead of ctx.body
You still can just set ctx.body, or you can just return a jsonable object. (See '/status' above.)
standardized error/warning handling
The koa context is extended with ctx.fail and ctx.warn, which take errors in either of these formats:
- ctx.fail(404, 'definitely not there', {optional: "extra json field map"});
- ctx.fail({code: 404, msg: 'definitely', misc: 'fields'});
You can also throw and that'll be converted to ctx.fail().
With or without a ctx.body, the response to client will always include {errors: [], warnings: []}
. 4xx events are shown to the user; 5xx events are logged and only a unique event ID is in the response.
While warnings don't affect the HTTP main code/message, errors do; if there's only 4xx type errors, the highest code will also be used for the HTTP response.
- meaning if one part of your code fails it as a 404, and another also fails it with 500, both are included in
errors:[]
but the response is a 500.
throwing
These three are translated to ctx.fail():
- throw {code: 432, msg: 'no likey'} (note that code:500 is the default)
- throw 407 (koa will set the msg to 'Proxy Authentication Required')
- throw "frisbee" (will default the code to 400.)
Controlling logging
By default, it logs to stdout/err. You can divert access logs to a file:
const service = atst({
access_log_dest: '/var/log/myservice.log',
error_log_dest: '/var/log/myservice.errs'
});
Or to your own handler: const service = atst({ access_log_dest(evt) { // process access event }, error_log_dest(evt) { // process errors that are code >= 500 } });
For short-lived servers in a TDD context, you can also set it to just accumulate logs in memory:
const hits = [];
const errors = [];
const service = atst({
access_log_dest: hits
error_log_dest: errors
});
assert.deepEqual(await curlAtDevServer('/status'), {stuff});
assert(hits.length === 1, 'access event should have been recorded');
assert(errors.length === 0, 'there should be no errors');
User/Pass Authentication
To turn on local passport plugins and the protected route support, pass in a function that takes user/pass and returns a promise to a user record. If the user record includes uid:number, that'll be reflected in access logs.
local() can throw or return undefined to signal a rejection.
const service = atst({
auth: {
keys: [process.env.SECRET1, process.env.SECRET2],
session_length_days: 7,
async local(user, pass) {
return {uid: 1, roles: ['admin']};
}
}
});
atst.authed.get('/secret', async ctx => {
return {secret: 'data'};
});
Session Expiry
set session_length_days
in auth{} or it'll default to 7.
(For testing convenience, you can instead set session_length_ms
.)
External DB for Session Storage
By default, atst uses a per-instance in-memory session cache.
Add three hooks to auth to redirect session management:
const sessions = {};
const service = atst({
auth: {
keys: [process.env.SECRET1, process.env.SECRET2],
session_length_days: 7
async local(user, pass) {
return {uid: 1, roles: ['admin']};
},
async setSession(key, profile) {
sessions[key] = profile;
},
async getSession(key) {
return sessions[key];
},
async deleteSession(key) {
delete sessions[key];
}
}
});
Roles like Admin
By default, atst only sets up public/authed routes.
You can create routing blocks with special permission rules by first defining the test functions at setup, and they can check user profile fields at request time:
const service = atst({
prefix: '/allendpoints',
auth: {
keys: [process.env.SECRET1, process.env.SECRET2],
async local(user, pass) {
return {uid: 1, roles: ['admin']};
},
roles: {
admin(profile) {
return profile.roles.indexOf('admin') !== -1;
}
}
}
});
atst.admin.get('/sensitive-stuff', async ctx => {
return {secret: 'data'};
});
# curl server/allendpoints/admin/sensitive-stuff