Skip to content

The router manages the workflow of http requests to the corresponding controller

License

Notifications You must be signed in to change notification settings

tramwayjs/router

Repository files navigation

Tramway is a simple router for the tramway framework. It includes:

  1. A dynamic routing system that separates routes from routing logic and is adaptable
  2. Restful routes to save time building APIs
  3. Authentication policies that allow for multiple strategies to be used and interchanged without needing the logic throughout the code. and so much more.
  4. Swappable router strategies that keep your app consistent no matter which solution you use, server or server-less.
  5. Includes http-status-codes to have status code enums.

Installation:

  1. npm install tramway-core-router

Example project

https://gitlab.com/tramwayjs/tramway-example

Documentation

Recommended Folder Structure

  • controllers
  • errors
  • policies
  • routes

Routes

Routes are where you store the routes that the Router will take.

A typical routes file in the routes folder would import Controllers and directly assign their actions to a route. The resulting JSON would be consumed and converted by the router into a standard, secure or restful route.

Here's a sample routes file. The order of routes matters and is preserved by the router at all times.

Note: The definition for controllerClass has been removed and the controller attributes only takes an instance now. The Controller action now has an attribute that takes the string name

import MainController from "../controllers/MainController";
import SecuredController from "../controllers/SecuredController";
import StandardAuthenticationPolicy from "../policies/StandardAuthenticationPolicy";
import TestController from '../controllers/TestController';
let standardAuthenticationStrategy = new StandardAuthenticationPolicy();
const routesValues = [
    {
        "methods": ["get"],
        "controller": new MainController(),
        "action": "index"
    },
    {
        "path": "/test",
        "controller": new TestController(),
        "arguments": ["id"],
        "restful": true
    },
    {
        "path": "/hello",
        "arguments": ["name"],
        "methods": ["get"],
        "controller": new MainController(),
        "action": "sayHello"
    },
    {
        "path": "/secure",
        "methods": ["get"],
        "controller": new SecuredController(),
        "action": "index",
        "policy": standardAuthenticationStrategy
    },
    {
        "arguments": ["name"],
        "methods": ["get"],
        "controller": new MainController(),
        "action": "sayHello"
    },
    {
        "arguments": ["name"],
        "methods": ["post", "put"],
        "controller": new MainController(),
        "action": "postTest"
    }
];
export default routesValues;

Route specs

Attribute Expected Values Default Values Notes
path Unparameterized path string "/" If no path is specified, the router will default to root.
controller Controller undefined If no Controller is specified, the app will break
action string undefined The name of the Controller method dedicated to handling the route. If not a restful route and no action is specified, the app will break
restful boolean undefined If the controller is a restful controller - note that action attribute will be ignored
methods ["get", "post", "put", "delete", "all"] ["get"] Indicates which http methods get assigned the controller. Restful routes will ignore this setting as it is automatically bound by implenentation
arguments string[] "" An optional ordered array of arguments to add to the path. ["id", "name"] equates to "/:id/:name"
policy AuthenticationStrategy undefined Ignored if unpresent, applies a policy or authentication strategy before allowing the router to proceed to the controller when a request is made to the

Integrating with Dependency Injection

To leverage dependency injection, just use the string name of the Controller service instead of the Controller instance itself. The same can be said for the policy.

let standardAuthenticationStrategy = new StandardAuthenticationPolicy();
const routesValues = [
    {
        "methods": ["get"],
        "controller": "controller.main",
        "action": "index"
    },
    {
        "path": "/model",
        "controller": "controller.test",
        "arguments": ["id"],
        "restful": true
    },
    {
        "path": "/hello",
        "arguments": ["name"],
        "methods": ["get"],
        "controller": "controller.main",
        "action": "sayHello"
    },
    {
        "path": "/secure",
        "methods": ["get"],
        "controller": "controller.secured",
        "action": "index",
        "policy": "policy.standard_authentication"
    },
    {
        "arguments": ["name"],
        "methods": ["get"],
        "controller": "controller.main",
        "action": "sayHello"
    },
    {
        "arguments": ["name"],
        "methods": ["post", "put"],
        "controller": "controller.main",
        "action": "postTest"
    }
];
export default routesValues;

A fully-implemented Controller will have the following dependency injection configuration:

import {
    TestRestController,
} from '../../controllers';

export default {
    "controller.test": {
        "class": TestController,
        "constructor": [
            {"type": "service", "key": "router"},
            {"type": "service", "key": "service.test"},
            {"type": "service", "key": "service.formatter"},
            {"type": "service", "key": "logger"},
        ],
        "functions": []
    },
}

Router

The Router will be called in your main server file where you create your Express server and get the routes file. This is typically at the root of your project. Once you have a router, initializing it will set up the routes and assign them to the app and return the app to be started via listen.

Here's an example usage among parts of an express server file:

import express from 'express';
import {Router, strategies} from 'tramway-core-router';
import routes from './routes/routes.js';

const PORT = 8080;

let app = express();
let {ExpressServerStrategy} = strategies;
let router = new Router(routes, new ExpressServerStrategy(app));
app = router.initialize();

Here's an example usage with dependency injection (the entire router configuration would just be a services configuration file):

import {Router, strategies} from 'tramway-core-router';
import {DependencyResolver} from 'tramway-core-dependency-injector';

const {ExpressServerStrategy} = strategies;

export default {
    "router": {
        "class": Router,
        "constructor": [
            {"type": "parameter", "key": "routes"},
            {"type": "service", "key": "express-router-strategy"},
            DependencyResolver,
        ],
    },
    "express-router-strategy": {
        "class": ExpressServerStrategy,
        "constructor": [
            {"type": "parameter", "key": "app"}, //the express app would also be containerized
        ]
    }
}

The router also exposes some static methods which can be used across your app without making another instance.

Function Usage Notes
buildPath(...string): string "a/b/c" === Router.buildPath("a", "b", "c") Returns a clean path given any number of strings.
buildQuery(params: Object): string "a=1&b=2&c=true" === Router.buildQuery({"a": 1, "b": 2, "c": true}) Returns a query string for any associative object

In addition, you can get a specific route by name or by controller and action.

Function Usage Notes
getRoute(name: string): Object Requires adding a name key to each route in the routes config
getRouteByAction(controllerName, actionName): Object A helper is available in the base Controller class so all you need to do is pass the actionName which is the name of the method

Strategies

The biggest addition is strategies which helps keep your apps consistent across clients and servers and also aid in keeping your app framework agnostic by adapting a consistent format across multiple router types which can be plug and play.

All strategies must extend the RouterStrategy class and implement the prepareRoute function (and optionally override the prepareRoutes function).

import {RouterStrategy} from 'tramway-core-router';

export default MyRouterStrategy extends RouterStrategy {
    // Takes a Route or RestfulRoute entity found in {entities}.
    prepareRoute(route) {
        //adapt route to your app's routing
    }
}

ExpressServerStrategy

The strategy that comes with this package is the ExpressServerStrategy which binds the pre-configured routes to the initialized Express app at application start. If you wanted to use React Router on the client side, strategies aid in adapting such that only a piece of the router needs to be replaced.

It takes the following arguments in the constructor:

Argument Default Description
app The instantiated Express app
security Security The Security middleware to apply to routes and handle authentication with. It has a method, generateMiddleware which will return an Express RouteHandler function(req, res, next). By default, the ExpressServerStrategy uses the default Security class but this parameter allows it to be overidden in cases where authentication is handled by a third party.

Note, any previous implementations of an overidden Security parameter will need to move the logic to the generateMiddleware function and pass a valid Security object.