npm install one-track --save
if intending to use with koa2
npm install one-track one-track-koa koa@next koa-bodyparser@3 --save
note that none of those are required to use this package, but they are used in the demonstrations below
import Koa from 'koa';
import bodyParser from 'koa-bodyparser';
import RouteManager from 'one-track';
import RouteMiddleware from 'one-track-koa';
import { retrieve_user } from './your-app';
const app = new Koa();
const Router = new RouteManager();
Router.GET('/user/:id', retrieve_user);
app.use(bodyParser());
app.use(RouteMiddleware(Router))
The above code will create the routes described, and call the functions implied with the following argument signature
(params, headers, body, app)
where app
is the Koa app context. Router supports standard CRUD opperations
for example:
Router.GET('/user/:id', retrieve_user);
Router.POST('/user', create_user);
Router.PUT('/user/:id', update_user);
Router.DELETE('/user/:id', delete_user);
Routers can be created from other Routers without disturbing the original
const Router = new RouteManager();
Router.GET('/user/:id', retrieve_user);
Router_2 = new RouteManager(Router);
Router_2.Post('/user', create_user);
In the above example, the Router object has 1 route (GET '/user/:id'
), and the
Router_2
object has 2 (GET '/user/:id'
and POST '/user'
);
Sometimes it is important to dictate the scope that a function is called, defining
this
for the context of the function. To do this, an array notation of [scope, function]
is used.
If the function is a string, the context is searched for the function's name
Router.get('/user/:id', [user, 'find']); //=> user['find'].call(user, ...args)
Otherwise the function is called with the context given
Router.get('/user/:id', [user, retrieve_user]); //=> retrieve_user.call(user, ...args)
Argument translators can be used to map the unweildy data from the request into something an environment-agnostic method can process
import Koa from 'koa';
import bodyParser from 'koa-bodyparser';
import RouteManager, { Send } from 'one-track';
import RouteMiddleware from 'one-track-koa';
import { retrieve_user } from './your-app';
// translate the arguments that come from the koa app
// into something that the retrieve_user method can understand
const koa_retrieve_user = Send((params, headers, body, app) => [params.id]).to(retrieve_user);
Router.GET('/user/:id', koa_retrieve_user);
This allows you to create functions that do not care about the environment that they ultimately live in. This also allows functions to live in multiple contexts, and for contexts to change, without affecting the business logic
Argument translations also respond to the array notation given above
Send((params, headers, body, app) => [params.id]).to([User, retrieve_user]);
import Koa from 'koa';
import bodyParser from 'koa-bodyparser';
import RouteManager from '../index';
import RouteMiddleware from '../one-track-koa';
const app = new Koa();
const Router = new RouteManager();
function hello(place){
console.log("in hello");
return "hello, "+place
}
function goodbye(){
console.log("in goodbye");
return "goodbye"
}
function say(phrase){
console.log("in say");
return "say "+phrase;
}
Router.GET('/hello/:place', hello); // => hello, {place}
Router.GET('/say/hello/:place', hello, say); // => say hello, {place}
Router.GET('/say/goodbye', goodbye, say); // => say goodbye
Router.GET('/goodbye', goodbye); // => goodbye
app.use(bodyParser());
app.use(RouteMiddleware(Router))
app.listen(3000);
console.log("app is listening");
The two code samples below are functionally equivalent. The first simply does the job, the second takes advantage of argument translators to abstract the request from the process of authentication
function simple_auth(params, headers, ...args){
if(sign(headers.user_id, SECRET) !== headers.authorization) throw {code:401, message:"Unauthorized"}
return [params, headers, ...args]
}
// this is a simple signature method that should not be used in production
const sign = (user, secret=SECRET) => user+secret;
// step 1: translate args to a method your validation function will understand.
// in this case we are returning the user id and auth sent in the header
// followed by the rest of the arguments in the appropriate order
function authentication_arguments(params, headers, ...args){
return [headers.user_id, headers.authorization, params, headers, ...args]
}
// step 2: validate the user and return the remaining arguments
function validate_authentication(user, token, ...args){
if(sign(user, SECRET) !== token) throw {code:401, message:"Unauthorized"}
return args
}
// step 3: create the authentication middleware by sending the proper authentication
// arguments to the authentication checker
const authenticate = Send(authentication_arguments).to(validate_authentication);
// example request headers:
// user_id : APPLE
// Authorization: APPLESAUCE
R2.POST('/goodbye/:say', authenticate,
Send((params)=>[params.say]).to(goodbye),
say);
One-Track does not require a server to work. Any application which can receive events can be routed through this software.
import RouteManager, { Send, GET } from 'one-track';
const Router = new RouteManager();
Router.GET('/path/:id/other/:info', another_function);
In this case, calling
Router.find(GET, '/path/some_id/other/text_to_send', arg1, arg2)
is basically equivalent to calling
(function(){
return new Promise(resolve, reject){
resolve(another_function.call(this, ...arguments));
}
})()
where arg_1
and arg_2
would be passed into another_function
, since
they are being passed in through the final arguments send in with Router.find
.
So, the koa middleware really ends up being as simple as setting the body of
the response to the result of the request path's function, with the relevant
info of the body, headers, and Koa context passed in as arguments
The default one-track-koa middleware is as follows. However, only the first
two arguments sent to .find()
are required. The remaining arguments indicate
the arguments that will be sent to the function indicated by .find()
.
In other words, altering this middleware to send different information to your
application is as trivial as adding, removing, or changing arguments after the
reference to ctx.path
const middleware = (Router) => async (ctx, next) => {
ctx.body = await Router
.find(ctx.method, ctx.path, ctx.request.body, ctx.headers, ctx)
.catch((err) => {
console.log(err.stack||err);
ctx.status = err.code||ctx.status;
return err;
});
}
export default middleware