UNPKG

40 kBMarkdownView Raw
1# apiextender
2This module allows to extend an API backend, by adding endpoints or modifying existing endpoints' behaviour.
3 This is accomplished in a plug-in fashion.
4With apiextender you can :
5+ enable third-party developers to create functionalities extending an application
6+ support the addition of new features in a simple way
7
8[![NPM](https://nodei.co/npm/apiextender.png?downloads=true&downloadRank=true&stars=true)![NPM](https://nodei.co/npm-dl/apiextender.png?months=6&height=3)](https://nodei.co/npm/apiextender/)
9
10* [Installation](#installation)
11* [Using apiextender](#using)
12* [make your environment capable to accept plugins functions](#folder)
13* [How to write function that extends the "API"](#howto)
14* [Reference](#reference)
15 * [extend(app)](#extend)
16 * [install(app,extender,save)](#install)
17* [Examples](#examples)
18 * [Examples:How to set an API extensible by plugins](#examplesapi)
19 * [Examples:How to write extensible plugin functions](#examplesplugin)
20 * [Examples:override mode](#exampleoverride)
21 * [Examples:before mode](#examplebefore)
22 * [Examples:after mode](#exampleafter)
23 * [Examples:before_after mode](#examplebefore_after)
24 * [Examples:extend throwing error](#exampleerror)
25 * [Examples:A Complete Example of plugin extension](#examplecomplete)
26 * [Example: Extend API on the fly at runtime](#exampleonFly)
27
28
29## <a name="installation"></a>Installation
30Install **apiextender** in your project by typing:
31
32```shell
33$ npm install apiextender
34```
35
36
37## <a name="using"></a>Usage
38This is an Express module for nodeJs. This section explains how to use it in your code to make your API extensible by plugin.
39
40### Include apiextender
41Just require it:
42```javascript
43var apiextender = require('apiextender');
44```
45
46### Using apiextender
47
48```javascript
49var express=require('express');
50var apiextender = require('apiextender');
51var app=express();
52
53// make your API extensible by plugin techniques in a simple and fast
54// and with one line of code.
55apiextender.extend(app); // now your API is exensible
56```
57
58## <a name="folder"></a>Make your environment compliant to plugins
59Plugin functions that extends your API must be defined in a file called ```extend.js```,
60located in a folder named ```plugin``` in the home directory of your application.
61
62### Create plugin folder
63To create folder type:
64```shell
65$ mkdir plugin
66```
67### Create plugin file container
68To create ```extend.js``` type:
69```shell
70$ cd plugin
71$ vi extend.js
72```
73
74```extend.js``` contains an array of plugin functions. Now we initialize it as an empty plugin function container:
75```javascript
76var plugins=[];
77module.exports = plugins;
78```
79
80
81## <a name="howto"></a>Write your functions that extend the API
82To extend an API endpoint, you must populate the ```plugins``` array containing all functions extending the API.
83
84### extend.js structure
85```javascript
86var plugins=[];
87module.exports = plugins;
88```
89**`plugins=[]`** is an array of objects , which are functions that constitutes a plugin. Each function is defined as:
90```javascript
91{
92 "resource":"/resourceToExtend",
93 "method":"GET",
94 "mode":"override",
95 "params":[query],
96 "extender": function(req,content,cType,callback){
97 callback(null,{"Content extension"});
98 }
99}
100```
101
102 * URI: URI of the resource to protect
103 * method: HTTP method used to call the resource to protect
104 * authToken : An array of Strings containing the token types authorized by this role
105
106where:
107* **resource**: URI of the resource to extend
108* **method** : HTTP method of the resource to extend
109* **mode** : Mode defining how the resource must be extended. There are four
110 different possible modes: ```override```, ```before```, ```after```, ```before_after```. In detail:
111 * **override** : overrides the original endpoint definition
112 * **before** : the plugin function is executed before the original endpoint
113 * **after** : the plugin function is executed after the original endpoint
114 * **before_after**: the plugin function is executed both before and after the original endpoint
115
116* **params** : Array of the fields of Express ```req``` object that must be passed to the plugin function in ```reqParams``` param
117* **enabled** : Optional boolean parameter indicating if the plugin is enabled or not. Default is disabled
118* **extender**: Plugin function definition. This function is invoked end executed by apiextender, defined as:
119 ```function(reqParams,content,contentType,callback)```
120 * **reqParams** : Object containing the Express request params defined in ```params``` field.
121 E.g. if ```params=["query"]```, ```reqParams``` contains the object ```{query:req.query}```
122 * **content** : Response of the original endpoint when ```mode``` param is set to ```after``` or ```before_after```.
123 Null when mode is set to ```override``` or ```before```
124 * **contentType** : Content Type of ```content```, e.g. ```application/json``` , ```text/html```...
125 Null when mode is set to ```override``` or ```before```
126 * **callback** : callback function to apiextender. It must be invoked with params ```error``` and ```newContent```, defined as:
127 ```callback(error,newContent)```
128 * **error** : If an error occurs, apiextender stops the request execution and sends a response to the client
129 with this object, that must have two fields:
130 * **error_code** : HTTP status code
131 * **error_message** : error message content
132 * **newContent** : response sent to the client if mode is ```override```, ```after``` or ```before_after```
133 (for the latter, only for what is executed in the ```after``` part). <br>
134 If mode is set to ```before``` or ```before_after```
135 (for the latter, only for what is executed in the ```before``` part), it is an
136 object whose keys are appended to Express' ```req``` .
137
138####<a name="nb"></a> Warning:
139If `mode` is set to `before_after`, it is mandatory to declare in `extender` param two functions,
140 both for the `before` and `after` actions.
141 So the `extender` parameter is not a function but an object containing two keys:
142* **before**: the function definition for `before` actions
143* **after** : the function definition for `after` actions
144
145######Example
146```javascript
147{
148 //....
149 //....
150 "mode":"before_after"
151 "extender":{
152 "before": function(req,content,cType,callback){
153 // before logic
154 callback(null,{"Content extension before"});
155 },
156 "after": function(req,content,cType,callback){
157 // after logic
158 callback(null,{"Content extension after"});
159 }
160 }
161}
162```
163
164######Example of extend.js plugin definition
165```javascript
166var plugins=[
167 {
168 "resource":"/resourceToExtend",
169 "method":"GET",
170 "mode":"override",
171 "params":[query],
172 "extender": function(req,content,cType,callback){
173 callback(null,{"Content extension"});
174 }
175 },
176 {
177 "resource":"/OtherResourceToExtend",
178 "method":"GET",
179 "mode":"before",
180 "params":[query],
181 "extender": function(req,content,cType,callback){
182 callback(null,{"Content extension"});
183 }
184 }
185]
186```
187
188
189## <a name="reference"></a>Reference
190### <a name="extend"></a>`extend(app)`
191Function that extends the API. The param `app` is the application that you want extend.
192
193### <a name="install"></a>`install(app,extender,save)`
194Function that extends an API at runtime, without stopping and restarting your application.
195* `app` is the application that you want extend
196* `extender` is the plugin function described in section [plugin extender structure](#functionextension)
197* `save` if true, the plugin is saved in the `extend.js` file, being appended in the `plugin` array and becoming permanent.
198
199######Example:
200```javascript
201var express=require('express');
202var apiextender = require('apiextender');
203var app=express();
204
205apiextender.extend(app); // now your API is extensible
206
207// Define an endpoint wrapping apiextender install function that lets you to extend API on the fly
208// The access to this endpoint should be protectd with token privileges
209app.post("/installPlugin",function(req,res,next){
210 // check for tokens
211 //.....
212
213 // install and run the plugin
214 apiextender.install(app,req.body.pluginExtender,req.body.save || false);
215 res.send({"status":"installed"});
216});
217```
218
219
220## <a name="examples"></a>`Examples`
221### <a name="examplesapi"></a>`Examples: How to set an API extensible by plugins`
222From your application home directory type:
223```shell
224$ cd /Your_App_Home
225$ npm install apiextender // install apiextender
226$ mkdir plugin // Create plugin container folder
227$ cd plugin // go into plugin folder
228$ vi extend.js // Create plugin container file
229```
230
231
232Insert this content in `extend.js`.
233```javascript
234var plugins=[];
235module.exports = plugins;
236```
237
238Include the apiextender module in `app.js`
239```javascript
240var express=require('express');
241var apiextender = require('apiextender');
242var app=express();
243
244apiextender.extend(app);
245```
246
247### <a name="examplesplugin"></a>`Examples: How to extend an API`
248
249Suppose we want to extend this API:
250```javascript
251var express=require('express');
252var apiextender = require('apiextender');
253var app=express();
254
255apiextender.extend(app); // now your API is exensible
256
257app.get('/overrideOriginal', function(req, res){
258 res.send({"response":"override"});
259});
260app.get('/beforeOriginal', function(req, res){
261 var params=req.optionalParams || null;
262 var response = params!=null ? {"response":"before", "response_before":params} : {"response":"before"}
263 res.send(response);
264});
265app.get('/afterOriginal', function(req, res){
266 res.send({"response":"after"});
267});
268app.get('/before_afterOriginal', function(req, res){
269var params=req.optionalParams || null;
270 var response = params!=null ? {"response":"before_after", "response_before":params} : {"response":"before_after"}
271 res.send(response);
272});
273```
274
275#### <a name="exampleoverride"></a>`Override mode`
276With no plugin function defined, the endpoint response is:
277```shell
278 $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/overrideOriginal
279 X-Powered-By: Express
280 Content-Type: application/json; charset=utf-8
281 Content-Length: 23
282 ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
283 Date: Fri, 11 Nov 2016 13:16:44 GMT
284 Connection: keep-alive
285
286 {"response":"override"} // original response because no extension function
287 $
288```
289
290Now we extend "/overrideOriginal" endpoint with a plugin, writing a plugin function in `extend.js`
291```javascript
292var plugins=[
293 {
294 "resource":"/overrideOriginal", // extend overrideOriginal endpoint
295 "method":"GET", // extend overrideOriginal endpoint in get method
296 "mode":"override", // endpoint overrideOriginal must be overrided
297 "params":[query], // express request "req.query" should be throw to extender function
298 "extender": function(requestParams,content,cType,callback){ // this is the extender Function definition
299 // the mode is set to "override" so "content,cType" are both null
300 var username=requestParams.username || ""; // if username exist is set otherwise use a void string
301 callback(null,{"response":"Hello " + username + " this is a plugin function that override"});
302 // ^
303 // |
304 // No Error
305 }
306 }
307];
308module.exports = plugins;
309```
310
311Restart your application (or invoke `apiextender.install`) to apply the plugin function.
312If you curl the endpoint, you can notice that the original endpoint is not executed, being overridden by the plugin function:
313```shell
314 $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/overrideOriginal
315 X-Powered-By: Express
316 Content-Type: application/json; charset=utf-8
317 Content-Length: 60
318 ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
319 Date: Fri, 11 Nov 2016 13:16:44 GMT
320 Connection: keep-alive
321
322 {"response":"Hello this is a plugin function that ovveride"} // original response "{"response":"before"}"
323 // overrided by extension function
324 $
325```
326
327
328#### <a name="examplebefore"></a>`Before mode`
329With no plugin function defined, the endpoint response is:
330```shell
331 $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/beforeOriginal
332 X-Powered-By: Express
333 Content-Type: application/json; charset=utf-8
334 Content-Length: 51
335 ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
336 Date: Fri, 11 Nov 2016 13:16:44 GMT
337 Connection: keep-alive
338
339 {"response":"before"} // original response because no extension function
340```
341
342Now we extend "/beforeOriginal" endpoint with a plugin, writing a plugin function in `extend.js`
343```javascript
344var plugins=[
345 {
346 "resource":"/beforeOriginal", // extend beforeOriginal endpoint
347 "method":"GET", // extend beforeOriginal endpoint in get method
348 "mode":"before", // plugin function must be executed before original endpoint beforeOriginal
349 "params":[query], // express request "req.query" should be throw to extender function
350 "extender": function(requestParams,content,cType,callback){ // this is the extender Function definition
351 // the mode is set to "before" so "content,cType" are both null
352 var username=requestParams.username || ""; // if username exist is set otherwise use a void string
353 callback(null,{"optionalParams":"Hello " + username + " this is a plugin function that extend"});
354 // ^
355 // |
356 // No Error
357 }
358 }
359];
360module.exports = plugins;
361```
362
363Restart your application (or invoke `apiextender.install`) to apply the plugin function.
364If you curl the endpoint, you can notice that the original endpoint is extended by the plugin function:
365
366```shell
367 $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/beforeOriginal?username=Alex
368 X-Powered-By: Express
369 Content-Type: application/json; charset=utf-8
370 Content-Length: 90
371 ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
372 Date: Fri, 11 Nov 2016 13:16:44 GMT
373 Connection: keep-alive
374
375 {"response":"before","response_before":"Hello Alex this is a plugin function that extend"} // original and extended response
376```
377
378
379#### <a name="exampleafter"></a>`After mode`
380With no plugin function defined, the endpoint response is:
381```shell
382 $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/afterOriginal
383 X-Powered-By: Express
384 Content-Type: application/json; charset=utf-8
385 Content-Length: 20
386 ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
387 Date: Fri, 11 Nov 2016 13:16:44 GMT
388 Connection: keep-alive
389
390 {"response":"after"} // original response because no extension function
391$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/afterOriginal?username=Alex
392X-Powered-By: Express
393Content-Type: application/json; charset=utf-8
394Content-Length: 20
395ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
396Date: Fri, 11 Nov 2016 13:16:44 GMT
397Connection: keep-alive
398
399{"response":"after"} // original response because no extension function
400```
401
402Now we extend "/afterOriginal" endpoint with a plugin, writing a plugin function in `extend.js`
403```javascript
404var plugins=[
405 {
406 "resource":"/afterOriginal", // extend afterOriginal endpoint
407 "method":"GET", // extend afterOriginal endpoint in get method
408 "mode":"after", // plugin function must be executed after original endpoint afterOriginal
409 "params":[query], // express request "req.query" should be throw to extender function
410 "extender": function(requestParams,content,cType,callback){ // this is the extender Function definition
411 // the mode is set to "after" so "content,cType" are both not null
412 if(cType==="application/json"){ // if content is a Json
413 var username=requestParams.username || ""; // if username exist is set otherwise use a void string
414 content.after_response="Hello " + username + " this is a plugin function that extend";
415 }
416 callback(null,content);
417 // ^
418 // |
419 // No Error
420 }
421 }
422];
423module.exports = plugins;
424```
425
426Restart your application (or invoke `apiextender.install`) to apply the plugin function.
427If you curl the endpoint, you can notice that the original endpoint is extended by the plugin function:
428
429```shell
430 $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/afterOriginal
431 X-Powered-By: Express
432 Content-Type: application/json; charset=utf-8
433 Content-Length: 82
434 ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
435 Date: Fri, 11 Nov 2016 13:16:44 GMT
436 Connection: keep-alive
437
438 {"response":"after","after_response":Hello this is a plugin function that extend"} // original response extended
439$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/afterOriginal?username=Alex
440X-Powered-By: Express
441Content-Type: application/json; charset=utf-8
442Content-Length: 87
443ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
444Date: Fri, 11 Nov 2016 13:16:44 GMT
445Connection: keep-alive
446
447 {"response":"after","after_response":Hello Alex this is a plugin function that extend"} // original response extended
448```
449
450
451#### <a name="examplebefore_after"></a>`Examples: before_after mode`
452With no plugin function defined, the endpoint response is:
453```shell
454 $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/before_afterOriginal
455 X-Powered-By: Express
456 Content-Type: application/json; charset=utf-8
457 Content-Length: 27
458 ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
459 Date: Fri, 11 Nov 2016 13:16:44 GMT
460 Connection: keep-alive
461
462 {"response":"before_after"} // original response because no extension function
463$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/before_afterOriginal?username=Alex
464X-Powered-By: Express
465Content-Type: application/json; charset=utf-8
466Content-Length: 27
467ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
468 Date: Fri, 11 Nov 2016 13:16:44 GMT
469Connection: keep-alive
470
471{"response":"before_after"} // original response because no extension function
472```
473
474Now we extend "/before_afterOriginal" endpoint with a plugin, writing a plugin function in `extend.js`
475```javascript
476var plugins=[
477 {
478 "resource":"/before_afterOriginal", // extend before_afterOriginal endpoint
479 "method":"GET", // extend before_afterOriginal endpoint in get method
480 "mode":"before_after", // plugin function must be executed before and after original endpoint before_afterOriginal
481 "params":[query], // express request "req.query" should be throw to extender function
482 "extender":{
483 "before": function(requestParams,content,cType,callback){ // this is the extender Function definition
484 // the mode is set to "before_after" so in before function "content,cType" are both null
485 var username=requestParams.username || ""; // if username exist is set otherwise use a void string
486 callback(null,{"optionalParams":"Hello " + username + " this is a plugin function that extend"});
487 // ^
488 // |
489 // No Error
490 },
491 "after": function(requestParams,content,cType,callback){ // this is the extender Function definition
492 // the mode is set to "before _after" so in after function "content,cType" are both not null
493 if(cType==="application/json"){ // if content is a Json
494 var username=requestParams.username || ""; // if username exist is set otherwise use a void string
495 content.after_response="Hello " + username + " this is a plugin function that extend";
496 }
497 callback(null,content);
498 // ^
499 // |
500 // No Error
501 }
502 }
503];
504module.exports = plugins;
505```
506
507Restart your application (or invoke `apiextender.install`) to apply the plugin function.
508If you curl the endpoint, you can notice that the original endpoint is extended by the plugin function:
509```shell
510$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/before_afterOriginal
511X-Powered-By: Express
512Content-Type: application/json; charset=utf-8
513Content-Length: 154
514ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
515Date: Fri, 11 Nov 2016 13:16:44 GMT
516Connection: keep-alive
517
518 {
519 "response":"before_after",
520 "response_before":"Hello this is a plugin function that extend", // original and extended response
521 "response_after":"Hello this is a plugin function that extend"
522 }
523
524$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/before_afterOriginal?username=Alex
525X-Powered-By: Express
526Content-Type: application/json; charset=utf-8
527Content-Length: 164
528ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
529Date: Fri, 11 Nov 2016 13:16:44 GMT
530Connection: keep-alive
531
532 {
533 "response":"before_after",
534 "response_before":"Hello Alex this is a plugin function that extend", // original and extended response
535 "response_after":"Hello Alex this is a plugin function that extend"
536 }
537```
538
539
540#### <a name="exampleerror"></a>`Error throwing`
541With no plugin function defined, the endpoint response is:
542```shell
543 $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/beforeOriginal
544 X-Powered-By: Express
545 Content-Type: application/json; charset=utf-8
546 Content-Length: 51
547 ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
548 Date: Fri, 11 Nov 2016 13:16:44 GMT
549 Connection: keep-alive
550
551 {"response":"before"} // original response because no extension function
552 $ // now with username param (no response changes)
553 $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/beforeOriginal?username=Alex
554 X-Powered-By: Express
555 Content-Type: application/json; charset=utf-8
556 Content-Length: 51
557 ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
558 Date: Fri, 11 Nov 2016 13:16:44 GMT
559 Connection: keep-alive
560
561 {"response":"before"} // original response because no extension function
562```
563Now we extend "/beforeOriginal" endpoint with a plugin, writing a plugin function in `extend.js`. Starting from the
564example [`Examples:before mode`](#examplebefore), we edit the plugin function to throw an error if no `username` field is sent.
565```javascript
566var plugins=[
567 {
568 "resource":"/beforeOriginal", // extend beforeOriginal endpoint
569 "method":"GET", // extend beforeOriginal endpoint in get method
570 "mode":"before", // endpoint beforeOriginal must be overrided
571 "params":[query], // express request "req.query" should be throw to extender function
572 "extender": function(requestParams,content,cType,callback){ // this is the extender Function definition
573 // the mode is set to "override" so "content,cType" are both null
574 var username=requestParams.username || null; // if username exist is set otherwise use a void string
575 if(!username)
576 callback({"error_code":"400", "error_message":"no username field"},null);
577 else
578 callback(null,{"response":"Hello " + username + " this is a plugin function that override"});
579 // ^
580 // |
581 // No Error
582 }
583 }
584];
585module.exports = plugins;
586```
587
588Restart your application (or invoke `apiextender.install`) to apply the plugin function.
589If you curl the endpoint, you can notice that the original endpoint is not executed after the plugin function,
590because no username field is sent. This is due to the fact that `before extension` function stops the execution
591with an error message to the apiextender callback:
592
593```shell
594 $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/beforeOriginal
595 X-Powered-By: Express
596 Content-Type: application/json; charset=utf-8
597 Content-Length: 37
598 ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
599 Date: Fri, 11 Nov 2016 13:16:44 GMT
600 Connection: keep-alive
601
602 {"error_message":"no username field"} // only error_message from extended function response
603 $
604 $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/beforeOriginal?username=Alex
605 X-Powered-By: Express
606 Content-Type: application/json; charset=utf-8
607 Content-Length: 90
608 ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
609 Date: Fri, 11 Nov 2016 13:16:44 GMT
610 Connection: keep-alive
611
612 {"response":"before","response_before":"Hello Alex this is a plugin function that extend"} // original and extended response
613```
614
615
616#### <a name="examplecomplete"></a>`Examples: A Complete Eexample of plugin extension`
617With no plugin function defined, the endpoint response is:
618```shell
619 $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/overrideOriginal?username=Alex
620 X-Powered-By: Express
621 Content-Type: application/json; charset=utf-8
622 Content-Length: 23
623 ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
624 Date: Fri, 11 Nov 2016 13:16:44 GMT
625 Connection: keep-alive
626
627 {"response":"override"} // original response because no extension function
628$
629$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/beforeOriginal?username=Alex
630X-Powered-By: Express
631Content-Type: application/json; charset=utf-8
632Content-Length: 21
633ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
634Date: Fri, 11 Nov 2016 13:16:44 GMT
635Connection: keep-alive
636
637{"response":"before"} // original response because no extension function
638$
639$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/afterOriginal?username=Alex
640X-Powered-By: Express
641Content-Type: application/json; charset=utf-8
642Content-Length: 20
643ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
644Date: Fri, 11 Nov 2016 13:16:44 GMT
645Connection: keep-alive
646
647{"response":"after"} // original response because no extension function
648$
649$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/before_afterOriginal?username=Alex
650X-Powered-By: Express
651Content-Type: application/json; charset=utf-8
652Content-Length: 27
653ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
654 Date: Fri, 11 Nov 2016 13:16:44 GMT
655Connection: keep-alive
656
657{"response":"before_after"} // original response because no extension function
658```
659
660Now we extend "/....Original" endpoint with a plugin, writing a plugin function in `extend.js`.
661
662```javascript
663var plugins=[
664 {
665 "resource":"/overrideOriginal", // extend overrideOriginal endpoint
666 "method":"GET", // extend overrideOriginal endpoint in get method
667 "mode":"override", // endpoint overrideOriginal must be overrided
668 "params":[query], // express request "req.query" should be throw to extender function
669 "extender": function(requestParams,content,cType,callback){ // this is the extender Function definition
670 // the mode is set to "override" so "content,cType" are both null
671 var username=requestParams.username || ""; // if username exist is set otherwise use a void string
672 callback(null,{"response":"Hello " + username + " this is a plugin function that override"});
673 // ^
674 // |
675 // No Error
676 }
677 },
678 {
679 "resource":"/overrideOriginal", // extend overrideOriginal endpoint
680 "method":"GET", // extend overrideOriginal endpoint in get method
681 "mode":"before", // endpoint overrideOriginal must be overrided
682 "params":[query], // express request "req.query" should be throw to extender function
683 "extender": function(requestParams,content,cType,callback){ // this is the extender Function definition
684 // the mode is set to "override" so "content,cType" are both null
685 var username=requestParams.username || null; // if username exist is set otherwise use a void string
686 if(!username)
687 callback({"error_code":"400", "error_message":"no username field"},null);
688 else
689 callback(null,{"response":"Hello " + username + " this is a plugin function that override"});
690 // ^
691 // |
692 // No Error
693 }
694 },
695 {
696 "resource":"/afterOriginal", // extend afterOriginal endpoint
697 "method":"GET", // extend afterOriginal endpoint in get method
698 "mode":"after", // plugin function must be executed after original endpoint afterOriginal
699 "params":[query], // express request "req.query" should be throw to extender function
700 "extender": function(requestParams,content,cType,callback){ // this is the extender Function definition
701 // the mode is set to "after" so "content,cType" are both not null
702 if(cType==="application/json"){ // if content is a Json
703 var username=requestParams.username || ""; // if username exist is set otherwise use a void string
704 content.after_response="Hello " + username + " this is a plugin function that extend";
705 }
706 callback(null,content);
707 // ^
708 // |
709 // No Error
710 }
711 },
712 {
713 "resource":"/before_afterOriginal", // extend before_afterOriginal endpoint
714 "method":"GET", // extend before_afterOriginal endpoint in get method
715 "mode":"before_after", // plugin function must be executed before and after original endpoint before_afterOriginal
716 "params":[query], // express request "req.query" should be throw to extender function
717 "extender":{
718 "before": function(requestParams,content,cType,callback){ // this is the extender Function definition
719 // the mode is set to "before_after" so in before function "content,cType" are both null
720 var username=requestParams.username || ""; // if username exist is set otherwise use a void string
721 callback(null,{"optionalParams":"Hello " + username + " this is a plugin function that extend"});
722 // ^
723 // |
724 // No Error
725 },
726 "after": function(requestParams,content,cType,callback){ // this is the extender Function definition
727 // the mode is set to "before _after" so in after function "content,cType" are both not null
728 if(cType==="application/json"){ // if content is a Json
729 var username=requestParams.username || ""; // if username exist is set otherwise use a void string
730 content.after_response="Hello " + username + " this is a plugin function that extend";
731 }
732 callback(null,content);
733 // ^
734 // |
735 // No Error
736 }
737 }
738];
739module.exports = plugins;
740```
741
742Restart your application (or invoke `apiextender.install`) to apply the plugin function.
743If you curl the endpoint, you can notice that the original endpoint is extended by the plugin function:
744```shell
745$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/overrideOriginal?username=Alex
746X-Powered-By: Express
747Content-Type: application/json; charset=utf-8
748Content-Length: 65
749ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
750Date: Fri, 11 Nov 2016 13:16:44 GMT
751Connection: keep-alive
752
753 {"response":"Hello Alex this is a plugin function that ovveride"} // original response "{"response":"before"}"
754 // overrided by extension function
755$
756$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/beforeOriginal?username=Alex
757X-Powered-By: Express
758Content-Type: application/json; charset=utf-8
759Content-Length: 90
760ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
761Date: Fri, 11 Nov 2016 13:16:44 GMT
762Connection: keep-alive
763
764{"response":"before","response_before":"Hello Alex this is a plugin function that extend"} // original and extended response
765$
766$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/afterOriginal?username=Alex
767X-Powered-By: Express
768Content-Type: application/json; charset=utf-8
769Content-Length: 88
770ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
771Date: Fri, 11 Nov 2016 13:16:44 GMT
772Connection: keep-alive
773
774{"response":"after","after_response":Hello Alex this is a plugin function that extend"} // original response extended
775$
776$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/before_afterOriginal?username=Alex
777X-Powered-By: Express
778Content-Type: application/json; charset=utf-8
779Content-Length: 165
780ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
781Date: Fri, 11 Nov 2016 13:16:44 GMT
782Connection: keep-alive
783
784{
785 "response":"before_after",
786 "response_before":"Hello Alex this is a plugin function that extend", // original and extended response
787 "response_after":"Hello Alex this is a plugin function that extend"
788}
789```
790
791
792
793#### <a name="exampleonFly"></a>`Extend API on the fly at runtime`
794To extend an API on the fly as described in reference section [install(app,extender,save)](#install), we need to define an
795endpoint that wraps apiextender install function. Add these code lines to you app.js:
796```javascript
797var express=require('express');
798var apiextender = require('apiextender');
799var app=express();
800
801// .....
802// Old app.js logic
803// .....
804
805// Define an endpoint that wrap apiextender install function that lets you to extend API on the fly
806// The access to this endpoint should be protectd with token privileges
807app.post("/installPlugin",function(req,res,next){
808 // check for tokens
809 //.....
810
811 apiextender.install(app,req.body.pluginExtender,req.body.save || false);
812 res.send({"status":"installed"});
813});
814```
815
816With no plugin function defined, the endpoint response is:
817```shell
818 $ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/overrideOriginal?username=Alex
819 X-Powered-By: Express
820 Content-Type: application/json; charset=utf-8
821 Content-Length: 23
822 ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
823 Date: Fri, 11 Nov 2016 13:16:44 GMT
824 Connection: keep-alive
825
826 {"response":"override"} // original response because no extension function
827```
828
829Now we extend "/overrideOriginal" endpoint with a plugin, installed on the fly without defining it in `extend.js`.
830To install, just call `/installPlugin` in POST:
831```shell
832EXTENDER='{
833 "pluginExtender":{
834 "resource":"/overrideOriginal", // extend overrideOriginal endpoint
835 "method":"GET", // extend overrideOriginal endpoint in get method
836 "mode":"override", // endpoint overrideOriginal must be overrided
837 "params":[query], // express request "req.query" should be throw to extender function
838 "extender": function(requestParams,content,cType,callback){ // this is the extender Function definition
839 // the mode is set to "override" so "content,cType" are both null
840 var username=requestParams.username || ""; // if username exist is set otherwise use a void string
841 callback(null,{"response":"Hello " + username + " this is a plugin function that override"});
842 // ^
843 // |
844 // No Error
845 }
846 },
847 "save":false; // not save extenson plugin
848}'
849$
850$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST http://hostname/overrideOriginal?username=Alex
851 -d $EXTENDER
852
853X-Powered-By: Express
854Content-Type: application/json; charset=utf-8
855Content-Length: 22
856ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
857Date: Fri, 11 Nov 2016 13:16:44 GMT
858Connection: keep-alive
859
860{"status":"installed"} // plugin extension installed
861```
862
863
864Restart your application (or invoke `apiextender.install`) to apply the plugin function.
865
866
867
868**Without restarting** your application, if you curl the endpoint, you can notice that the original endpoint is extended by the plugin function:
869```shell
870$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://hostname/overrideOriginal?username=Alex
871X-Powered-By: Express
872Content-Type: application/json; charset=utf-8
873Content-Length: 65
874ETag: "35-6BXjKyRXlm+rSEU9a23z/g"
875Date: Fri, 11 Nov 2016 13:16:44 GMT
876Connection: keep-alive
877
878 {"response":"Hello Alex this is a plugin function that ovveride"} // original response "{"response":"before"}"
879 // overrided by extension function
880$
881```
882
883License - "MIT License"
884-----------------------
885
886MIT License
887
888Copyright (c) 2016 aromanino
889
890Permission is hereby granted, free of charge, to any person obtaining a copy
891of this software and associated documentation files (the "Software"), to deal
892in the Software without restriction, including without limitation the rights
893to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
894copies of the Software, and to permit persons to whom the Software is
895furnished to do so, subject to the following conditions:
896
897The above copyright notice and this permission notice shall be included in all
898copies or substantial portions of the Software.
899
900THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
901IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
902FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
903AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
904LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
905OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
906SOFTWARE.
907
908Author
909------
910Alessandro Romanino ([a.romanino@gmail.com](mailto:a.romanino@gmail.com))<br>
911Guido Porruvecchio ([guido.porruvecchio@gmail.com](mailto:guido.porruvecchio@gmail.com))
912
913Contributors
914------
915CRS4 Microservice Core Team ([cmc.smartenv@crs4.it](mailto:cmc.smartenv@crs4.it))