Summary: This document provides basic information about the extension Node.js + Express support for Web applications.

Extension ID

com.castsoftware.nodejs

What's new?

Please see Node.js - 2.7 - Release Notes for more information.

Description

This extension provides support for Node.jsNode.js is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. 

CAST recommends using this extension with HTML5 and JavaScript ≥ 2.0.0 for the best results.

In what situation should you install this extension?

Regarding Front-End to Back-End connections, we do support the following cross-technology stacks:

iOS Front-End connected to Node.js/PostgreSQL Back-endiOS Front-End connected to Node.js/MSSQL Back-endAngularJS Front-End connected to Node.js/MongoDB Back-end

If your Web application contains Node.js source code and you want to view these object types and their links with other objects, then you should install this extension:

Express framework

The following declarations will create a Node.js Get Operation:

app.get('/login', function (req, res) {
    "use strict";
    console.log('login ' + req.url);
    console.log('login ' + req.query.pseudo);
    var currentSession = getSessionId(req, res);
    datab.userExists(currentSession, req.query.pseudo, res, cbLogin);
});

and this one will create a NodeJS Service Operation:

var admin = express();

app.use('/admin', admin);


Hapi.js framework

Create a server - index.js:

const Hapi = require('hapi');

// Create Server
const server = new Hapi.Server();

Routes: create a route for server:

server.route([     
	{
        method: 'GET',
        path: '/api/directors/{id}',
        handler: api.directors.get,
        config: {
            tags: ['api'],
            description: 'Get one director by id',
            notes: 'Get one director by id',
            validate: {
                params: {
                    id: Joi.number().required()
                }
            },
            cors: {
                origin: ['*']
            }
        }
    }
];

Sails.js framework

Create a server: app.js.

...
  // Start server
  sails.lift(rc('sails'));
...


Routes control at config/routes.js:

...
'GET /site/:idSite' : {controller: "Site", action: "getSite", rel: RelServices.REL_ENUM.GET_VIEWED_SITE},
...
'PUT /alert' : {controller: "Alert", action: "putAlert", rel: RelServices.REL_ENUM.PUT_ALERT, profile: ProfileServices.PROFILE_ENUM.OPERER},
...

Controller actions:

...
self.getSite = function (req, res) {
  ...
  var promise = Site.findOne({
    idSite: idSite
  });
  ...
};


...
self.putAlert = function (req, res) {
  ...
  var promise = Alert.findOne({
    alertId: alertId
  });
  ...
};

Model definition:

...
self.connection = 'postgresqlServer';

self.tableName = 'T_SITE';


self.attributes = {
...
}
...


...
self.connection = 'postgresqlServer';

self.tableName = 'T_ALERT';


self.attributes = {
...
}
...

Transaction from get operation method to database when using SQL analyzer:

Loopback framework

Create webservice from Express API

The App extends and supports Express Middleware. Webservice can be supported as API Express framework:

var loopback = require('loopback');
var app = loopback();

// Create get method
app.get('/', function(req,res){
res.send('hello wor;ld')
});

app.listen(3000);

Create webservice from model

Model todo.js:

module.exports = function(Todo) {
  Todo.stats = function(filter, cb) {
  ...
  };
  ...
  Todo.remoteMethod('stats', {
    accepts: {arg: 'filter', type: 'object'},
    returns: {arg: 'stats', type: 'object'},
    http: { path: '/stats' }
  }, Todo.stats);
  ...
}

Exposing models over REST: https://loopback.io/doc/en/lb3/Exposing-models-over-REST.html. LoopBack models automatically have a standard set of HTTP endpoints that provide REST APIs.

Example: todo.json:

{
  "name": "Todo",
  "base": "PersistedModel",
  "strict": "throw",
  "persisteUndefinedAsNull": true,
  "trackChanges": true,
  "properties": {
    "id": {
      "id": true,
      "type": "string",
      "defaultFn": "guid"
    },
    "title": "string",
    "completed": {
      "type": "boolean",
      "default": false
    },
    "created": {
      "type": "number"
    }
  }
}

 

Koa.js framework

Webservice application from Koa:

var koa = require('koa'),
  router = require('koa-router'),
  cors = require('koa-cors'),
  json = require('koa-json'),
  errorHandler = require('koa-onerror'),
  bodyParser = require('koa-body')(),
  app = koa(),
  routes = new router();


function render(controller, action) {
	...
}

/* routes start */

routes.get(  '/todos',                     render('todos',     'all'));
routes.post( '/todos',        bodyParser,  render('todos',     'create'));
routes.get(  '/todos/:id',                 render('todos',     'show'));
routes.del(  '/todos/:id',                 render('todos',     'delete'));
routes.patch('/todos/:id',    bodyParser,  render('todos',     'update'));
routes.del(  '/todos',                     render('todos',     'deleteAll'));


app.use(require('./app/middlewares/request_logger')());
app.use(json());
app.use(cors({methods: ['GET', 'PUT', 'POST', 'PATCH', 'DELETE']}));
app.use(routes.middleware());

errorHandler(app);

app.listen(Number(process.env.PORT || 9000));

Knex.js framework

Knex.js is a "batteries included" SQL query builder for Postgres, MSSQL, MySQL, MariaDB, SQLite3, Oracle, and Amazon Redshift designed to be flexible, portable, and fun to use. We do not support the creation of tables for this framework. Example:

Define database config:

const Config = require('../config');

module.exports = {
  client: 'postgresql',
  connection: Config.DATABASE_URL || {
    database: Config.DB_NAME,
    host: Config.DB_HOST,
    username: Config.DB_USER,
    password: Config.DB_PASSWORD
  }
};

Create bookshelf from Knex and Bookshelf:

const DatabaseConfig = require('../../db');

const Bookshelf = require('bookshelf');
const Knex      = require('knex')(DatabaseConfig);

module.exports = Bookshelf(Knex);


Add model for bookshelf:

const Bookshelf = require('../util/bookshelf');

const Config = require('../../config');

module.exports = Bookshelf.Model.extend({
  tableName: 'todos',
  url: function () {
    return `${Config.DOMAIN}/${this.get('id')}`;
  },
  serialize: function () {
    return {
      id: this.get('id'),
      title: this.get('title'),
      url: this.url(),
      completed: this.get('completed'),
      order: this.get('order'),
      object: 'todo'
    };
  }
});

Define method for model:

const Todo = require('../../models/todo');

exports.deleteAll = () => {
  // hack to get around Bookshelf's lacking destroyAll
  return new Todo().where('id', '!=', 0).destroy()
  .then(() => []);
};

Access model from webservice method:

exports.register = (server, options, next) => {

server.route([{
    method: 'DELETE',
    path: '/',
    config: {
      handler: (request, reply) => {
        reply(Controller.deleteAll());
      }
    }
  },
])
}

If table isn't found from external, an unknown database table will be created:

Node.js MQTT

Controller.js defines a publisher with a messager:

function openGarageDoor () {
  // can only open door if we're connected to mqtt and door isn't already open
  if (connected && garageState !== 'open') {
    // Ask the door to open
    client.publish('garage/open', 'true')
  }
}


function closeGarageDoor () {
  // can only close door if we're connected to mqtt and door isn't already closed
  if (connected && garageState !== 'closed') {
    // Ask the door to close
    client.publish('garage/close', 'true')
  }
}

garage.js defines a subscriber as:

client.on('connect', () => {
  client.subscribe('garage/open')
  client.subscribe('garage/close')

  // Inform controllers that garage is connected
  client.publish('garage/connected', 'true')
  sendStateUpdate()
})


client.on('message', (topic, message) => {
  console.log('received message %s %s', topic, message)
  switch (topic) {
    case 'garage/open':
      return handleOpenRequest(message)
    case 'garage/close':
      return handleCloseRequest(message)
  }
})

Node.js Seneca Microservice

Create a service:

web-app.js:

var seneca = require('seneca')()

seneca
  .use('user')
  .use('auth')
  .use('../lib/api.js')
  .client({port:10202,pin:{role:'offer',cmd:'*'}})
  .client({port:10201,pin:{role:'user',cmd:'*'}})


var app = express()

app.use( bodyParser.json() )
app.use( seneca.export('web') )
app.use( express.static('./public') )

app.listen(3000)

offer-service:

require('seneca')()
  .use('../lib/offer')
  .listen(10202)
  .ready(function(){
    this.act({role:'offer',cmd:'provide'},console.log)
  })

Define the api.js:

module.exports = function( options ) {
var seneca = this
var plugin = 'api'

seneca.add( {role:plugin, end:'offer'}, end_offer) 

function end_offer( args, done ) {
var user = args.req$.seneca.user || {}

this.act('role:offer,cmd:provide',{nick:user.nick},done)
}

seneca.act({role:'web', use:{
prefix:'/api/',
pin:{role:plugin,end:'*'},
map:{
'offer': { GET:true },
}
}})

return {name:plugin};
}

offer.js:

module.exports = function( options ) {
  var seneca = this
  var plugin = 'offer'


  seneca.add( {role:plugin, cmd:'provide'}, cmd_provide)
  

  function cmd_provide( args, done ) {
    if( args.nick ) return done(null,{product:'Apple'});

    return done(null,{product:'Orange'});
  }


  return {name:plugin};
}

When a service sends an action (seneca.act()):

Click to enlarge

Webservice RestAPI:

...

    seneca.act('role:web',{use:{
      prefix:'/product',
      pin:'role:api,product:*',
      startware: verify_token,
      map:{
        star: { 
          GET:true,
          alias:'/:id/star' 
        },
        handle_star:{
          PUT:true,
          DELETE:true,
          POST:true,
          alias:'/:id/star'
        }
      }
...

Click to enlarge

Supported Node.js versions

VersionSupportComment
v0.x(error)No longer supported
v4.x(tick)LTS
v5.x(tick)Based on Javascript ES6
v6.x(tick)Based on Javascript ES6

v7.x

(tick)Based on Javascript ES6
v8.x(tick)
v9.x(tick)
v10.x(tick)
v11.x(tick)
v12.x(tick) 
v13.x(tick)
v14.x(tick)
v15.x(tick)
v16.x(tick)
v17.x(tick)

Node.js Ecosystem

Node.js comes with numerous libraries and frameworks bringing data access, web services calls, microservices architectures. This list contains all supported libraries:

LibraryCommentData AccessWeb ServiceMessaging
AWS.DynamoDBAmazon database access(tick)

AWS.S3Amazon storage service(tick)

AWS.SQSAmazon messaging service

(tick)
AWS.LambdaAmazon routing solution
(tick)
CosmosDBMicrosoft Azure NoSQL Database solution(tick)

CouchdbCouchdb access(tick)

Couchdb-nanoCouchdb access(tick)

elasticsearchOpen-source search engine (tick)

ExpressNode.js application framework
(tick)
HapiNode.js application framework(tick)(tick)
KnexNode.js SQL query builder (tick)

KoaNode.js application framework(tick)

LoopbackNode.js application framework(tick)(tick)
MarklogicMarklogic access(tick)

MemcachedStorage framework(tick)

Mode-mongodb-nativeMongoDB access(tick)

Mongo-clientMongoDB access(tick)

MongooseMongoDB access(tick)

MQTTMessaging library

(tick)
mssqlSQL server(tick)

my_connectionMySQL access(tick)

myssqlNodejs module to manipulate MySQL database


Node-couchdbCouchdb access(tick)

node-sqlserverSQL server(tick)

oracledbOracle Database access(tick)

pgPostgreSQL access(tick)

redisMongoDB access(tick)

SailsNode.js application framework(tick)(tick)
SenecaMicroservice toolkit
(tick)

Function Point, Quality and Sizing support

This extension provides the following support:

Function Points
(transactions)
(tick)
Quality and Sizing(tick)

Comparison with existing support for JavaScript

CAST AIP has provided support for analyzing JavaScript via its JEE and .NET analyzers (provided out of box in CAST AIP) for some time now. The HTML5/JavaScript extension (on which the Node.js extension depends) also provides support for JavaScript but with a focus on web applications. CAST highly recommends that you use this extension if your Application contains JavaScript and more specifically if you want to analyze a web application, however you should take note of the following:

In CAST AIP 8.3.x support for analyzing JavaScript has been withdrawn from the JEE and .NET analyzers.

AIP Core compatibility

This extension is compatible with:

AIP Core release
Supported
8.3.x(tick)

Supported DBMS servers

DBMSSupported?
CSS / PostgreSQL(tick)

Prerequisites

(tick)An installation of any compatible release of AIP Core (see table above)

Dependencies with other extensions

Some CAST extensions require the presence of other CAST extensions in order to function correctly. The Node.js extension requires that the following other CAST extensions are also installed:

Note that when using the CAST Extension Downloader to download the extension and the Manage Extensions interface in CAST Server Manager to install the extension, any dependent extensions are automatically downloaded and installed for you. You do not need to do anything.

Download and installation instructions

The extension will be automatically downloaded and installed in AIP Console (it is a "shipped" extension which means it is delivered with AIP Core). You can also manually install the extension using the Application - Extensions interface. When installed, follow the instructions below to run a new analysis/snapshot to generate new results:

Packaging, delivering and analyzing your source code

Once the extension is downloaded and installed, you can now package your source code and run an analysis. The process of packaging, delivering and analyzing your source code is described below:

What results can you expect?

Once the analysis/snapshot generation has completed, you can view the results in the normal manner (for example via CAST Enlighten):

Node.js application with MongoDB data storage exposing web services

Objects

The following specific objects are displayed in CAST Enlighten:

IconDescription

Node.js Application

Node.js Port

Node.js Delete Operation Service

Node.js Get Operation Service
Node.js Post Operation Service
Node.js Put Operation Service

Node.js Service

Node.js Express Use

Node.js Express Controller

Node.js Get Http Request Service

Node.js Post Http Request Service

Node.js Put Http Request Service

Node.js Delete Http Request Service

Node.js Unknown Database

Node.js Collection

Node.js Memcached Connection

Node.js Memcached Value

Node.js Call to Java Program

Node.js Call to Generic Program

Node.js Restify Get Operation

Node.js Restify Post Operation

Node.js Restify Put Operation

Node.js Restify Delete Operation

Node.js AWS SQS Publisher

Node.js AWS SQS Receiver

Node.js AWS SQS Unknown Publisher

Node.js AWS SQS Unknown Receiver

Node.js AWS SNS Publisher

Node.js AWS SNS Subscriber

Node.js AWS SNS Unknown Publisher

Node.js AWS SNS Unknown Subscriber

Node.js AWS call to Lamba Function

Node.js AWS call to unknown Lambda Function

External link behavior

Behaviour is different depending on the version of CAST AIP you are using the extension with:

Connector per RDBMS Vendor

Oracle "oracledb" connector

var oracledb = require('oracledb');
connection = oracledb.getConnection(
  {
    user          : "hr",
    password      : "welcome",
    connectString : "localhost/XE"
  }
);
connection.execute(
      "SELECT department_id, department_name FROM departments WHERE department_id < 70",
      function(err, result)
      {
        if (err) { console.error(err); return; }
        console.log(result.rows);
      }
  );

MS SQL "node-sqlserver" and "mssql" connectors

var sql = require('node-sqlserver');
//
var connStr = "Driver={SQL Server Native Client 11.0};Server=myySqlDb,1433;Database=DB;UID=Henry;PWD=cat;";
var query = "SELECT * FROM GAData WHERE TestID = 17";
sql.open(connStr, function(err,conn){
    if(err){
        return console.error("Could not connect to sql: ", err);
    }
    conn.queryRaw("SELECT TOP 10 FirstName, LastName FROM authors", function (err, results) {
        if (err) {
            console.log("Error running query!");
            return;
        }
        for (var i = 0; i < results.rows.length; i++) {
            console.log("FirstName: " + results.rows[i][0] + " LastName: " + results.rows[i][1]);
        }
    });
});
var match = "%crombie%";
sql.query(conn_str, "SELECT FirstName, LastName FROM titles WHERE LastName LIKE ?", [match], function (err, results) { 
    for (var i = 0; i < results.length; i++) {
        console.log("FirstName: " + results[i].FirstName + " LastName: " + results[i].LastName);
    }
});


var sql = require('mssql');
var config = {
    user: '...',
    password: '...',
    server: 'localhost', // You can use 'localhost\\instance' to connect to named instance 
    database: '...',
     
    options: {
        encrypt: true // Use this if you're on Windows Azure 
    }
}
  
var connection = new sql.Connection(config, function(err) {
    // ... error checks 
     
    // Query 
     
    var request = new sql.Request(connection); // or: var request = connection.request(); 
    request.query('select * from authors', function(err, recordset) {
        // ... error checks 
         
        console.dir(recordset);
    });
     
    // Stored Procedure 
     
    var request = new sql.Request(connection);
    request.input('input_parameter', sql.Int, 10);
    request.output('output_parameter', sql.VarChar(50));
    request.execute('procedure_name', function(err, recordsets, returnValue) {
        // ... error checks 
         
        console.dir(recordsets);
    });
     
});

PostgreSQL "pg" connector

var pg = require("pg");
var conString = "pg://operator:CastAIP@localhost:2280/postgres";
var client = new pg.Client(conString);
client.connect();
var querySchemas = client.query("select nspname from pg_catalog.pg_namespace");
querySchemas.on("row", function (row, result) {
    "use strict";
    result.addRow(row);
});
querySchemas.on("end", function (result) {
    "use strict";
    console.log(result.rows);
    client.end();
});

MySQL "my_connection" connector

var connection = require("my_connection");
connection.query('my_url', 
			function result_getCatLogDetails(getCatLogDetails_err, getCatLogDetails_rows, 
			getCatLogDetails_fields) {
		
				if (getCatLogDetails_err) {
			        logContent += '|ERROR'+";";
					logContent += getCatLogDetails_err.message+";";
					utils.logAppDetails(logContent);
			        deferred.reject(new Error(getCatLogDetails_err));
			    } else {
			        deferred.resolve(getCatLogDetails_rows);
			    }
			});

Connector per NoSQL Vendor

Even if we don't have NoSQL server side representation, we will create a client side representation based on the API access. Node.js analyzer will create links from Javascript functions to NoSQL "Database" or "Table" equivalents as follows:

Azure Cosmos DB

See Azure Cosmos DB support for Node.js source code.

CouchDB

See CouchDB support for Node.js source code.

DynamoDB

See DynamoDB support for Node.js source code.

Elasticsearch

See Elasticsearch support for Node.js source code.

MarkLogic

See MarkLogic support for Node.js source code.

Memcached

See Memcached support for Node.js source code.

MongoDB "mongoose"

See MongoDB support for Node.js source code.

Redis

See MongoDB support for Node.js source code.

Amazon Web Services (AWS)

Support for lambda

Lambda services allow executing some source code on the cloud. The execution can be set to be triggered by some AWS events. 

Lambda functions can be deployed using several deployment frameworks. The supported deployment frameworks are listed on this page.

When a lambda function is created and its runtime is java, the current extension is responsible for linking the lambda objects and their triggers with the java handler functions.

Example

Let us consider a source code defining a lambda function that has two triggers: an SQS queue and an API Gateway. The lambda function has a java runtime (for instance java8) and the handler function is given by the handler function fullname. 

If the lambda function is deployed using a supported deployment framework (such as CloudFormation), the analysis will create a lambda function, an SQS receiver, and an API Gateway objects. Each of these objects has a runtime property (java8) and a handler property with the function fullname. 

If the current extension finds a java method matching the handler fullname a link to that java method will be added from the lambda function, the SQS queue and the API Gateway objects.


Support for SQS

Links

Link TypeFunction
callLink
  • sendMessage

  • sendMessageBatch
  • receiveMessage

Code samples

This code will publish a message into the "SQS_QUEUE_URL" queue:

// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set the region 
AWS.config.update({region: 'REGION'});

// Create an SQS service object
var sqs = new AWS.SQS({apiVersion: '2012-11-05'});

var params = {
   // Remove DelaySeconds parameter and value for FIFO queues
  DelaySeconds: 10,
  MessageAttributes: {
    "Title": {
      DataType: "String",
      StringValue: "The Whistler"
    },
    "Author": {
      DataType: "String",
      StringValue: "John Grisham"
    },
    "WeeksOn": {
      DataType: "Number",
      StringValue: "6"
    }
  },
  MessageBody: "Information about current NY Times fiction bestseller for week of 12/11/2016.",
  // MessageDeduplicationId: "TheWhistler",  // Required for FIFO queues
  // MessageGroupId: "Group1",  // Required for FIFO queues
  QueueUrl: "SQS_QUEUE_URL"
};

sqs.sendMessage(params, function(err, data) {
  if (err) {
    console.log("Error", err);
  } else {
    console.log("Success", data.MessageId);
  }
});

This code will receive a message from the queue "SQS_QUEUE_URL":

// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set the region 
AWS.config.update({region: 'REGION'});

// Create an SQS service object
var sqs = new AWS.SQS({apiVersion: '2012-11-05'});

var params = {
   // Remove DelaySeconds parameter and value for FIFO queues
  DelaySeconds: 10,
  MessageAttributes: {
    "Title": {
      DataType: "String",
      StringValue: "The Whistler"
    },
    "Author": {
      DataType: "String",
      StringValue: "John Grisham"
    },
    "WeeksOn": {
      DataType: "Number",
      StringValue: "6"
    }
  },
  MessageBody: "Information about current NY Times fiction bestseller for week of 12/11/2016.",
  // MessageDeduplicationId: "TheWhistler",  // Required for FIFO queues
  // MessageGroupId: "Group1",  // Required for FIFO queues
  QueueUrl: "SQS_QUEUE_URL"
};

sqs.receiveMessage(params, function(err, data) {
  if (err) {
    console.log("Error", err);
  } else {
    console.log("Success", data.MessageId);
  }
});

What results can you expect?

Once the analysis/snapshot generation has completed, you can view the results in the normal manner (for example via CAST Enlighten):

Click to enlarge

When the evaluation of the queue name fails, a Node.js AWS SQS Unknown Publisher (or Receiver) will be created.

Support for AWS S3

Links

Link TypeFunction
No Link
  • createBucket
callLink
  • createMultipartUpload

  • createPresignedPost

  • abortMultipartUpload

  • completeMultipartUpload

  • deleteBucketAnalyticsConfiguration

  • deleteBucketCors

  • deleteBucketEncryption

  • deleteBucketInventoryConfiguration

  • deleteBucketLifecycle

  • deleteBucketMetricsConfiguration

  • deleteBucketPolicy

  • deleteBucketReplication

  • deleteBucketTagging

  • deleteBucketWebsite

  • deleteObjectTagging

  • deletePublicAccessBlock

  • getBucketAccelerateConfiguration

  • getBucketAcl

  • getBucketAnalyticsConfiguration

  • getBucketCors

  • getBucketEncryption

  • getBucketInventoryConfiguration

  • getBucketLifecycle

  • getBucketLifecycleConfiguration

  • getBucketLocation

  • getBucketLogging

  • getBucketMetricsConfiguration

  • getBucketNotification

  • getBucketNotificationConfiguration

  • getBucketPolicy

  • getBucketPolicyStatus

  • getBucketReplication

  • getBucketTagging

  • getBucketVersioning

  • getBucketWebsite

  • getObjectAcl

  • getObjectLegalHold

  • getObjectLockConfiguration

  • getObjectRetention

  • getObjectTagging

  • getPublicAccessBlock

  • getSignedUrl

  • listBuckets
  • listBucketAnalyticsConfigurations

  • listBucketInventoryConfigurations

  • listBucketMetricsConfigurations

  • listMultipartUploads

  • listObjectVersions

  • listParts

  • putBucketLogging
  • putBucketAnalyticsConfiguration
  • putBucketLifecycleConfiguration

  • putBucketMetricsConfiguration

  • putBucketNotification

  • putBucketNotificationConfiguration

  • putBucketPolicy

  • putBucketReplication

  • putBucketRequestPayment

  • putBucketTagging

  • putBucketVersioning

  • putObjectAcl

  • putObjectLegalHold

  • putObjectLockConfiguration

  • putObjectRetention

  • putObjectTagging

  • putPublicAccessBlock

  • putBucketAccelerateConfiguration

  • putBucketAcl

  • putBucketCors

  • putBucketEncryption

  • putBucketInventoryConfiguration

  • putBucketLifecycle

  • upload

  • uploadPart

  • uploadPartCopy

useInsertLink
  • putObject
useDeleteLink
  • deleteBucket
  • deleteObject

  • deleteObjects

useSelectLink
  • getObject
  • getObjectTorrent
  • listObjects

  • listObjectsV2

useUpdateLink
  • putBucketLogging
  • putBucketAnalyticsConfiguration

Code samples

This code will create a S3 Bucket named "MyBucket" on an AWS server in region "REGION" and puts an object in it

// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set the region 
AWS.config.update({region: 'REGION'});

// Create S3 service object
s3 = new AWS.S3({apiVersion: '2006-03-01'});

// Create the parameters for calling createBucket
var bucketParams = {
  Bucket : "MyBucket",
  ACL : 'public-read'
};

// call S3 to create the bucket
s3.createBucket(bucketParams, function(err, data) {
  if (err) {
    console.log("Error", err);
  } else {
    console.log("Success", data.Location);
  }
});

params = {
	// ...
    Bucket: "MyBucket"
};
s3.putObject(params, function(err, data) {
    if (err) console.log(err, err.stack); // an error occurred
    else     console.log(data);           // successful response
});

What results can you expect?

Once the analysis/snapshot generation has completed, you can view the results in the normal manner (for example via CAST Enlighten):

Analysis of the code sample

Known limitations for AWS support
sqs.receiveMessage(params).promise().then( () => {});
var request = sqs.receiveMessage(params);
request.send(() => {});

Support for SNS

The following APIs are supported:

For SDK V2

For the publish method a NodeJS AWS SNS Publisher object is created. Its name is that of the topic.

For the subscribe methods, a NodeJS AWS SNS Subscriber object is created. Its name is that of the topic. Then for each supported protocol, an object is created with a callLink from the subscriber to that object. The supported protocols are the following:

protocolobject createdname of the object
emailNodeJS Emailan Email   (the email addresses are not evaluated)
smsNodeJS SMSan SMS   (the SMS numbers are not evaluated)
http/httpsNodeJS AWS Post HttpRequers servicethe url (evaluated from the endpoint)
sqsNodeJS AWS Simple Queue Service Publisherthe name of the queue (evaluated from the endpoint)
lambdaNodeJS Call to AWS Lambda Functionthe name of the lambda function (evaluated from the endpoint)

the com.castsoftware.wbslinker will create a callLink between the SNS Publishers and SNS Subscribers which have the same name.


Example

When analyzing the following source code:

var AWS = require('aws-sdk');
// Set region
AWS.config.update({region: 'REGION'});
// Create promise and SNS service object

var sns = new AWS.SNS({apiVersion: '2010-03-31'})

function my_subscribe(params) {
    sns.subscribe(params, function (err, data) {
        if (err) console.log(err, err.stack); // an error occurred
        else console.log(data);           // successful response
    });
}

function my_publish(params) {
    sns.publish(params);
}

function foo() {
    let topicArn = "arn:aws:sns:eu-west-3:757025016730:testTopic";
    my_subscribe({Protocol: "EMAIL", TopicArn: topicArn, Endpoint: "EMAIL_ADDRESS"})
    my_subscribe({Protocol: "SMS", TopicArn: topicArn, Endpoint: "911"})
    my_subscribe({Protocol: "LAMBDA", TopicArn: topicArn, Endpoint: "arn:aws:lambda:eu-west-3:757025016730:testLambda"})
    my_subscribe({Protocol: "HTTP", TopicArn: topicArn, Endpoint: "http:/myapi.api.test.com"})
 }
function bar() {
    const params2 = {
        TopicArn: "arn:aws:sns:eu-west-3:757025016730:testTopic",
        Message: "MESSAGE_TEXT"
    };
    my_publish(params2)
}



Linking

The extension com.castsoftware.wbslinker is responsible for matching NodeJS Call to AWS Lambda Function objects to Lambda Function objects such as Java AWS Lambda Function during application-level analysis.

Support for AWS XRay

aws-xray encapsulates AWS methods calls in order to provide status and load status. However, the encapsulation did not allow the extension to provide objects and links. With the support of AWS XRay starting in 2.6.0-beta4, these objects and links will be created.

Code samples

This code will encapsulate AWS SDK then create a dynamoDB instance, and Document client instance.

// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0

// default imports
const AWSXRay = require('aws-xray-sdk-core')
const AWS = AWSXRay.captureAWS(require('aws-sdk')) // Encapsulate AWS SDK
const { metricScope, Unit } = require("aws-embedded-metrics")
const DDB = new AWS.DynamoDB({ apiVersion: "2012-10-08" }) // use AWS as usual
const { v1: uuidv1 } = require('uuid');

// environment variables
const { TABLE_NAME, ENDPOINT_OVERRIDE, REGION } = process.env
const options = { region: REGION }
AWS.config.update({ region: REGION })

if (ENDPOINT_OVERRIDE !== "") {
    options.endpoint = ENDPOINT_OVERRIDE
}

const docClient = new AWS.DynamoDB.DocumentClient(options)
// response helper
const response = (statusCode, body, additionalHeaders) => ({
    statusCode,
    body: JSON.stringify(body),
    headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', ...additionalHeaders },
})

function isValidRequest(context, event) {
    return (event.body !== null)
}

function getCognitoUsername(event){
    let authHeader = event.requestContext.authorizer;
    if (authHeader !== null)
    {
        return authHeader.claims["cognito:username"];
    }
    return null;

}

function addRecord(event) {

    let usernameField = {
        "cognito-username": getCognitoUsername(event)
    }

    // auto generated date fields
    let d = new Date()
    let dISO = d.toISOString()
    let auto_fields = {
        "id": uuidv1(),
        "creation_date": dISO,
        "lastupdate_date": dISO
    }

    //merge the json objects
    let item_body = {...usernameField, ...auto_fields, ...JSON.parse(event.body) }

    console.log(item_body);
    
    //final params to DynamoDB
    const params = {
        TableName: TABLE_NAME,
        Item: item_body
    }

    return docClient.put(params)
}

// Lambda Handler
exports.addToDoItem =
    metricScope(metrics =>
        async (event, context, callback) => {
            metrics.setNamespace('TodoApp')
            metrics.putDimensions({ Service: "addTodo" })
            metrics.setProperty("RequestId", context.requestId)

            if (!isValidRequest(context, event)) {
                metrics.putMetric("Error", 1, Unit.Count)
                return response(400, { message: "Error: Invalid request" })
            }

            try {
                let data = await addRecord(event).promise()
                metrics.putMetric("Success", 1, Unit.Count)
                return response(200, data)
            } catch (err) {
                metrics.putMetric("Error", 1, Unit.Count)
                return response(400, { message: err.message })
            }
        }
    )

What results can you expect?

Once the analysis/snapshot generation has completed, you can view the results in the normal manner (for example via CAST Enlighten):

Click to enlarge

Analysis of the code sample

Support for AWS Embedded Metrics

aws-embedded-metrics encapsulates functions to provide metrics. However, the encapsulation did not allow the extension to provide links. With the support of AWS Embedded Metrics starting in 2.6.0-beta4, these links will be create.

Code samples

This code will encapsulate lambda handler "addToDoItem".

// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0

// default imports
const AWSXRay = require('aws-xray-sdk-core')
const AWS = AWSXRay.captureAWS(require('aws-sdk')) // Encapsulate AWS SDK
const { metricScope, Unit } = require("aws-embedded-metrics")
const DDB = new AWS.DynamoDB({ apiVersion: "2012-10-08" }) // use AWS as usual
const { v1: uuidv1 } = require('uuid');

// environment variables
const { TABLE_NAME, ENDPOINT_OVERRIDE, REGION } = process.env
const options = { region: REGION }
AWS.config.update({ region: REGION })

if (ENDPOINT_OVERRIDE !== "") {
    options.endpoint = ENDPOINT_OVERRIDE
}

const docClient = new AWS.DynamoDB.DocumentClient(options)
// response helper
const response = (statusCode, body, additionalHeaders) => ({
    statusCode,
    body: JSON.stringify(body),
    headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', ...additionalHeaders },
})

function isValidRequest(context, event) {
    return (event.body !== null)
}

function getCognitoUsername(event){
    let authHeader = event.requestContext.authorizer;
    if (authHeader !== null)
    {
        return authHeader.claims["cognito:username"];
    }
    return null;

}

function addRecord(event) {

    let usernameField = {
        "cognito-username": getCognitoUsername(event)
    }

    // auto generated date fields
    let d = new Date()
    let dISO = d.toISOString()
    let auto_fields = {
        "id": uuidv1(),
        "creation_date": dISO,
        "lastupdate_date": dISO
    }

    //merge the json objects
    let item_body = {...usernameField, ...auto_fields, ...JSON.parse(event.body) }

    console.log(item_body);
    
    //final params to DynamoDB
    const params = {
        TableName: TABLE_NAME,
        Item: item_body
    }

    return docClient.put(params)
}

// Lambda Handler
exports.addToDoItem =
    metricScope(metrics =>
        async (event, context, callback) => {
            metrics.setNamespace('TodoApp')
            metrics.putDimensions({ Service: "addTodo" })
            metrics.setProperty("RequestId", context.requestId)

            if (!isValidRequest(context, event)) {
                metrics.putMetric("Error", 1, Unit.Count)
                return response(400, { message: "Error: Invalid request" })
            }

            try {
                let data = await addRecord(event).promise()
                metrics.putMetric("Success", 1, Unit.Count)
                return response(200, data)
            } catch (err) {
                metrics.putMetric("Error", 1, Unit.Count)
                return response(400, { message: err.message })
            }
        }
    )

What results can you expect?

Once the analysis/snapshot generation has completed, you can view the results in the normal manner (for example via CAST Enlighten)

Analysis of the code sample

Call to Program

NodeJS extension now supports call to external programs using the child-process module.

The fork() function is not handled as its only purpose is to fork node.js programs.

These declaration create a call to a java program/JAR file

const exec = require('child_process').exec;

exec('java -cp com.castsoftware.Archive -jar jarFile.jar', (e, stdout, stderr) => {
    if (e instanceof Error) {
        console.error(e);
        throw e;
    }

    console.log('stdout ', stdout);
    console.log('stderr ', stderr);
});

const cp = require('child_process');
const class_name = 'com.castsoftware.Foo'

function call_foo(req, resp) {
    const args = [
        '-cp',
        '/bin',
        class_name
    ];
    const proc = cp.spawn('java', args);
}

These declarations creates a call to a Python Program

const execFile = require('child_process').execFile;
const python_file = 'app.py'

const child = execFile('python', [python_file], (error, stdout, stderr) => {

    if (error) {
        console.error('stderr', stderr);
        throw error;
    }
    console.log('stdout', stdout);
});

Restify

NodeJS extension now supports routing using the restify module. The following is an example of application using restify to handle some URIs.

var restify = require('restify');

function send(req, res, next) {

  res.send('hello ' + req.params.name);
  next();
}

var server = restify.createServer();

server.post('/hello', function create(req, res, next) {

  res.send(201, Math.random().toString(36).substr(3, 8));
  return next();

});

server.put('/hello', send);
server.get('/hello/:name', function create(req, res, next) {

  res.send(201, Math.random().toString(36).substr(3, 8));
  return next();

},send);
server.head('/hello/:name', send);

server.del('hello/:name', function rm(req, res, next) {

  res.send(204);
  return next();

});

server.listen(8080, function() {

  console.log('%s listening at %s', server.name, server.url);

});

SQL Named Query 

When executing an sql query directly, a CAST SQL NamedQuery object will be created:

var oracledb = require('oracledb');

connection = oracledb.getConnection(
  {
    user          : "hr",
    password      : "welcome",
    connectString : "localhost/XE"
  }
);

oracledb.getConnection(
  {
    user          : "hr",
    password      : "welcome",
    connectString : "localhost/XE"
  },
  function(err, connection)
  {
    if (err) { console.error(err); return; }
    connection.execute(
      "SELECT department_id, department_name "
    + "FROM titles "
    + "WHERE department_id < 70 "
    + "ORDER BY department_id",
      function(err, result)
      {
        if (err) { console.error(err); return; }
        console.log(result.rows);
      });
  });

Structural Rules

The following structural rules are provided:

2.7.0-funcrelhttps://technologies.castsoftware.com/rules?sec=srs_nodejs&ref=||2.7.0-funcrel
2.7.0-beta3https://technologies.castsoftware.com/rules?sec=srs_nodejs&ref=||2.7.0-beta3
2.7.0-beta2https://technologies.castsoftware.com/rules?sec=srs_nodejs&ref=||2.7.0-beta2
2.7.0-beta1https://technologies.castsoftware.com/rules?sec=srs_nodejs&ref=||2.7.0-beta1

Known Limitations

In this section we list the most significant functional limitations that may affect the analysis of applications using Node.js: