How to mock a target backend with a Node.js API proxy using apimocker

The problem

Very frequently when implementing an API proxy we run into the following problem: “Oh no, the target backend my API proxy has to hit is not yet available! I cannot try my stuff :(, what shall I do?“.

That is actually nothing to worry about, you just need to create a mock for the target backend! You can even create that target backend mock as an API proxy and deploy it in Apigee Edge.

Below you can find a detailed description of the backend mocking solution.

The Solution

So it is easier to grasp how this backend mocking concept works we have provided a sample project here.

Imagine that we have an online shop where we sell movie DVDs. When the user browses on the product details page, we would like to provide him with some information about the movie. Our shop API is calling The Open Movie Database (OMDb) API, to fetch the required data. Unfortunately, just when we are about to start testing our shop API, OMDb is unavailable for a whole day, due to some maintenance activities. But that is not going to be a problem for us, because at the beginning of the project, we prepared for such a scenario, creating a mock for that service, so we can continue with our work, while the target backend is under maintenance.

The image below shows the directory structure of our mock API proxy.

3144-mock-api-proxy.png

Details of all the files included in the mock API proxy are provided below.

APIProxy Base Configuration

In this file we only need to specify the name of our API proxy, that needs to be unique within the organization where it is being deployed.

mock/apiproxy/mock.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<APIProxy name="mock">
    <Description>Mock</Description>
</APIProxy>

ProxyEnpoint

Our mock API proxy has a unique ProxyEndpoint configured to route every request to a Node.js application that will be the one serving the mock responses.

mock/apiproxy/proxies/default.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ProxyEndpoint name="default">
    <Description>default</Description>
    <RouteRule name="Any">
        <TargetEndpoint>node</TargetEndpoint>
    </RouteRule>
    <RouteRule name="No Route"/>
    <HTTPProxyConnection>
        <BasePath>/mock</BasePath>
        <VirtualHost>default</VirtualHost>
    </HTTPProxyConnection>
</ProxyEndpoint>

Node.js application

The Node.js application is the core of our mock and uses a Node.js module called apimocker, that in most cases will allow us to quickly create a mock service without coding.

Find below the package.json file used for this Node.js application.

mock/apiproxy/resources/node

{
  "name": "mock",
  "version": "1.0.0",
  "description": "Mock",
  "main": "app.js",
  "dependencies": {
    "apimocker": "^0.4.13"
  }
}

The entry point of the Node.js application would look as follows:

mock/apiproxy/resources/node/app.js

var ApiMocker = require('apimocker');
var options = {};
var apiMocker = ApiMocker.createServer(options)
    .setConfigFile('config.json')
    .start();

Apimocker uses a configuration file to define the routes for which our mock should be providing responses. For each of the routes we can define the supported HTTP verbs, the HTTP status code to return and the location of the file containing the response payload to serve.

As explained in their README, different responses can actually be provided based on different request parameters.

Find below the configuration file used in the sample mock API proxy created.

{
    "mockDirectory": "./mock",
    "quiet": false,
    "port": "8080",
    "latency": 50,
    "logRequestHeaders": false,
    "webServices": {
        "": {
            "latency": 20,
            "verbs": ["get"],
            "switch": ["i"],
            "responses": {
                "get": {"httpStatus": 200, "mockFile": "error.json"}
            },
            "switchResponses": {
                "itt2294629": {"httpStatus": 200, "mockFile": "frozen.json"},
                "itt0120338": {"httpStatus": 200, "mockFile": "titanic.json"}
            }
        }
    }
}

In the example above the response sent back depends on the value provided for the "i" parameter in the querystring. When hitting /?i=itt2294629 the payload returned is the one in mock/frozen.json, whereas when requesting /?i=itt0120338 the one in mock/titanic.json will be returned. For any other value of parameter "i", the error response available in mock/error.json would be provided.

TargetEndpoint

Finally we only need to set that created Node.js application to be the target of the mock API proxy

mock/apiproxy/targets/node.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<TargetEndpoint name="node">
    <ScriptTarget>
        <ResourceURL>node://app.js</ResourceURL>
    </ScriptTarget>
</TargetEndpoint>

The structure described above can be used for any mock, the only thing to change would be the config.json file and the response payloads in the apiproxy/resources/node/mock directory.

Using the mock API proxy

Once the mock API proxy is ready and deployed in Apigee Edge, we just need to create the TargetEndpoint in the shop API proxy as shown below.

shop/apiproxy/targets/movies.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<TargetEndpoint name="movies">
    <PreFlow name="PreFlow">
        <Request>
            <Step>
                <Name>LookupCache.MoviesTarget.Basepath</Name>
            </Step>
            <Step>
                <Name>KeyValueMapOperations.Get.MoviesTarget.Basepath</Name>
                <Condition>lookupcache.LookupCache.MoviesTarget.Basepath.cachehit != "true"</Condition>
            </Step>
            <Step>
                <Name>PopulateCache.MoviesTarget.Basepath</Name>
                <Condition>lookupcache.LookupCache.MoviesTarget.Basepath.cachehit != "true"</Condition>
            </Step>
        </Request>
    </PreFlow>
    <Flows>
        <Flow name="Movies">
            <Request>
                <Step>
                    <Name>AssignMessage.Movies</Name>
                </Step>
            </Request>
            <Condition>request.verb = "GET" AND proxy.pathsuffix MatchesPath "/movies/*"</Condition>
        </Flow>
    </Flows>
    <HTTPTargetConnection>
        <LoadBalancer>
            <Server name="movies"/>
        </LoadBalancer>
        <Path>{movies.targetBasepath}</Path>
    </HTTPTargetConnection>
</TargetEndpoint>

Using the management API we create the target server "movies" and set it so it points to the OMDb API server.

curl -v -X POST -u {email}:{password} -H "Content-Type: application/json" -d '{
        "name" : "movies",
        "host" : "www.omdbapi.com",
        "port" : 80
    }' "https://api.enterprise.apigee.com/v1/organizations/{org_name}/environments/{env_name}/targetservers"

In addition to that we create a key value map called "movies" with an entry named "targetBasepath" to store the basepath to hit in the target server. For the OMDb API server it is equal to "/"

curl -v -X POST -u {email}:{password} -H "Content-Type: application/json" -d '{
          "name" : "movies",
          "entry" : [
                {
                    "name" : "targetBasepath",
                    "value" : "/"
                }
            ]
    }' "https://api.enterprise.apigee.com/v1/organizations/{org_name}/environments/{env_name}/keyvaluemaps"

Finally we create a cache called "movies" where the value of the key value map entry is cached for better performance.

curl -v -X POST -u {email}:{password} -H "Content-Type: application/json" -d '{
      "name" : "movies"
    }' "https://api.enterprise.apigee.com/v1/organizations/{org_name}/environments/{env_name}/caches"

If we want to switch to the mock at runtime, because the OMDb API server is unavailable, we only need to follow these steps:

  • Update the target server
    curl -v -X PUT -u {email}:{password} -H "Content-Type: application/json" -d '{
            "name" : "movies",
            "host" : "{org_name}-{env_name}.apigee.net",
            "port" : 80
        }' "https://api.enterprise.apigee.com/v1/organizations/{org_name}/environments/{env_name}/targetservers"
    	
  • Update the target basepath to "/mock" in the key value map
    curl -v -X POST -u {email}:{password} -H "Content-Type: application/json" -d '{
            "name" : "targetBasepath",
            "value" : "/mock"
        }' "https://api.enterprise.apigee.com/v1/organizations/{org_name}/environments/{env_name}/keyvaluemaps/movies/entries/targetBasepath"
    	
  • Invalidate the "movies" cache
    curl -v -X POST -u {email}:{password} "https://api.enterprise.apigee.com/v1/organizations/{org_name}/environments/{env_name}/caches/movies/entries?action=clear"
    	

Once OMDb becomes available, it is enough to re-run the same three steps listed before with the suitable values for the host and the basepath.

Comments
jonesfloyd
Staff

Hey @Miren - No need to have the doc team review, if that's why you added it to this forum. Feel free to move to the appropriate public (or internal) forum whenever you're ready. Also, if you want to future-proof your sample (for example if the omdb api isn't available or requires OAuth or something like what happened to the Yahoo! weather API), you can use our internal mock target, http://mocktarget.apigee.net. Thanks!

Not applicable

I would love to see the sample proxy bundle added to the edge samples on github too.

benrodriguez
New Member

This is great idea @Miren I was doing something similar but I was standing up AWS boxes. your mock api is much cleaner and faster. I'll be trying out tomorrow morning with some of my product managers.

edu4krishanu
New Member

Hi @Miren..Great Post...I was trying to use the project in my on prem environment. When I was hitting the url .../movies/itt2294629, I was getting back 500 internal server error.

Actually, it got error in the Mock proxy and the error is as below,

{"fault":{"faultstring":"Script node executed prematurely: Error: Cannot find module 'apimocker'\nError: Cannot find module 'apimocker'\n    at module.js:340\n    at module.js:280\n    at module.js:364\n    at require (module.js:380)\n    at \/organization\/environment\/api\/app.js:1\n    at module.js:456\n    at module.js:474\n    at module.js:356\n    at module.js:312\n    at module.js:497\n    at startup (trireme.js:142)\n    at trireme.js:923\n","detail":{"errorcode":"scripts.node.runtime.ScriptExitedError"}}}

Do i need to install apimocker node module in apigee servers?

Thanks,

rtalanki
New Member

This change fixed for me. Use devDependencies instead of Dependencies in apiproxy/resources/node/node_resources/package.json

{

"name": "mock",

"version": "1.0.0",

"description": "Mock",

"main": "app.js",

"devDependencies": {

"apimocker": "^0.5.0"

}

}

Not applicable

Curious to know if there are any mocking solutions that can be derived JUST from a swagger spec file? e.g. create the spec, and import it as API proxy (I think Apigee 127 let's us do something like this), however then I could have some API mock endpoint already created for me.

apistudio.io appears to be something that I'm looking for, but I don't think that is offered on-prem which I need.

Not applicable

Hi @Miren,

I tried the same example but getting the 400 error.

Please help me on this.mangement-api1.png

sidd-harth
Bronze 1
Bronze 1

Hi @swapna vellanki, please open a new post (Ask a Question) with your question for better solution.

Not applicable

Still I am getting this error. I tried with

"devDependencies": {

"apimocker": "^0.5.0"

}

{"fault":{"faultstring":"Script node executed prematurely: Error: Cannot find module 'apimocker'\nError: Cannot find module 'apimocker'\n    at module.js:340\n    at module.js:280\n    at module.js:364\n    at require (module.js:380)\n    at \/organization\/environment\/api\/app.js:1\n    at module.js:456\n    at module.js:474\n    at module.js:356\n    at module.js:312\n    at module.js:497\n    at startup (trireme.js:142)\n    at trireme.js:923\n","detail":{"errorcode":"scripts.node.runtime.ScriptExitedError"}}}
Not applicable

is this issue resolved ?

Version history
Last update:
‎07-07-2016 09:02 AM
Updated by: