json-routing
Version:
JSON configurable MVC routing for express 4
875 lines (666 loc) • 20.8 kB
Markdown
JSON Routes
===================
[](https://travis-ci.org/gimox/json-routing)
[](https://badge.fury.io/js/json-routing)
[](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