UNPKG

json-routing

Version:

JSON configurable MVC routing for express 4

875 lines (666 loc) 20.8 kB
JSON Routes =================== [![Build Status](https://travis-ci.org/gimox/json-routing.svg?branch=2.0)](https://travis-ci.org/gimox/json-routing) [![npm version](https://badge.fury.io/js/json-routing.svg)](https://badge.fury.io/js/json-routing) [![Coverage Status](https://coveralls.io/repos/github/gimox/json-routing/badge.svg?branch=2.0)](https://coveralls.io/github/gimox/json-routing?branch=2.0) Look at branch [v1.x](https://github.com/gimox/json-routing/tree/1.x) for older release. - Typescript code, more optimized - more speed - remove some unused option - make code more extensible and simple - add automatic JWT route protection with auth0/express-jwt - add route validator with express-validator - prepare it for something more...... MAKE ME THE CODE ------------- #### 1 init module **Typescript** ```javascript import {JsonRoute} from "json-routing"; let routeInfo:Array<any> = new JsonRoute(app, { "routesPath": "./api/routes", "processdir": __dirname }).start(); ``` **JS ES6** ```javascript let jsonRoute = require("json-routing") let routeInfo = new JsonRoute(app, { "routesPath": "./api/routes", "processdir": __dirname }).start(); ``` #### 2 create routes **Create a route file definition** ```json { "/admin": { "GET": { "route": "action", "policy": [ "test:check", "test:all", "./subfolder/test2:index" ] }, "POST": { "route": "action", "policy": [ "test:all", ] } }, "/dashboard": { "GET": { "route": "dashboard", } } } ``` Routig with pure regular expression, add prefix "RE " before uri: ```json { "RE /.*fly$/": { "GET": { "route": "index" } } } ``` WHAT IS IT? ------------- Make routes much easier to use in MVC format. I've been searching for a while for a nodejs routing solution with a: - simple configuration, - super performance, - simple code, - MVC organization, - manage only routing, no other auto magic api creation - customizable - least possible dependency This is: json-routes. How It Works ------------- **The basic concepts.** Create a json file with your routing config and add code logic in a external file called *controller* , creating an MVC style structure. I followed the Expressjs 4 routing standards, helping the developer to manage the routes creation and project organization faster and in the standard synthax. Look at [express routing guide](http://expressjs.com/en/guide/routing.html) for a complet route pattern syntax Proposed Structure ------------- This is an example of the default proposed structure, you can customize and change as you prefer. ``` project root ├── controllers/ ├── IndexController.js ├── AuthController.js ├── UsersController.js ├── policy/ ├── AuthorizationPolicy.js ├── mymiddleware.js ├── routes/ ├── auth.json ├── users.json ├── index.json ├── app.js/ ├── package.json/ ├── README.md/ ``` - **Controller**: contains the code's logic - **Policy**: contains the function called before the controller = middleware - **Routes**: contains all the `*.json` routing configuration. You can create as many *.json files as you need. By default, all routes within a file look up the corresponding controller (= modules = controllers = middleware) inside the controller's default directory `./controller` (or within the directory specified by the user), which uses the following naming format: Route-name (without .json) + "Controller.js (the first letter must be capitalized)". > **EXAMPLE:** > If you have a definition file called `users.json`, by default the route searches the controller `UsersControllers.js`. For routes *auth.json* all routes call the controller `AuthController.js` ecc.. ecc.. > **NOTE:** this is a proposed structure but you can configure the module for your structure, you can change dir structure or add all routes in a single file. ### Creating JSON Configuration file The routing file is encoded in the JSON format and by **default is in `./routes.`** Router is created using this syntax: `{ "RoutePath" : {"verb": {options} } }` *Example of extended config* ```javascript { "routePath": { "VERB": { "route": "controller:method", "policy": [ "controller:method", ] } }, "/admin": { "GET": { "route": "action", "policy": [ "./demo/policycustom/test:check", "test:all", "./subfolder/test2:index" ] }, "POST": { "route": "./mycustomfolder/controllername:index", "policy": [ "./demo/policycustom/test:check", "test:all", "./subfolder/test2:index" ] } }, ... more routes } ``` ### RoutePath This is the routing path and it follows the express4 standard routing. You can use jolly character and other type syntax `/admin*,` `/admin/:name` etc. etc.; ### Verb Relates to the call verb and can assume any valid http verbs like GET, POST, PUT, DELETE etc etc. You can add more verbs for a single routePath: ```javascript { "/admin": { "GET": { "route": "action", "policy": [ "./demo/policycustom/test:check", "test:all", "./subfolder/test2:index" ] }, "POST": { "route": "action", "policy": [ "test:all", ] } } ``` `/admin` has GET and POST verbs. ### Route Relates to `file:method` to call a route address. By default, the routing search controller file inside the default controller directory is: `./controlles`, and you can change it using the global options explained in this document. If the controller is not set, the routing will search a file called with the same name as the json file, with "Controller" as suffix. > **Example:** > If you have a definition file called `users.json`, by default the route searches the controller `UsersControllers.json`. For routes *auth.json* all routes call the controller `AuthController.js` etc.. etc.. **Summarize route params** If you omit the route params, the system routing assumes you have a default route controller path/name and a method called "index". If you add only a parameter, it assumes that the controller is in the default directory with standard name `nameController.js` , and the parameter is the method that should be called. example route: "testall" If the route params contain both values `./path/controllername:method` (user:index) it will search the controller using the default directory path structured as controller name followed by method. For example, route: "./test/user:index" searches for a controller called ./test/user with method index. If you **need to call a controller in a subfolder**, simply add the path before the controller name. Example route: "./afolder/user:index", fire ./controller/afolder/user with method index. If you **need to call a controller starting to your project root** simply add `.` before the path. Example route: "./lib/user:index", fire ./lib/user.js with method index. ### Policy Is a module/function called before the controller (= middleware), by default it calls a file in ./policy named as you set in parameters "fileName" and a function named as you set in "functionName". Example: policy: "auth/index" calls ./policy/auth.js and method index **The syntax is the same as `route` params** It can be a string for a single policy or an array for multiple policy files. ### CORS Enable or disable Cross-origin resource sharing. default is true, look at global options for more info. ### Regex You can set a regex to validate your route, however I discourage using it. Instead, I prefer to add this logic in the controller for better code speed. **To set a rexeg route, use the prefix "RE " before pattern.**. ```javascript { "RE /.*fly$/": { "GET": { "route": "action", "policy": [ "./demo/policycustom/test:check", "test:all", "./subfolder/test2:index" ] } } ``` ### Init Module Configure the routing modules in your main js file, as any other nodes modules. ```javascript var routes = require('json-routing'); new routes(expressApp, options).start(); ``` Example: ```javascript // Includes var express = require('express'); var app = express(); var routes = require('json-routing'); // add module ... // your code.. app.set(...); app.use(...); // this is the magic! new routes(app, { "processdir": __dirname }).start(); ``` **IT'S VERY IMPORTANT TO SET `processdir": __dirname` if your project is in a subfolder of root. (example ./src/)** Change default Options ----------------- When you initialize the module, you can specify a few options to customize the directory structure. All are listed below with the default values. An explanation follows. your main.js file. ```javascript // Includes var express = require('express'); var app = express(); var routes = require('json-routing'); // add module // your code.. app.set(...); app.use(...); //define routes default options var routeOptions = { routesPath : "./api/routes", controllerPath : "./api/controllers", policyPath : "./api/policy", cors : true, displayRoute : true, defaultAction : "index", processDir : process.cwd() } //init routes var routeInfo = new routes(app, routeOptions); ``` - routesPath : the path to your routes folder. `Default ./routes` - controllerPath : the path to your controller folder. `Default ./controllers` - policyPath : the path to your policy folder. `Default ./policy` - cors : enable cross origin resource sharing for all routes. (more cors options coming soon..). `Default false` - displayRoute : display in console loading route info, `default true`. - defaultAction : the function called in route if not specified. It's not so useful, but it's here!.`Default index` - processDir : The root base path of the project, default `process.cwd()` set as `__dirname` if you need to start in a subfolder or complex project. If you omit routeOptions or some params it use defaults values. Change json file Global Options ----------------- If you need to change options for all routes only for a specific *.json file, you can set in your file the key `GLOBAL` as in the following example: user.json ```javascript { "GLOBAL": { "controller": "./customdir/customControllerName", "policy":["config:setall","config:connection"], "baseUrl":"/user" }, "/create": { "PUT": { "route": "index", "policy": [ "auth:check", "auth:adduserparams" ] } } } ``` Example: route controller is ./customdir/UserController.js - controller: set a custom controller path for all routing file - policy: is an array of policy `file:action` to fire before controller - baseUrl: is a base path for all url routes in file. Example, inside a file all routes start with `/api/*`, i can set base url as `/api`. Now all file routes start with `/api`. If i have a routes `/users`, it fired when user called `/api/users` > **NOTE:** the key "GLOBAL" must be uppercase. Full extended example ----------------- *app.js* ```javascript var express = require('express') , app = express() , port = process.env.PORT || 3000 , routing = require('./lib/route'); /** * global options for routing * * set all file inside /api/* for a more cleaner code */ var routeOptions = { routesPath: "./api/routes" , controllersPath: "./api/controllers" , policyPath: './api/policy' , cors: false , processDir: __dirname }; /** * init json-routing */ new routing(app, routeOptions); /** * standard express 4 routing * yes.. you can use both routing together if you need */ var router = express.Router(); router.get('/express/', function (req, res) { res.send(' this is a standard routing '); }); app.use('/', router); /** * server start * * @type {http.Server} */ var server = app.listen(port, function () { console.log('Listening on port %d', server.address().port); }); ``` This is the main file, we set routing and add global setting to use ./api as root directory *./api/routes/users.json* ```javascript { "/banned": { "GET": { "route": "./lib/bannedCustom:index", } }, "/user": { "GET": { "route": "find", "policy": [ "auth:check", "auth:adduserparams" ] }, "PUT": { "route": "create", "policy": [ "auth:check", ] } } } ``` define the routes *./api/controllers/UsersController.js* ```javascript exports.index = function(req,res,next) { res.send(' index routes '); }; exports.create = function(req,res,next) { res.send(' create routes params:'+req.params.name); }; ``` a basic controller logic *./api/controllers/bannedCustom.js* ```javascript exports.getbanned = function(req,res,next) { res.send(' custom controller name '); }; ``` this is the controller with custom name *./api/policy/auth.js* ```javascript exports.check = function(req,res,next) { if (!req.session.isLogged){ return res.redirect('http://'+req.hostname+":3000/403"); } next(); }; ``` Let me explain this policy: it checks if a user is logged, else set a redirect, so we can use the middleware to check ACL, authorization or get/set global vars, and this is very useful. Create a Policy File and Pass vars to controller ----------------- We encourage to use standard tecnique for best performance: use middleware. using the full example described below we can create a standard policy file to attach a global var using `req` *./api/policy/auth.js* ```javascript exports.getbanned = function(req,res,next) { if (!req.session.isLogged){ return res.redirect('http://'+req.hostname+":3000/403"); } //use req req.session.lastPing = new Date(); next(); }; ``` **Read the value in the controller or policy** *./api/controllers/bannedCustom.js* ```javascript exports.getbanned = function(req,res,next) { res.send(' custom controller name, middleware loaded at: '+req.session.lastPing); }; ``` Case: using middleware ----------------- A special case: if we want to add an authentication before some route, take a look at this example: ```javascript { "/admin*": { "GET": { "route": "./policy/auth:check", }, "POST": { "route": "auth:check", }, "PUT": { "route": "auth:check", }, "DELETE": { "route": "auth:check", }, }, "/admin/dashboard": { "GET": { "route": "getItem", } }, "/admin/user": { "GET": { "route": "find", }, "PUT": { "route": "create", } } }} ``` All `admin*` route calls the controller `auth`, so now `auth:check` is executed before all `admin*` controller and it becomes a policy (=middleware) and for a clear structure i put the file in policy dir. An alternative example use the global file option: ```javascript { "GLOBAL": { "policy":["auth:check"], "baseUrl":"/admin" }, "/dashboard": { "GET": { "route": "getItem", } }, "/user": { "GET": { "route": "find", }, "PUT": { "route": "create", } } }} ``` Protected route with JWT ----------------- You can protect a routes using [jwt](https://jwt.io/). Json-routing use [auth0/express-jwt](https://github.com/auth0/express-jwt). To protect a route add a property `jwt:true` and set the global options for jwt as example. **Before using jwt you need to install express-jwt manually: `npm install --save express-jwt`** *Route file: protected.json* ``` { "/protected": { "GET": { "route": "index", "jwt": true } }, "/notprotected": { "GET": { "route": "indexnot", "jwt": false } } } ``` NB note to pretect a route we need to set `jwt:true` In main file: server.ts/js ``` ... export const routeInfo: Array<IRouteInfo> = new JsonRoute(app, { "processdir": __dirname, "jwt": { "secret": "12345678910abc" } }).start(); ... ``` NB in json-routing init we need to set jwt object with secret DONE!!!!! ### jwt extra route for error to make a better jwt unauthorized response we can add a specific route like this: ``` export const routeInfo: Array<IRouteInfo> = new JsonRoute(app, { "processdir": __dirname, "jwt": { "secret": "12345678910abc" } }).start(); app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { if (err.name === 'UnauthorizedError') { res.status(401).json({"message": "invalid token..."}); } next(); }); ``` Add a global route prefix ----------------- You can add a global prefix path for all routes set options.urlPrefix ``` export const routeInfo: Array<IRouteInfo> = new JsonRoute(app, { "processdir": __dirname, "urlPrefix":"/api/v1" }).start(); ``` All routes now start with `/api/vi` Route validation params ----------------- It can be done by express-validator using schema. Add `validators` object with: - params -> route params es /home/:id - query -> query params es -> /home?id=124 - boby -> body params es post params like {"id":"1233"} according [express-validator](https://github.com/ctavan/express-validator) "validation by Schema" route file.json ``` { "/validateparam/:id": { "GET": { "route": "validateparam", "jwt": false, "validators": { "params": { "id": { "notEmpty": true, "isLength": { "options": [ { "min": 5, "max": 10 } ], "errorMessage": "Must be between 2 and 10 chars long" }, "errorMessage": "id is required" } } } } } } ``` **NB body-parser middleware is injected by json-routing, you can pass params in global params** ALL OPTIONS ----------------- ```javascript export interface IOptions { routesPath?: string , controllersPath?: string , policyPath?: string , processdir?: string , cors?: boolean , displayRoute?: boolean , defaultAction?: string , urlPrefix?: string , jwt?: { secret: any } , bodyParserUrlEncoded?: any } ... let options: IOptions; ... // add params to optins object let routeInfo:Array<any> = new JsonRoute(app, options}).start(); ``` Example ----------------- Look at ./demo for a fully working example. Changelog 2.0.5 ------------- - better cors support - can be enable/disable cors for single routes - better console output Changelog 2.0.4 ------------- - add validator Changelog 2.0.1 ------------- - add protected route with JWT Changelog 2.0.0 ------------- - add urlPrefix for all routes Changelog 2.0.0Rc1 ------------- - minor fix - route with regular expression Changelog 2.0.0b1 ------------- - completely rewrite in typecript (build transpiled in es6) - remove global policy options - global now has "controller" option - make more simple - regex now work good! - more speed - add test and coverage Changelog 0.1.5 ------------- - Preparing for typescript... es6 and node > 6.4 rewrite, removed underscore. No esternal modules deps!! Changelog 0.1.0 ------------- - minor fix Changelog 0.1.0 ------------- - removed not working cors features for file definition and route... working on it.. cors for global setting work good. Changelog 0.0.27 ------------- - improve log info Changelog 0.0.26 ------------- - add `defaultAction`, not so useful, but it's here!. - start cleaning code - add `CORS` Global file options, to enable CORS only in specific *.json routes - add `CORS` for specific routes. - route log info display CORS status Changelog 0.0.25 ------------- - improve route info on load, it can disabled with global options "displayRoute:false" Changelog 0.0.24 ------------- - initial CORS support (look at "Change default Options"), more CORS options coming soon... Changelog 0.23 ------------- - fix url union for windows platform Changelog 0.0.20 ------------- - fix policy string is not added if global policy is set - working test Changelog 0.0.19 ------------- - add Global base Url Changelog 0.0.17 ------------- - fix default route - add mre error check Changelog 0.0.15 ------------- - add goblal file policy (=middleware) Changelog from version 0.0.13 ------------- - No longer compatible with <0.13 version - new json syntax