Terence Bennett - February 18, 2016

DreamFactory provides the ability to create custom scripting services that can be invoked from the REST API. These can be written in JavaScript (V8js or Node.js) or PHP. You can use these services to implement business logic or combine multiple API calls into a single call. For example, if you have several databases you could access each one from the script and then combine the results as JSON or XML for return to the client.

Read more about DreamFactory’s new features.

This blog post presents an example of creating a custom scripting service named ‘Math’ that implements a simple calculator. I’ll show you how to create the service and then how to create the OpenAPI (Swagger 2.0) service definition for that service. The OpenAPI spec provides a portable service definition format and allows you to use the API Docs in the DreamFactory admin console to test and explore your service.

Prerequisites

You’ll need a DreamFactory instance running version 2.1.0 or later.  To check the version go to the Config tab in the admin console and select System Info.  Look for DreamFactory Version. Your instance needs to support server side scripting with V8js. The Bitnami installers support V8js scripting; our free hosted system does not.

 Create a New Custom Scripting Service

0.png

  • Go to the Services tab in the admin console.
  • Click Create and set Service Type to ‘Custom Scripting Service’.
  • Set the name to ‘math’. This will be part of the URL when you call the service.
  • Set the label to ‘Math’.
  • Click the Config tab and set the Scripting Engine Type to ‘V8js’.
  • Paste the following code into the Content text area, then click Create Service to save your new service.
var params, required, n1, n2, resource, result;

var lodash = require("lodash.min.js");

if (event.request.method !== "GET") {
    throw("Only HTTP GET is allowed on this service.");
}

// get query params from request
params = event.request.parameters;

// get resource, /math —> "", /math/add —> "add"
resource = event.resource;
if (resource !== "") {
    // check for required params
    required = ["n1","n2"];
    lodash._.each( required, function( element ) {
        if (!params.hasOwnProperty(element) || !params[element]) {
            throw( "Missing '" + element + "' in params\n" );
        }
    });
    n1 = Number(params.n1);
    n2 = Number(params.n2);
}

switch (resource) {
    case "":
        // /math means return all supported resources
        result = {"resource": ["add", "subtract", "multiply", "divide"]};
        break;
    case "add":
        result = {"result": n1 + n2};
        break;
    case "subtract":
        result = {"result": n1 - n2};
        break;
    case "multiply":
        result = {"result": n1 * n2};
        break;
    case "divide":
        if (!n2) {
            throw("Divide by zero error.");
        }
        result = {"result": n1 / n2};
        break;
    default:
        throw("Invalid or missing resource name.");
        break;
}

return result;

 Now you can call this service from the REST API. Using your REST client of choice, make the following GET request. Replace everything before /api/v2 with your instance base URL. You can use HTTP basic auth or pass an authenticated session token in the API call.

GET https://127.0.0.1:8080/api/v2/math

For example, with cURL:

curl -i -k -3 -X GET "https://127.0.0.1:8080/api/v2/math" 
-H "X-DreamFactory-Session-Token: your-session-token"

 The URL ends with the service name, ‘math’. Since there is no resource specified like add or subtract, the scripting service will return a list of available resources.

{
    "resource": [
		"add",
		"subtract",
		"multiply",
		"divide"
	]
}

 To add two numbers, make the following request. Now the URL also includes the resource ‘add’.

GET https://127.0.0.1:8080/api/v2/math/add?n1=3&n2=5

 The script returns the sum of n1 and n2 in JSON format.

{
    "result": 8
}

 The script checks to make sure both n1 and n2 are specified. If not, an exception is thrown and an error returned to the client. This logic is implemented in the script for the service. How you design and code your services is completely up to you. Here’s an example of an invalid request that is missing n2.

GET https://127.0.0.1:8080/api/v2/math/add?n1=3
{
    "error": {
		"context": null,
		"message": "Missing 'n2' in params\n",
		"code": 500
	}
}

Creating a Swagger Service Definition

To turn up the awesomeness on your service you can create a Swagger service definition for it. This defines the inputs and outputs for the service and allows you to explore the service from the API Docs in the DreamFactory admin console, or any other Swagger-compliant tool. The first step is to go to the Swagger editor and click the ‘try the live demo‘ link. Paste the following YAML into the editor. We provided this one for you for the Math service but you would build the YAML for your own services using this same editor.

swagger: '2.0'

info:
  version: "2.0"
  title: "Math Service"
  
parameters:
  n1:
    name: n1
    in: query
    description: First number
    required: true
    type: number
    format: integer
  n2:
    name: n2
    in: query
    description: Second number
    required: true
    type: number
    format: integer
  
paths:
  /math:
    get:
      tags:
      - math
      description: Resources available to this service.
      responses:
        '200':
          description: Success
          schema:
            $ref: '#/definitions/MathResourceList'
        default:
          description: Error
          schema:
            $ref: '#/definitions/MathError'
  /math/add:
    get:
      tags:
        - math
      description: Add two numbers.
      parameters:
        - $ref: "#/parameters/n1"
        - $ref: "#/parameters/n2"
      responses:
        '200':
          description: Success
          schema:
            $ref: '#/definitions/MathSuccess'
        default:
          description: Error
          schema:
            $ref: '#/definitions/MathError'
  /math/subtract:
    get:
      tags:
        - math
      description: Subtract two numbers.
      parameters:
        - $ref: "#/parameters/n1"
        - $ref: "#/parameters/n2"
      responses:
        '200':
          description: Success
          schema:
            $ref: '#/definitions/MathSuccess'
        default:
          description: Error
          schema:
            $ref: '#/definitions/MathError'
  /math/multiply:
    get:
      tags:
        - math
      description: Multiply two numbers.
      parameters:
        - $ref: "#/parameters/n1"
        - $ref: "#/parameters/n2"
      responses:
        '200':
          description: Success
          schema:
            $ref: '#/definitions/MathSuccess'
        default:
          description: Error
          schema:
            $ref: '#/definitions/MathError'
  /math/divide:
    get:
      tags:
        - math
      description: Divide two numbers.
      parameters:
        - $ref: "#/parameters/n1"
        - $ref: "#/parameters/n2"
      responses:
        '200':
          description: Success
          schema:
            $ref: '#/definitions/MathSuccess'
        default:
          description: Error
          schema:
            $ref: '#/definitions/MathError'
definitions:
  MathResourceList:
    type: object
    properties:
      resource:
        type: array
        description: Array of accessible resources available to this service.
        items:
          type: string
  MathSuccess:
    type: object
    properties:
      result:
        type: integer
  MathError:
    type: object
    properties:
      code:
        type: integer
        format: int32
        description: Error code.
      message:
        type: string
        description: String description of the error.

Once the YAML is correct you should export it as Swagger JSON from the menu option Generate Client –> Swagger JSON. The output should look like this.

{
    "swagger": "2.0",
	"info": {
		"version": "2.0",
		"title": "Math Service"
	},
	"paths": {
		"/math": {
			"get": {
				"tags": ["math"],
				"description": "Resources available to this service.",
				"parameters": [],
				"responses": {
					"200": {
						"description": "Success",
						"schema": {
							"$ref": "#/definitions/MathResourceList"
						}
					},
					"default": {
						"description": "Error",
						"schema": {
							"$ref": "#/definitions/MathError"
						}
					}
				}
			}
		},
		"/math/add": {
			"get": {
				"tags": ["math"],
				"description": "Add two integers.",
				"parameters": [{
					"name": "n1",
					"in": "query",
					"description": "First number",
					"required": true,
					"type": "number",
					"format": "integer"
				}, {
					"name": "n2",
					"in": "query",
					"description": "Second number",
					"required": true,
					"type": "number",
					"format": "integer"
				}],
				"responses": {
					"200": {
						"description": "Success",
						"schema": {
							"$ref": "#/definitions/MathSuccess"
						}
					},
					"default": {
						"description": "Error",
						"schema": {
							"$ref": "#/definitions/MathError"
						}
					}
				}
			}
		},
		"/math/divide": {
			"get": {
				"tags": ["math"],
				"description": "Divide two integers.",
				"parameters": [{
					"name": "n1",
					"in": "query",
					"description": "First number",
					"required": true,
					"type": "number",
					"format": "integer"
				}, {
					"name": "n2",
					"in": "query",
					"description": "Second number",
					"required": true,
					"type": "number",
					"format": "integer"
				}],
				"responses": {
					"200": {
						"description": "Success",
						"schema": {
							"$ref": "#/definitions/MathSuccess"
						}
					},
					"default": {
						"description": "Error",
						"schema": {
							"$ref": "#/definitions/MathError"
						}
					}
				}
			}
		},
		"/math/multiply": {
			"get": {
				"tags": ["math"],
				"description": "Multiply two integers.",
				"parameters": [{
					"name": "n1",
					"in": "query",
					"description": "First number",
					"required": true,
					"type": "number",
					"format": "integer"
				}, {
					"name": "n2",
					"in": "query",
					"description": "Second number",
					"required": true,
					"type": "number",
					"format": "integer"
				}],
				"responses": {
					"200": {
						"description": "Success",
						"schema": {
							"$ref": "#/definitions/MathSuccess"
						}
					},
					"default": {
						"description": "Error",
						"schema": {
							"$ref": "#/definitions/MathError"
						}
					}
				}
			}
		},
		"/math/subtract": {
			"get": {
				"tags": ["math"],
				"description": "Subtract two integers.",
				"parameters": [{
					"name": "n1",
					"in": "query",
					"description": "First number",
					"required": true,
					"type": "number",
					"format": "integer"
				}, {
					"name": "n2",
					"in": "query",
					"description": "Second number",
					"required": true,
					"type": "number",
					"format": "integer"
				}],
				"responses": {
					"200": {
						"description": "Success",
						"schema": {
							"$ref": "#/definitions/MathSuccess"
						}
					},
					"default": {
						"description": "Error",
						"schema": {
							"$ref": "#/definitions/MathError"
						}
					}
				}
			}
		}
	},
	"definitions": {
		"MathResourceList": {
			"type": "object",
			"properties": {
				"resource": {
					"type": "array",
					"description": "Array of accessible resources available to this service.",
					"items": {
						"type": "string"
					}
				}
			}
		},
		"MathSuccess": {
			"type": "object",
			"properties": {
				"result": {
					"type": "integer"
				}
			}
		},
		"MathError": {
			"type": "object",
			"properties": {
				"code": {
					"type": "integer",
					"format": "int32",
					"description": "Error code."
				},
				"message": {
					"type": "string",
					"description": "String description of the error."
				}
			}
		}
	},
	"parameters": {
		"n1": {
			"name": "n1",
			"in": "query",
			"description": "First number",
			"required": true,
			"type": "number",
			"format": "integer"
		},
		"n2": {
			"name": "n2",
			"in": "query",
			"description": "Second number",
			"required": true,
			"type": "number",
			"format": "integer"
		}
	}
}

Open the downloaded zip file and copy the JSON from swagger.json. Paste it into the DreamFactory service definition editor and save it as part of your service. After that you can explore the service using the API Docs tab in the DreamFactory admin console. Here are the detailed steps.

  • Go to the Services tab in the admin console.
  • Click the Manage tab then select your custom scripting service ‘Math’ from the list.
  • Click the Service Definition tab from the service editor.
  • Paste the JSON into the editor.
  • Click Update Service to save your changes.
  • Go to the API Docs tab in the admin console.
  • You should see the Math service in the list.
  • Click to expand, and you’ll see the list of APIs for the service.

1.png

To get a list of resources, click the first GET button then click Try it out!. It will show you the request URL and response data. To add two numbers click the GET button next to /math/add. Enter your two required numbers, n1 and n2, then click Try it out!. You get the same result as when using a REST client or calling from your app, but now you are using the UI that was automatically built from the Swagger definition that you created.

Now you can write your app that calls this service and use roles to control access to the service. To recap, we created a new custom scripting service and called it from a REST client to prove it was working. Then we created a Swagger service definition that allows us to explore the service from the API Docs UI. Finally, we used that UI to call the service. The custom scripting service and its service definition are portable and can be easily moved to other DreamFactory instances. We have more examples in the works, but this one should get you started creating and documenting your own custom scripting services with DreamFactory.

Read more about DreamFactory 2.1:

DreamFactory 2.1 lets you put some Swagger in your custom scripting services

DreamFactory 2.1 released, includes OpenAPI spec upgrade

DreamFactory 2.1.1 released, includes Angular 2 and React apps

DreamFactory 2.1.2 released, includes app packages

DreamFactory 2.10 adds OpenAPI 3.0 and Admin App RBAC

DreamFactory 2.11 adds GraphQL and RabbitMQ

DreamFactory 2.12 adds Bitbucket Support for Git Services