decorator-module

0.1.6 • Public • Published

Decorator pattern

This library implement 3 Symfony features for node without Babel:

  • Annotation in controller method. For example: @Route('/info', 'GET') create a route /info with GET verb. When you are calling the route, the method annotated is executed automatically.

  • All controller's methods receives request object in parameters and Must be return a Response object.

  • It is possible to add custom annotation. They could replace the express middleware for the annoted route

Usage

implementation

On server.js

const decorator = require('decorator-module');  
  
//launch analyse of the controller directory  
decorator.launch(__dirname + '/src/');  
// add manual route (optional)  
decorator.router.add('get', '/test', 'Main', 'test');  
  
//inject the router inside your app  
app.use(CONF.APIURL, decorator.router.router);  
  

on src/ directory

The controllers must be named with a specific syntax:

src/

----MainController.js

----AuthController.js

All methods must be wrote with 'Action' suffix (getCustmerAction...)

All methods must be returned a new Response object

All methods must be static


Response signature:

constructor(success: boolean, code: number = 200, result: object = {})  

example (ES6):

const Response = require('decorator-module').response;  
  
/**  
 * Main controller class 
 */
class MainController { 
	/** 
	* route annotation 
	* @Route('/info', 'GET') 
	* 
	* @param req 
	* @returns {Response} 
	*/ 
	static mainAction(req) {  
		 return new Response(true, 200, { message: 'api ok' });  
	} 
	
	/**  
	 * Route added manually on main.js
	 * @param req 
	 * @returns {Response} 
	 */
	 static testAction(req) {  
		 return new Response(true, 200, { message: 'test action' });  
	}
}  
  
module.exports = MainController;  
  

example (ES5):

const Response = require('decorator-module').response;  
  
module.exports = { 
	/** 
	 * @Route('/init', 'POST') 
	 * 
	 * @param req 
	 */  
	 mainAction: function(req) { 
		 return new Response(true, 200, {action : 'init'}) 				
	 }
};  
  

Query and Request param validator (v0.1.1)

@QueryParam(paramName: string, regex: string, required: string = true)

This annotation test if the sent query string respect the provided regex.

param type require desc default
paramName string true name of req.query property
regex string true A match regex
required string false 'true' or 'false' 'true'

Example:

 /** 
 * @Route('/customer', 'GET') 
 * @QueryParam('id', '^([0-9a-f]*)$', 'true') 
 * @param req 
 */  
 static getInfoAction(req) {  
	 return new Response(true, 200, { result: {  
		  lastname: 'Dupont',  
		  firstname: 'Thomas'  
	 }}); 
 }  

If the req.query.id doesn't respect the ^([0-9a-f]*)$ regex, it answers:

Status code 400 Bad Request, response:

{"error":"param id doesn't match requirement"}  

If the req.query.id is not provided:

Status code 400 Bad Request, response:

{"error":"param id missing"}  

@BodyParam(paramName: string, regex: string, required: boolean = true)

Same as QueryParam decorator for req.body object

Custom annotation

It is possible to create custom decorators

decorator directory

You have to create a new decorator prototype / class
On the server.js, it mandatory to add the new decorator folder:

  
const decorator = require('decorator-module');  
  
//new decorator folder  
decorator.addDecorators(__dirname + '/decorator/');  
//launch analyse of the controller directory  
decorator.launch(__dirname + '/src/');  
//inject the router inside your app  
app.use(CONF.APIURL, decorator.router.router);  
  

The syntax of the file name must be have a "Decorator" suffix. For example: CheckInCallDecorator.js

The class must implement execute method and inBuild property.


  1. example of annotation call when the route affected to action is call:
const Response = require('decorator-module').response;  
  
/**  
 * * @Method execute(controller, method, request, ...params): Response */
class CheckInCall { 
	static execute(controller, method, req, secret) {  
		 if (secret !== req.header('secret')) {  
			 return new Response(false, 403, { error: 'access_denied' });  
		 }  
		 return new Response(true);  
	 }
}  
  
/*  
* Mandatory  
* false: call the decorator only on api call  
* true: call the decorator only on npm start  
*/  
CheckInCall.inBuild = false;  
module.exports = CheckInCall;  

controller implementation:

/**  
 * @Route('/info', 'POST') 
 * @CheckInCall('astrongsecret') 
 * 
 * @param req 
 * @returns {Response} 
 */  
 static mainAction(req) {  
	 return new Response(true, 200, { message: 'api ok' });  
 }

  1. example of annotation called when the application is starting:
/**  
 * @type inBuild Decorator 
 * @Method execute(controller, method, ...params): Void 
 */
 class Test { 
	 static execute(controller, method, param1) {  
		 console.log(`call test decorator on ${controller}Controller , method ${method} with param ${param1}`);  
	 }
}  
  
Test.inBuild = true;
module.exports = Test;  
  

controller implementation:

/**  
 * @Route('/info', 'POST') 
 * @Test('hello world') 
 * 
 * @param req 
 * @returns {Response} 
 */  
 static mainAction(req) {  
	 return new Response(true, 200, { message: 'api ok' });  
 }

The decorator could take any number of params

Params binding on controller methods (v0.1.5)

It's possible to bind method params with a decorator. To bind a new parameter, the Response instance returned by the decorator must have an typescript additionalCallParams : array<any> property. It's possible to bind unlimited number of new params

Here, we have an example with user fetching.

The decorator:

const Response = require('decorator-module').response;  
const User = require('../model/user');

class FetchUser { 
	static async execute(controller, method, req) {  
		const user = await User.findOne({ id : req.body.user_id });

		return new Response(true, null, {
            additionalCallParams: [user],
        });  
	 }
}  
   
FetchUser.inBuild = false;  
module.exports = FetchUser;  

Inside the controller:

 /** 
 * @Route('/customer', 'GET')  
 * @FetchUser()
 * @param user User
 * @param req 
 */  
 static getInfoAction(req, user) {  
	 return new Response(true, 200, { result: user }); 
 }  

Response listener

v0.1.3 added a response listener, onDecoratorResponse and onControllerResponse.

This listener uses the Decorator response and Controller response, the callback has two parameters.

The response from Decorator or Controller and the express res object.

It's a nice feature to standardize the API returns.

On server.js

const decorator = require('decorator-module');  
  
//launch analyse of the controller directory  
decorator.launch(__dirname + '/src/');  
//The two listeners 
decorator.responseListener.onDecoratorResponse((response, res) => res.status(response.code).send({  
  error: true,  
  message : response.result.error  
}));  
  
decorator.responseListener.onControllerResponse((response, res) => res.status(response.code).send({  
  error: false,  
  customResult: response.result  
}));  
  
// add manual route (optional)  
decorator.router.add('get', '/test', 'Main', 'test');  
  
//inject the router inside your app  
app.use(CONF.APIURL, decorator.router.router);  
  

Application example

https://gitlab.com/ThomasDupont/empty_node_api.git

Author

Thomas Dupont https://gitlab.com/ThomasDupont

Package Sidebar

Install

npm i decorator-module

Weekly Downloads

3

Version

0.1.6

License

MIT

Unpacked Size

27.3 kB

Total Files

26

Last publish

Collaborators

  • thomasdupont