Cloud Software Group, Inc. EBX®
Documentation > Developer Guide > EBX® Script IDE > Tutorials
Navigation modeDocumentation > Developer Guide > EBX® Script IDE > Tutorials

Creating scripted REST services tutorial

Introduction to scripted REST services

A scripted REST service is the combination of a REST endpoint definition and its related script. Scripts handle programmatic actions for REST services and are written in the EBX® Script language. REST service definitions created in the EBX® Script IDE conform to a subset of the OpenAPI Specification (OAS). This allows you to import and export definitions that conform the OAS v3 standard.

During implementation use the options in the UI to define:

Each REST service script can include multiple functions to handle logic for multiple endpoints and REST methods. After implementing and publishing the scripted REST service, it is accessible by an external consumer. Note that only JSON requests and responses are supported.

How this tutorial is organized

This tutorial shows how to create and publish scripted REST services in the EBX® Script IDE. It does not cover complex use cases, or all of the user interface options.

The parts that make up this tutorial are:

Prerequisites

Before beginning this tutorial take a moment to review the following:

Understanding the anatomy of a scripted REST service

There are 2 main components to a scripted REST service:

Attention

The bridge between the REST API and script is the Operation id. You must use the same value for the function name in your script as for the Operation id property when defining the REST API. It's this Id that allows EBX® to know what function to call when an endpoint is accessed.

Whats next? create and publish the tutorial's data model.

Tutorial data model

To follow along, you can create the basic data model shown below:

/RESTserviceTutorialModel.png

Whats next? create and test a scripted REST service.

Creating a scripted REST service

The goal of this scripted REST service is to create an endpoint and its related script that returns a list of user records from our Users table.

  1. If the EBX® Script IDE perspective is not open, click the /uiPerspectiveIcon.png icon at the top right of the EBX® toolbar and select Script IDE.

  2. In the Dock on the left of the screen locate and select REST service.

  3. Click the + icon to create a new script and after providing a name, save the script.

  4. Create the REST path and operation:

    1. Select the API tab and create a new path.

    2. Enter /users for the Path template name and click to confirm.

      Note

      Although not shown here, use curly brackets separated by forward slashes to add parameters. For example: /somePath/{param1}/{param2}.

    3. Create a new operation under the GET tab.

    4. Enter an Operation id, or optionally select Generate to automatically create an id. For this tutorial, we'll use the value getUsers.

      Attention

      The Operation id serves as the map between this endpoint and its corresponding script function.

    5. Save the API.

  5. Build a data type for the operation response:

    1. Select the DATA TYPES tab.

    2. Add a new Data type with the name Person.

    3. Add the following properties that will be mapped to data model fields in the script:

      • Name: id, Type: decimal

      • Name: firstName, Type: string

      • Name: lastName, Type: string

    4. Save the data type.

  6. Add the data type to the REST response:

    1. Open the API DEFINITION tab.

    2. Expand the /users path.

    3. In the Responses group, choose Person from the Type menu and tic the Is Array box.

    4. Save the API.

  7. Create the script:

    1. On the right side of the Operation id field, click the SCRIPT button to automatically generate a function template with the same name as the operation id.

      The generated function is shown below:

      export function getUsers(): rest_response
      begin
      //Replace the following code with your implementation.
      
        var object1 := instanceOf<User>();
        //Set object1 properties.
        var object2 := instanceOf<User>();
        //Set object2 properties.
      
        var content := list.of(object1, object2);
        return rest.responseOf(200, content);
      end
    2. Before writing the logic for our function, create helper methods to retrieve the dataspace and dataset where we want this script to run. Note that in the following example, you'll need to provide the names of your dataspace and dataset, if different from Reference and userDirectory, respectively:

      uses core.data as data;
      
      //Helpers to retrieve a specific dataspace and data set
      function getDirectoryDataspace(): dataspace
      begin
      	return data.findDataspace('Reference');
      end
      
      function getDirectoryDataset(dataspace: dataspace): dataset
      begin
      	return data.findDataset(dataspace, 'userDirectory');
      end
    3. Write a script to handle the service's business logic.

      The following sample is annotated to describe the function's implementation:

      //Import core units
      //The core.rest unit methods are used in REST services
      uses core.rest as rest;
      //The core.list unit methods enable creating and updating lists
      //We need this as the return type we specified requires an Array
      uses core.list as list;
      //The core.data unit provides database CRUD operations
      uses core.data as data;
      
      //Create a model reference
      //Be sure to supply your model's name, ours is "userDirectory"
      references model "userDirectory" as dir;
      
      export function getUsers(): rest_response
      begin
          //Set the dataset location using the helper methods
      	const directory := getDirectoryDataset(getDirectoryDataspace());
      	
          //Create the SQL query to retrieve the desired response data
          //Be sure to use the correct field and table names defined in your data model
      	var query := sql `select u.id, u.firstName, u.lastName from dir."users" u order by u.id`;
          //Add the dataset to the query
      	data.addDataset(query, 'dir', directory);
      	
          //Create a list for the 'Person' data type
      	var listOfUsers := list.of<Person>();
      	
          //Create a variable to store the query result
      	var queryResult := data.executeQuery(query);
          //Loop over, and add query results to the 'listOfUsers'
      	for tuple in queryResult do
      	begin
              //Create an Object of type 'Person'
      		var user := instanceOf<Person>();
              //Map each Object property with the corresponding query result
      		user.id := tuple.id;
      		user.firstName := tuple.firstName;
      		user.lastName := tuple.lastName;
      		
              //Add each user to the list
      		list.add(listOfUsers, user);
      	end;
      	
          //Return the optional REST response code and the list of users
      	return rest.responseOf(200, listOfUsers);
      end
  8. Save the script. The IDE automatically checks for any compilation errors and will display them in the Messages section below the script.

  9. If no errors are found, select PUBLISH.

  10. Test the service using the Script IDE's built-in testing capabilities. Alternatively, you can test using a tool such as Postman:

    1. In the upper-right click TEST API.

    2. Select the API to test, users in our case.

    3. Click Test Request and then Send in next screen that displays.

      As shown below, the response Body should include all records in the users table:

      /RESTserviceTutorialTestResponse.png

Whats next? Build on what you've learned in the tutorial by creating additional scripted CRUD operations.

Example scripted REST services for CRUD operations

This section contains examples of scripted REST services for various CRUD operations. Each example includes the path, REST methods, operation ids, and data types used. The samples build on the above tutorial and are meant to demonstrate how to implement some common uses cases. Be sure to review the Examples prerequisites before testing these examples.

Examples prerequisites

If you want to use these examples with your existing data, you'll need to update the sample code with the appropriate data model, dataspace, and dataset information. To use the sample code as it is set up your environment as follows:

Data model

The sample code is based on the same data model used in the tutorial. This is reflected in any referenced data model component naming in the code samples.

Data types

Ensure you have the following types create under the DATA TYPES tab:

Data types

Properties

Person
  • id of type decimal

  • firstName of type string

  • lastName of type string

Error

message of type string

Shared code template

The following code is required for all of the following examples. It includes required core units, data model reference, and two helper functions. Later examples reference this template and show only the code that is specific to each operation.

//Required core units
uses core.rest as rest;
uses core.list as list;
uses core.data as data;
uses core.complex as cp;

//Specifies the data model as 'userDirectory'
references model "userDirectory" as dir;


//Helper to retrieve the 'Reference' dataspace
function getDirectoryDataspace(): dataspace
begin
	return data.findDataspace('Reference');
end

//Helper to retrieve the 'directoryDataset' dataset
function getDirectoryDataset(dataspace: dataspace): dataset
begin
	return data.findDataset(dataspace, 'directoryDataset');
end

Create a user

Use the following API definition and script to create a user.

API definition

Path

/users

Path parameters

N/A

REST Method

POST

Operation id:

createUser

Query parameter

N/A

Body

Type: Person

Required: Yes

Responses:

Type: Person

Script

The following script includes 2 functions, 1 procedure, and does the following:

export function createUser(const user: Person): rest_response
begin
	ref response: rest_response;
	
	execute transaction tr on getDirectoryDataspace() as system
	begin
		response := doCreateUser(tr, user);
	end
	
	return response;
end

function doCreateUser(tr: transaction, user: Person): rest_response
begin
	
	const directory := getDirectoryDataset(tr.dataspace);
	const table := data.findTable<dir.users>(directory);
	
	var pk := cp.primaryKeyOf<dir.users>();
	pk.id := user.id;
	
	var record := data.lookupRecord(table, pk);
	if isNotNull(record) then
	begin
		var error := instanceOf < Error > ();
		error.message := 'User "' | user.id | '" already exists';
		return rest.responseOf(400, error);
	end
	
	tr.isAllPrivileges := true;
	createOrUpdateUser(tr, user.id, user.firstName, user.lastName);
	
	return rest.responseOf(201, user);
end

procedure createOrUpdateUser(tr: transaction, id: int, firstName: string, lastName: string)
begin
	const directory := getDirectoryDataset(tr.dataspace);
	const table := data.findTable<dir.users>(directory);
	
	var pk := cp.primaryKeyOf<dir.users>();
	pk.id := id;
	var user := data.lookupRecordForUpdate(tr, table, pk);
	if isNull(user) then
	begin
		user := data.recordOf(tr, table);
		user.id := id;
	end
	
	user.firstName := firstName;
	user.lastName := lastName;
	data.saveRecord(user);
end

Getting a user by id

Use the following API definition and script to return a user using their id.

API definition

Path

/users/{id}

Path parameters

Name: id

Type: integer

REST Method

GET

Operation id:

getUser

Responses:

Type: Person

Is Array: checked

Script

The following script queries the database for a user, and returns an error message if that user is not found.

export function getUser(id: int): rest_response
begin
	const directory := getDirectoryDataset(getDirectoryDataspace());
	
	var query := sql `select u.id, u.firstName, u.lastName from dir."users" u where u.id = ?`;
	data.addDataset(query, 'dir', directory);
	data.setParameter(query, 0, id);
	
	var tuple := data.fetchOne(query);
	if isNull(tuple) then
	begin
		var error := instanceOf <Error>();
		error.message := 'User not found';
		return rest.responseOf(404, error);
	end
	
	var user := instanceOf<Person>();
	user.id := tuple.id;
	user.firstName := tuple.firstName;
	user.lastName := tuple.lastName;
	
	return rest.responseOf(200, user);
end

Update a user

Use the following API definition and script to update a user.

API definition

Path

/user

Path parameters

N/A

REST Method

PUT

Operation id:

updateUser

Query parameter

N/A

Body

Type: Person

Required: Yes

Responses:

None. Check No Response Body

Script

The following script includes 2 functions and does the following:

export function updateUser(const user: Person): rest_response
begin
	ref response: rest_response;
	
	execute transaction tr on getDirectoryDataspace() as system
	begin
		response := doUpdateUser(tr, user);
		
	end
	
	return response;
end

function doUpdateUser(tr: transaction, user: Person): rest_response
begin
	const directory := getDirectoryDataset(tr.dataspace);
	const table := data.findTable<dir.users>(directory);
	
	var pk := cp.primaryKeyOf<dir.users>();
	pk.id := user.id;
	
	var record := data.lookupRecordForUpdate(tr, table, pk);
	if isNull(record) then
	begin
		var error := instanceOf < Error > ();
		error.message := 'User "' | user.id | '" not found';
		return rest.responseOf(404, error);
	end
	
	record.firstName := user.firstName;
	record.lastName := user.lastName;
	data.saveRecord(record);
	
	return rest.responseOf(200, user);
end

Deleting a user by id

Use the following API definition and script to delete a user using their id.

API definition

Path

/users/{id}

Path parameters

Name: id

Type: integer

REST Method

DELETE

Operation id:

deleteUser

Responses:

No Response Body: checked

Script

The following script queries the database for a user, and deletes them if the user is found.

export function deleteUser(const id: int): rest_response
begin
	execute transaction tr on getDirectoryDataspace() as system
	begin
		const directory := getDirectoryDataset(tr.dataspace);
		const table := data.findTable<dir.users>(directory);
		
		var pk := cp.primaryKeyOf<dir.users>();
		pk.id := id;
		
		var record := data.lookupRecord(table, pk);
		if isNotNull(record) then
		begin
			data.deleteRecord(tr, record);
		end
	end
	
	return rest.emptyResponse(204);
end
Documentation > Developer Guide > EBX® Script IDE > Tutorials