Skip to main content
Skip table of contents

Model Services - REST

In this lesson, you will learn about Model Services.  This is the automatic REST interface to the business model. First read about the details of how to make requests to Model Services, then we will do some activities.

Model Services Syntax

Documentation for the JSON version of the REST interface may be accessed at <serverRoot>/openAPI.html. It also gives you the opportunity to try it out. For example: http://localhost:7080/training/openAPI.html.

Model Services provide API access to the Business Model for REST style create, read, update, delete, and invoke.  They are available at <serverRoot>/<format>/<objectType>/<id>?<parameters> along with an HTTP Method to indicate the CRUD (create, read, update, delete) verb. There are a number of <format> endpoints.

  • rest/json - (recommended) the current standard JSON endpoint
  • json - legacy JSON endpoint - can be made to behave like the rest/json endpoint through querystring parameters but uses older standards by desfault. For example, it will handle enumerations by object rather than by value.
  • rest/xml - the current standard XML endpoint
  • xml - legacy XML endpoint
  • web - SOAP endpoint
  • api/... - versioned apis as described in Model Services Versioning

For example, to use the rest/json endpoint to retrieve (read) all contacts with a last name of Lamont and display their first name, last name, and addresses in JSON, issue the following GET request in the address bar of your browser:

http://localhost:7080/nexj/rest/json/Person?$indent&$syntax=js&attributes=firstName lastName addrs.city addrs.state addrs.zip&where=lastName == "Lamont"

[
   {
      "$class": "Person",
      "$oid": "101523C22B0D234FEBA0609E945E0D3834",
      "lastName": "Lamont",
      "addrs": [
         {
            "$class": "Address",
            "$oid": "100ACC40CA4EA44D09BE5BBF6D1C221847",
            "state": "ON",
            "zip": "M2M 2S9",
            "city": "Toronto"
         },
         {
            "$class": "Address",
            "$oid": "108AB688E1BC7841BB90313D64E429792B",
            "state": "ON",
            "zip": "M4N 1S5",
            "city": "Toronto"
         }
      ],
      "firstName": "Tim"
   },
   {
      "$class": "Person",
      "$oid": "102D841BEB687949C68FAFE87289EEB1BA",
      "lastName": "Lamont",
...
]

$class and $oid

The $class and $oid attributes of an object are always returned.

They are the class name of the object and unique identifier respectively.

Request Structure

Model Services are available at <serverRoot>/<format>/<objectType>/<id>?<parameters> along with an HTTP method to indicate the CRUD verb.  

serverRoot 

The host and context root of the server. For example: http://localhost:7080/training

This is defined in your environment file in the httpURL property.

format 

Defines the return type for the request.  Formats supported include "rest/json", "rest/xml", "json", "xml", and "web" for a SOAP encoding. These endpoints all support the REST style requests documented here. All examples in this documentation use the json format.  To see the effect of another protocol on the example output, replace json with xml or web. There are additional supported protocols, including soap and text, but they use a different API style called Generic RPC and are not covered here.  

objectType 

Specifies the object type to work on, such as Person, Household, or Account. The object's namespace is required if it has one. For example: rest/json/ilm:Lead?$indent

id 

An optional unique identifier for the item to work on.  If there is no id in the URL, the request will work on a collection. If there is an id in the URL, the request will work on a specific instance. For example: http://localhost:7080/training/json/Person/101DBCCB4F0B4E4345BDD1C68ECE20DBBD?$indent

parameters

The following parameters employ the same syntax as other object queries. That is, they use the same syntax that is used when creating a script for a read operation.

  • attributes
  • where
  • orderby
  • count
  • offset

A simplified syntax is available if $syntax=js is included as a parameter. This is the default on the rest/json and rest/xml endpoints.

$indent 

Can improve the readability of the output.

The $indent parameter is included in many of the following examples. It is only for readability and is not required.

$syntax 

$syntax=js uses a simplified syntax for attributes and where clauses. It is the default on the rest/json and rest/xml endpoints.

$syntax=scheme uses scheme syntax as documented in the Class'read event.

attributes

If you don't specify an "attributes" parameter, all public primitive attributes are returned. If you do specify a list of primitive attributes in the attributes parameter, then only those attributes will be returned along with the message type and oid (unique identifier), which are always returned.

If no default $syntax is set on an endpoints component with, for example, <Property name="syntax">js</Property> then, for backward compatibility, outer brackets are required and "dot" notation is not supported. For example, attributes=(firstName lastName (addrs city (type name))) is valid

If either $syntax=js or $syntax=scheme is specified, then the outer brackets are not required and a mix of dot notation or brackets are supported. For example,

rest/json/Person?$indent&attributes=firstName lastName genderCode returns:

[
   {
      "$class": "Person",
      "$oid": "10DB4B54A113294254B68D6083376E34E9",
      "lastName": "Lamont",
      "firstName": "Dionne",
	  "genderCode": "F"    
   }, 
	....
]

Associated objects can also be retrieved using nested lists in the attributes parameter. To do this, specify the non-primitive attribute name followed by a list of one or more attributes to retrieve. For example:

rest/json/Person?$indent&attributes=firstName lastName (addrs city state zip) returns:


[
   {
      "$class": "Person",
      "$oid": "100BFAA7399522406195DE457CD94C519B",
      "lastName": "Lamont",
      "addrs": [
         {
            "$class": "Address",
            "$oid": "1090704C0AA3A04A539FDF1B31B10798E2",
            "state": "ON",
            "zip": "M2M 2S9",
            "city": "Toronto"
         },
         {
            "$class": "Address",
            "$oid": "10C4317637F4AE432993BD488C1FC7D563",
            "state": "ON",
            "zip": "M5G 2L3",
            "city": "Toronto"
         }
      ],
      "firstName": "Tim"
   },
...

The attribute clause could also be written with "dot" notation as attributes=firstName lastName addrs.city addrs.state addrs.zip

Dot notation is convenient and familiar, but if you are addressing many sub-attributes the bracket notation is less typing. Compare:

attributes=firstName lastName addrs.city addrs.state addrs.zip addrs.type.name

attributes=firstName lastName (addrs city state zip (type name))

If a complex attribute itself has a complex attribute, then these nestings can continue. For example:

firstName lastName (addrs city state zip (type name))

Attributes can also be retrieved polymorphically. That is, attributes that only exist on subclasses can be still retrieved from reading the base class. (see the examples below)

Attribute names in the where and order by parameters may take the simple form <attributeName> or alternatively (@ <attributeName>). The second form, using @ notation, is recommended. In this case, the "@" represents the current instance and may be used to traverse arbitrarily long association paths. For example, on a User you might specify:

(= (@ person homeAddress city) "Toronto") 

Example response
[{
      "$class": "Person",
      "$oid": "10031C7A7EF46D45ADBC72090BCCD04825",
      "lastName": "Cotherman",
      "addrs": [
         {
            "$class": "Address",
            "$oid": "10EE388953279847C6B7DB63CACAB774C0",
            "state": null,
            "zip": null,
            "type": {
               "$class": "AddressType",
               "$oid": "1000000000000010008000CEEDED004000",
               "name": "Business"
            },
            "city": null
         }
      ],
      "firstName": "Joseph",
      "initials": null
   }
]

Multiple chaining is supported in where clauses. So, in an "js" clause, both of the following options are valid and equivalent:

(@ person homeAddress city)

where=person.homeAddress.city == "Toronto" syntax

To traverse association paths, you can wrap associated objects in brackets and then list their primitive and associated attributes.  This can be done recursively. For example:

http://localhost:7080/training/json/Person?attributes=(firstName lastName initials (addrs city state zip (type name)))&$indent results in:

Example response
{
      "$class": "Person",
      "$oid": "10031C7A7EF46D45ADBC72090BCCD04825",
      "lastName": "Cotherman",
      "addrs": [
         {
            "$class": "Address",
            "$oid": "10EE388953279847C6B7DB63CACAB774C0",
            "state": null,
            "zip": null,
            "type": {
               "$class": "AddressType",
               "$oid": "1000000000000010008000CEEDED004000",
               "name": "Business"
            },
            "city": null
         }
      ],
      "firstName": "Joseph",
      "initials": null
   },
Advanced syntax for downcast and annotations

Two special (advanced) syntax structures exist for downcast and annotations. 


Attribute downcast syntax (@@) - A polymorphic read is used to proactively load subclass-specific attributes when reading from a base class.  For example, the following will only retrieve the homePhone attribute for Entity records that are of type Person, otherwise it is omitted.

http://localhost:7080/training/json/Entity?attributes=(fullName lastName (@@ Person firstName homePhone))&$indent


Annotations syntax - Creates ad hoc calculated attributes

To show how this syntax works, the following example call will return the following:

  • The lastName attribute
  • A calculated attribute, _lnLen, which is the length of the last name
  • A calculated attribute, _fullName, which concatenates the firstName and lastName attributes.

Underscore characters are used here to avoid name collisions.

http://localhost:7080/training/json/Person?attributes=(lastName (: _lnLen (string-length (@ lastName))) (: _fullName (string-append (@ lastName) (@ firstName))))&$indent

Aggregate functions - Expand the annotations syntax

The annotations syntax can be further expanded with the use of aggregate functions. These functions apply to subcollections within instances. Supported operators include count, average, min, max, and sum.

For example:

http://localhost:7080/training/json/Person?attributes=(firstName lastName (: _countryCnt (count (@ addrs country))) (: _uniqueCountryCnt (count (unique (@ addrs country)))) (: _nullCodeCnt (count (null? (@ addrs zip)))) (: _avg (average (string-length (@ addrs city)))) (: _min (minimum (string-length (@ addrs city)))) (: _max (maximum (@ addrs city))) (: _sum (sum (string-length (@ addrs city)))))&$indent

where

The where clause. The following examples illustrate the use of associations, reverse associations and qualified associations.

Simple where not using the @ syntax - (and ( = firstName "Tim") (= lastName "Lamont"))

Simple where using the @ syntax - (and ( = (@ firstName) "Joe") (= (@ lastName) "Test"))

Simple where using the js syntax - firstName=="Joe" and lastName == "Test"

To use the "js" syntax in a browser for GET requests, some characters such as && and || for AND and OR must be urlencoded and escaped as %26%26 and %7C%7C respectively. Alternatively, simply use and or or (lower case).


Any

The "any" operator reduces the multiplicity of an association to 1 

This allows you to create queries that would be difficult or impossible to express using only joins. In this example, the query returns all people who have at least one address in Toronto and at least one in Richmond Hill.

(and (any (= (@ addrs city) "Toronto")) (any (= (@ addrs city) "Richmond Hill"))) ; two part association path


Or, using "js" syntax:

any(addrs.city == "Toronto") && any(addrs.city == "Richmond Hill")


In contrast, nothing would be returned if the any operator were left out: (and (= (@ addrs city) "Toronto") (= (@ addrs city) "Richmond Hill")) since this statement requires that a single city attribute have both the value of "Toronto" and of "Richmond Hill" at the same time.


Using "any" to optimize database filtering on collection associations

In this example, consider a case in which you are searching for people with an address in the city of Toronto. If you were to use a simple where clause such as (Person'read ... '(= (@ addrs city) "Toronto") ...), then any person with multiple Toronto addresses would be returned multiple times, once for each address match. To avoid this redundancy, you can use the "any" operator as part of a two-part association path, as follows:

(any (= (@ addrs city) "Toronto"))

Alternatively, you can use the "js" syntax:

any(addrs.city == "Toronto")


Conditions in where clause association paths

This syntax allows you to restrict parts of association paths to specific subclasses and also to apply arbitrary conditions on association paths.

A common use case for this is to limit an association based on a qualifier; for example, to look for all Entities with a user named "Shaw" acting as their advisor. The query for this case is:

(= (@ coverage (= (@ coverageRole roleName) "Advisor") userPerson lastName) "Shaw")

or with "js" syntax:

this.coverage(coverageRole.roleName=="Advisor").userPerson.lastName == "Shaw"

Note that the "this" is required in the above "js" syntax to avoid ambiguity


Subclass restrictions

In this example, homeAddress is an attribute that is only found on the Person subclass of Entity

(= (@ entity (instance? (@) Person) homeAddress city) "Toronto")


Arbitrary conditions

When applied to a read of the Telcom classes, this example returns all instances that have an entity with a lastname of "Shaw", an address in "Toronto", and a non-null value in the address2 line.

(not (null? (@ entity (= (@ lastName) "Shaw") addrs (= (@ city) "Toronto") address2)))



Reverse associations (@@)

You can apply conditions to reverse associations as well as the more typical forward associations. Reverse associations query from the end of an association "back" to the class you are reading.  To compare the syntax structures:

A forward association takes the form (@ part1 part2 part3 ...) where the @ represents an instance of the class being read.

A reverse association takes the form (@@ <ClassName> part3 part2 part1) where part1 represents an attribute with a type of the class being read.

Forward associations are far more common, but you may encounter the reverse asociation syntax if you are tracing requests from NexJ UI clients.

Address?where=(= (@@ User person addrs) <userId>)

orderBy

The expressions by which to order the collection - a list of (<sort-expr> . <ascending?>) pairs.

For example:

(((@ firstName) . #t) ((@ lastName) . #f))

or more simply, since it is a single associate path length

((firstName . #t) (lastName . #f)) 


Or, using "js" syntax:

in the simple case if you used an expression it would sort ascending

orderBy=lastName

For more than one attribute, separate them by commas

orderBy=lastName, firstName

To indicate descending or explicitly ascending, use the following syntax:

orderBy=lastName asc, firstName desc

count

The number of instances to retrieve. Null '() for the default value of -1, meaning up to the readLimit specified in the data source.  You cannot pass a larger count than the readLimit defined in the data source.

offset

The number of top level instances to skip from the beginning of the retrieved collection. Default value of 0.

$enum=value|object

This parameter determines if enumerations are returned as values or sub-objects.

The default for the recommended rest/json and rest/xml endpoints is $enum="value". The legacy endpoints /json and /xml default to $enum="object".

$enum=value

Enumerations are represented as the "value" of the enumeration as seen in the entityStatus part below. This is easier to work with than as sub-objects so is the default on our rest endpoints.

[
   {
      "$class": "Person",
      "$oid": "100BFAA7399522406195DE457CD94C519B",
      "lastName": "Lamont",
      "addrs": [
         {
            "$class": "Address",
            "$oid": "1090704C0AA3A04A539FDF1B31B10798E2",
            "city": "Toronto"
         },
         {
            "$class": "Address",
            "$oid": "10C4317637F4AE432993BD488C1FC7D563",
            "city": "Toronto"
         }
      ],
      "entityStatus": "Client",
      "firstName": "Tim"
   },
$enum=object

Enumeration values are represented as sub-objects as seen in the entityStatus part below. This is the default behavior of the legacy /json and /xml endpoints.

[
   {
      "$class": "Person",
      "$oid": "100BFAA7399522406195DE457CD94C519B",
      "lastName": "Lamont",
      "addrs": [
         {
            "$class": "Address",
            "$oid": "1090704C0AA3A04A539FDF1B31B10798E2",
            "city": "Toronto"
         },
         {
            "$class": "Address",
            "$oid": "10C4317637F4AE432993BD488C1FC7D563",
            "city": "Toronto"
         }
      ],
      "entityStatus": {
         "$class": "EntityStatusEnum",
         "$oid": "46436C69656E7442656E4D53454E54495459535441545553",
         "value": "Client"
      },
      "firstName": "Tim"
   },


HTTP Method

Maps as follows.  GET=read, POST=create or invoke, PUT=update, DELETE=delete

Body

The body of the request when creating, updating, or optionally deleting is an object structure similar to that returned during a read (or GET).


JS expression reference

The following table shows some typical equivalent expressions using js and @ notation.

js expression@ expressionNotes

a.b

(@ a b)
this.a (@ a)
Global.XYZEnum.A(XYZEnum'A)
a.b(m == n).c(@ a b (= m n) c)== must be escaped in the URL
a.b(m eq n).c (@ a b (= m n) c)
this.a(m eq n).b(@ a (= m n) b)"this" required here to avoid ambiguity
a eq b and not (c like "xyz")(and (= a b) (not (like? c "xyz")))



Try it out

Let's start by looking at the Open API documentation for Model Services. Navigate to http://localhost:7080/training/openAPI.html. You should get a response something like...

If we expand the GET /Person section, we will see that get supports the following querystring parameters.

At the top of the parameters section, press the 

button.

In the attributes textbox, enter (firstName lastName (addrs city state zip)) and in the count textbox enter 3.

Then press the blue Execute button at the bottom of the parameters section. That will execute the request against the server and return the result in the Response body section.

The request URL is http://localhost:7080/training/json/Person?attributes=(firstName%20lastName%20(addrs%20city%20state%20zip))&count=3.

Try out some of the other get parameters in your browser i.e. attributes, where, orderBy, count, and offset.  You will also want to put &$indent in the URL as well so your output is more readable.

You can try out some of the other methods (POST, DELETE, PUT) by installing Postman and trying it out.


JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.