Introduction
- Does your work depend on other's API?
- Does your work delay due to API unavailability?
- Are you interested in developing a Node utility?
If your answer to any of the above questions is YES, then this article is for you.
Background
Constant change in API breaks the endpoints and also consumes time for deployment. These changes in API delays frontend team's work. So I thought of using some API mocking tools, but all those tools have their own learning curve. This encouraged me to quickly create a simple and straightforward API mocking utility.
About the Utility
This utility helps developers to continue their task independently of API availability. A developer just needs to route all their API requests through this tool and it will cache the response and return mocked response in case of errors.
Uses
-
Install api-mock-proxy
npm install -g api-mock-proxy
-
Now, you can run your mock proxy as:
api-mock-proxy -p 8000 -t http://your.api.com
Now your proxy server is ready at http://localhost:8000. Just use this URL in all clients and you will get the response from http://your.api.com. The best part is, it will return the mocked response if the actual API is not working.
Available Options
-
port or -p: (default:8080)
- to specify the port to which mock server will listen
-
targetUrl or -t: (default:http://localhost:80)
- to specify the target URL where the mock server will forward all requests and return the response back. This will be URL for your actual API and the mock server will save the response for mocking when API responds with an error.
-
errorCodes or -e: (default:*)
- this will provide options for mocking on errors, error code will be provided in comma separated list, e.g., 404,500,ECONNREFUSED
The default value is *
, which means it will return a mocked response for any error unless not running in record mode.
mode or -m: (default:mix)
- It can take any of the three values mock
|record
|mix
mock
: mock all the requests, there is no need to call actual API record
: always return response from actual API and create a cache for future mocking mix
: call the actual API and return actual response except error code passed as mockedError
-
dataPath or -d: (default:./data.json)
- path for the mock data, it should be a valid data file. For the first time, just specify the path and mock will be created at that location. For running in mock mode, there must be pre-populated mock data.
-
--cors
- use this to enable cors header for all origin, methods and headers
-
allowOrigin
- provide a string
to be used in response header 'Access-Control-Allow-Origin
'
-
allowMethods
- provide a string
to be used in response header 'Access-Control-Allow-Methods
'
-
allowHeaders
- provide a string
to be used in response header 'Access-Control-Allow-Headers
'
-
sslKey
- provide a string
to be used as Key
for https server, https server will be created only if both key and certificates are provided
-
sslCert
- provide a string
to be used as Certificate for https server
-
keyFile
- provide pat for a Key file to be used for https server
-
certFile
- provide pat for Certificate file to be used for https server
About the Code
The code is written in simple steps listed below:
- Read user input or command line argument
- Create HTTP Server based on passed arguments
- Request target server
- Return actual or mocked response
Now we will understand this one by one.
Process Command Line Arguments
There are lots of NPM packages for processing command line arguments like yargs, commander, etc., but to keep minimum dependency, this utility is processing command line arguments without any third party library. Node provides all command line arguments in an array that can be processed easily. For example, a command api-mock-proxy -p 8000 -t http://your.api.com
will receive arguments in your program as:
[ 'C:\\Program Files\\nodejs\\node.exe',
'C:\Users\admin\AppData\Roaming\npm\node_modules\api-mock-proxy',
'-p',
'8000',
'-t',
'http://your.api.com' ]
Notice the first two elements of the array, these are the path of node
and, package
so the argument should start from the third element of the array.
Another important part of argument processing is providing default values, so for the same lodash is used, which is a utility library for all such tasks:
const options = _.defaults(getOptions(myArgs), defaults);
Creating Proxy Server
After processing all the arguments, we are ready to create a Proxy server based on provided options. A user can choose either to create HTTP
or HTTPS
server, which can be created by using native node modules for these tasks:
const createServer = options.ssl ? _.curry(https.createServer)(sslOptions) : http.createServer;
const server = createServer((req, res) => {
}
sslOptions
contains key and certificate for HTTPS
server.
After creating a server, an important part is to set response headers as provided in options via CORS
related headers or via --cors
flag.
Request to target Server
Now, the next step is to forward the request to the target server. If a user has not opted to receive a mocked response only by using -m mock
or --mode mock
. Each response from the server will be stored as mocked data for future use. The response will be saved against three keys, the first is a hash of the entire request object, the second is a hash of request without header and the third is a hash of request without header and body. This way, we can get matching mocked response minimum details:
const fullHash = getHash(req);
const withOutHeader = getHash({ ...req, headers: "" });
const minHash = getHash({ ...req, headers: "", body: "" });
Serving Response
The actual response to any request will depend on user preference. Target server response will be forwarded after saving it for future uses if a user is running on record mode (-m record
or --mode record
) or response without error in mix mode (-m mix
or --mode mix
or avoid providing this option). A mock response will be returned if a user is running on mock mode (-m mock
or --mode mock
) or target server responded with an error code specified by -e
404,500
,ECONNREFUSED
or --
errorCodes
*
.
To fetch mocked response from mocked data, the first step is to find a response for any of three hashed request keys created while saving request:
for (let i = 0; i < reqHash.length; i++) {
const item = reqHash[i];
const existing = data[item];
if (existing) {
if (existing.length == 0) {
return existing[0].res;
}
return findBestMatch(existing, reqObj);
}
}
If it returned multiple values for hash, then it will find the best match user using epsilonjs, which is an approximate string
matching library and the same will be used in the entire data if no match is found by hash:
const findBestMatch = (data, matchObj) => {
const diffIndex = data.map(item => {
return epsilon.leven(JSON.stringify(matchObj), JSON.stringify(item.req));
});
const minIndex = diffIndex.indexOf(Math.min(...diffIndex));
const mock = data[Math.max(minIndex, 0)];
return (mock || {}).res;
};
Points of Interest
- Creating utilities in nodejs is very simple.
- Add
#!/
usr
/bin/env node
as a first line in your main file (file mentioned in the main key of package.json) to make your node file executable. - Node passes the first two arguments as the path of node executable and executing package/file path.
History
The next important step for this utility is to provide UI to create mock data.
For future updates, feature requests and issues, please see on GitHub. If you want to contribute, please raise a PR and/or join the conversation on git.