Database Table Endpoint Obfuscation Through Custom Scripting

DreamFactory’s power lies in the automatic generation of API endpoints and its standardization. There are times however, where you need something a little more custom, and that’s why we have scripting engines included. Let’s look at a specific use case for custom scripting that we have users ask about a lot.



Let’s say your database was built without user friendliness in mind, because it was not originally meant to be exposed this way. Perhaps the database was named appprod01data and all your customer info is in a table called appdata_custinfo04. I sense some raised eyebrows, but this is way more common than you would think. Normally, in DreamFactory, you would access this customer data using the endpoint https://myapi.com/api/v2/appprod01data/_table/appdata_custinfo04, using various HTTP request verbs to access and manipulate the data. It’s a bit long and not terribly descriptive. What if we wanted to shorten this to simply https://myapi.com/api/v2/customers?  Well, that’s what I’m going to show you how to do with this custom script.

First step, make sure your appprod01data database is connected as a DreamFactory service, following the standard procedure.

Next, we want to create a new custom script service.

  • Go to Services, and click on Create.
  • Under Service Type, select Script->V8js

Screen Shot 2016-12-21 at 10.53.43 AM.png

  • Name the service customers and give it a label and description of your choosing.
  • In the Config tab, paste the script below and save the service.

Here is the script we want to use for this functionality. I’ll post the whole thing and then walk through its parts.

    var api_path = 'appprod01data/_table/appdata_custinfo04';
var method = event.request.method;
var options = {};
options.parameters = event.request.parameters;
var result;

if (event.resource && event.resource !== '') {
api_path = api_path + '/' + event.resource;
}

if (event.request.payload) {
var payload = event.request.payload;
} else {
var payload = null;
}

switch (method) {
case 'GET':
result = platform.api.get(api_path, null, options);
break;
case 'POST':
result = platform.api.post(api_path, payload, options);
break;
case 'PUT':
result = platform.api.put(api_path, payload, options);
break;
case 'PATCH':
result = platform.api.patch(api_path, payload, options);
break;
case 'DELETE':
result = platform.api.delete(api_path, payload, options);
break;
default:
result = {"message":"Cannot interpret this call. Invalid verb."};
break;
}

return result;

Script Source.

Let’s look at the pieces.

    var api_path = 'appprod01data/_table/appdata_custinfo04';
var method = event.request.method;
var options = {};
options.parameters = event.request.parameters;
var result;

Here, we are setting the real api path, the one we’re obfuscating with the script. Then, we get the HTTP verb being used so that we can know which function to use below. We then establish the options object and add the incoming parameters. This is so we can pass any parameters in the original call (such as filters or limits) on to the real api call. Lastly, we establish the result variable which will be used to store the result of the real api call and returned to the client.

    if (event.resource && event.resource !== '') {
api_path = api_path + '/' + event.resource;
}

In this conditional block we check to see if there were additional resources included in the request. If there are, we add them to them to the real api call path. For example if I wanted a specific record from the table I would have called customers/45 to get record with ID 45. We need to pass this on to the real api call as well.

    if (event.request.payload) {
var payload = event.request.payload;
} else {
var payload = null;
}

In this conditional block we check to see if a payload was included in the original call. If yes it is stored in the payload variable. If not, payload is set to null.

Now the guts of the real api calls.

    switch (method) {
case 'GET':
result = platform.api.get(api_path, null, options);
break;
case 'POST':
result = platform.api.post(api_path, payload, options);
break;
case 'PUT':
result = platform.api.put(api_path, payload, options);
break;
case 'PATCH':
result = platform.api.patch(api_path, payload, options);
break;
case 'DELETE':
result = platform.api.delete(api_path, payload, options);
break;
default:
result = {"message":"Cannot interpret this call. Invalid verb."};
break;
}
return result;

Here we use JavaScript case switching to check with verb to use. Based on the original call verb we use platform.api to call that same verb on the real api path. Payloads and parameters are included. In the rare event that you somehow managed to send a different verb, the script returns an error.

This script should accommodate all standard DreamFactory sql table use cases, and you access them the same way, just with your shortened friendly endpoint.

A few examples:

You want to get the customer record with id 1776 but only the id and name fields:

GET https://myapi.com/api/v2/customers/1776?fields=id,name

Perhaps you want to get all customers whose name starts with ‘Bo’ but only want 5 records:

GET https://myapi.com/api/v2/customers?filter=(name%20LIKE%20'Bo%')&limit=5

Or maybe you need to change the name on record 1776:

PATCH https://myapi.com/api/v2/customers/1776

    Payload:
{
"name":"Samuel Adams"
}

You get the idea. All of your normal sql table operations are available in their usual ways, only we’ve managed to make the endpoint short and user friendly.

This script will be posted to the DreamFactory scripts repo. It is only compatible with DreamFactory 2.4 and higher. A version of the script that is compatible with older v2 releases can be found in the repo as well.

Related reading: Building a Healthcare App with Auto Generated APIs from SQL and NoSQL Databases