What are your best practices for structuring Javascript modules in Apigee Edge?

The Javascript callout policy in Apigee Edge is flexible and powerful. API publishers can use it for simple things, like converting the case of properties on a JSON hash, or for injecting a single header.

But sometimes there's a desire to do something a little more complex, like generate or validate a JWT, or verify a JSON schema. And, in those cases, it's nice to be able to use JS lexical scoping to keep the JS in separate modules, separate.

What I mean is, suppose you have two scripts in your api proxy: file1.js and dependency.js . The first depends on the second. In the JS policy you would specify this, like so:

<Javascript name='JS-1' timeLimit='200' >
  <Properties>
    <Property name='prop1'>value-here</Property>
  </Properties>
  <IncludeURL>jsc://dependency.js</IncludeURL>
  <ResourceURL>jsc://file1.js</ResourceURL>
</Javascript>

Suppose the contents of dependency.js are:

// dependency.js
// ------------------------------------------------------------------
//

var context = { foo: 5 };
// ...other work here...
var seven = 7;

What this means is, the 'context' object which is exposed by Apigee Edge, is now over-written, for all subsequent modules. If the code in file1.js looks like this:

// file1.js
// ------------------------------------------------------------------
//
// this module depends on dependency.js
//

context.setVariable('seven', seven);

...then at runtime, the callout will fail with an error like this:

{
  "fault": {
    "detail": {
      "errorcode": "steps.javascript.ScriptExecutionFailed"
    },
    "faultstring": "Execution of JS-2 failed with error: Javascript runtime error: \"TypeError: Cannot find function setVariable in object [object Object]. (file1_js#16). at line 16 \""
  }
}

The way to fix this is to wrap the code in each module in an independent lexical scope (if you're not familiar, you can read a little about this concept here). Using lexical scoping is an old trick and is used to keep separate modules separate, in systems everywhere, from nodejs modules, jQuery, browserify, and many others.

Using lexical scoping in dependency.js might look like this:

// dependency.js
// ------------------------------------------------------------------
//

(function(){
  var context = { foo: 5 };
  // ...other work here...
  var seven = 7;
}());

The above still uses a variable called 'context' , but that variable is scoped inside a function. In fact this is a special sort of function - an Immediately-Invoked Function Expression, or IIFE. (It's also anonymous, so we might call it an Immediately-Invoked and Anonymous Function Expression). The point is, the variable 'context' is wrapped inside the body of a function, and that function is invoked "right now", in other words, _immediately_.

The variable declarations made inside that function have no effect on the global scope.

This is good, and bad. The good: file1.js still can access the context variable that Edge provides. The bad: file1.js cannot access the variable 'seven'. How can we get the dependency.js module to expose SOME variables but not others? The answer is, using the global scope.

Let's modify the dependency.js module to look like this;

// dependency.js
// ------------------------------------------------------------------
//

(function(){
  var context = { foo: 5 };
  // ...other work here...
  var seven = 7;
  var globalScope = (function(){ return this; }).call(null);
  globalScope.seven = seven;
}());

What's going on here? The globalScope variable inside the IIFE refers to the "global" or toplevel scope in Javascript. (If you need the details, they are available here). It's a way the function can expose something that will be available to other modules.

Using this design, the code in file1.js will "see" the "seven" variable from dependency.js, but won't "see" the context variable. The result is, this code will work as expected:

context.setVariable('seven', seven);

I like this approach for encapsulating my JS code into modules.

The example of a module overwriting "context" illustrates the point, but the problem is a general one. Suppose you have two dependencies, and one of the dependencies overwrites the values exposed by the other. One cannot be sure that any two arbitrary modules won't step on each other's variable names. Using lexical scoping is a proven, well-known way to avoid that pitfall, and this particular implementation is how I do it within the realm of Javascript callouts in Apigee Edge.

You may be thinking, what if I want my module to expose a set of things? Maybe a set of functions? Let's consider a base64 codec, that exposes an encode and a decode function. What would that look like? Remember, you can export full objects. And that's probably a good idea.

// base64codec.js
// ------------------------------------------------------------------
//
// Base64 encoder and decoder
//

(function (){
  var Base64 = {
        decode : function (input) { .... },
        encode : function (input) { .... }
      };

  // export into the global namespace
  if (typeof exports === "object" && exports) {
    // works for nodejs
    exports.B64 = Base64;
  }
  else {
    // works in rhino
    var globalScope = (function(){ return this; }).call(null);
    globalScope.B64 = Base64;
  }

}());

Then, given the above dependency.js file you could configure your JS callout like this:

<Javascript name='JS-3' timeLimit='200' >
  <IncludeURL>jsc://dependency.js</IncludeURL>
  <IncludeURL>jsc://base64codec.js</IncludeURL>
  <ResourceURL>jsc://file2.js</ResourceURL>
</Javascript>

And then the file2.js source could access the "exports" of both independent modules:

// file2.js
// ------------------------------------------------------------------
//
// this module depends on base64codec.js and dependency.js
//

context.setVariable('seven', seven);
context.setVariable('b64', B64.encode(context.getVariable('proxy.pathsuffix')));

What do YOU think? What are YOUR best practices for modularizing JS source? Got any tips?

7 9 2,293
9 REPLIES 9

you could posted this as a article!

I think Dino posted this as a question because wanted to get your feedback/opinion as an answer 😉

yes that's TRUE.

Not applicable

Excellent advice here Dino.

Note that attempts within the Edge runtime to overwrite global scope variables do not persist past the execution of the current resource callout.

The example of overwriting context thankfully is localized to just the current script execution and does not impact runtime variables beyond that script.

Very nice @Dino, exactly what I needed when I needed it!

Now to figure out how to test this outside of Apigee and to share it with other proxies.

Hey Kurt, Testing outside of Apigee Edge is a little tricky. You can of course run things using jrunscript, which is a JS environment included with the JDK that uses Rhino. But inside the jrunscript, there are none of the Apigee Edge ambient variables, like context, response, and properties. So you'd need to mock those, to allow your script to be testable. Mocking those things to enable unit testing of JS callouts is an interesting topic for another article!

Thanks, my goal is to unit test without modification, in other words, use the JS-source-file.js as is from the proxy bundle.

This article is interesting: https://community.apigee.com/articles/3964/unit-testing-javascript-code-with-mocha-sinon-and.html

Yes, that's what I figured - test the source with no modification. What I mean is, you'd need to inject those variables into the context before running your script, probably with some sort of unit-testing framework that supports mocks. I don't know mocha or sinon, but that article seems to describe exactly what you'd need.

Great article, in the JS file we can also follow better error handling which can wrap the code in try...catch.