RESTForms - REST API for your classes

In this article I would like to present the RESTForms project - generic REST API backend for modern web applications.

The idea behind the project is simple -after I wrote several REST APIs I realized that generally, REST API consists of two parts:

  • Work with persistent classes
  • Custom business logic

And, while you'll have to write your own custom business logic, RESTForms provides all things related to working with persistent classes right out of the box.
Use cases

  • You already have a data model in Caché and you want to expose some (or all) of the information in a form of REST API
  • You are developing a new Caché application and you want to provide a REST API

Client side

This project is developed as a web applications backend, so JS just gets it. No format conversion required.

Note: CRUD

4 operations can be done over an object or a collection:

  • Create
  • Read
  • Update
  • Delete

Features

What can you already do with RESTForms:

  • CRUD over exposed class - you can get class metadata, create, update and delete class properties
  • CRUD over object - you can get, create, update and delete objects
  • R over object collections (via SQL) - protected from SQL injections
  • Self-discovery – first you get a list of available classes, after that you can get class metadata, and based on that metadata you can do CRUD over object

Paths

Here's the table of the main paths, showcasing what can you do via RESTForms.

URL

Description

info

List of all available classes

info/all

Get metadata for all classes

info/:class

Class metadata

field/:class

Add property to class

field/:class

Modify class property

field/:class/:property

Delete class property

object/:class/:id

Retrieve object

object/:class/:id/:property

Retrieve one property of the object

object/:class

Create object

object/:class/:id

Update object from dynamic object

object/:class

Update object from object

object/:class/:id

Delete object

objects/:class/:query

(SQL) Get objects for the class by query

objects/:class/custom/:query

(SQL) Get objects for the class by custom query

 

How do I start using RESTForms?

  1. Import the project from GitHub (recommended method: add as a submodule to your own repo, or just download a release)
  2. For each class, you wish to expose via RESTForms
    • Inherit from adaptor class
    • Specify permissions (for example you may expose some classes as read-only)
    • Specify property used as a display value for an object
    • Specify display names for properties, you wish to display

Setup

  1. Download and import from latest release on release page 20161.xml (for Caché 2016.1) or 201162.xml (for Caché 2016.2+) into any namespace
  2. Create new web application /forms with Dispatch class Form.REST.Main
  3. Open http://localhost:57772/forms/test?Debug in the browser to validate install (should output  {"Status": "OK"} and possibly prompt for password).
  4.  If you want test data, call:
do ##class(Form.Util.Init).populateTestForms()

Example

First, you need to know what classes are available. To get that information, call:

http://localhost:57772/forms/form/info

You'll receive something like this as a response:

[
   { "name":"Company",     "class":"Form.Test.Company" },
   { "name":"Person",      "class":"Form.Test.Person"  },
   { "name":"Simple form", "class":"Form.Test.Simple"  }
]

There are currently 3 sample classes (provided with RESTForms), let's see metadata for Person (Form.Test.Person class). To get that information, call:

http://localhost:57772/forms/form/info/Form.Test.Person

In response you'll receive class metadata:

{  
   "name":"Person",
   "class":"Form.Test.Person",
   "displayProperty":"name",
   "objpermissions":"CRUD",
   "fields":[  
      { "name":"name",     "type":"%Library.String",    "collection":"", "displayName":"Name",          "required":0, "category":"datatype" },
      { "name":"dob",      "type":"%Library.Date",      "collection":"", "displayName":"Date of Birth", "required":0, "category":"datatype" },
      { "name":"ts",       "type":"%Library.TimeStamp", "collection":"", "displayName":"Timestamp",     "required":0, "category":"datatype" },
      { "name":"num",      "type":"%Library.Numeric",   "collection":"", "displayName":"Number",        "required":0, "category":"datatype" },
      { "name":"аge",      "type":"%Library.Integer",   "collection":"", "displayName":"Age",           "required":0, "category":"datatype" },
      { "name":"relative", "type":"Form.Test.Person",   "collection":"", "displayName":"Relative",      "required":0, "category":"form"     },
      { "name":"Home",     "type":"Form.Test.Address",  "collection":"", "displayName":"House",         "required":0, "category":"serial"   },
      { "name":"company",  "type":"Form.Test.Company",  "collection":"", "displayName":"Company",       "required":0, "category":"form"     }
   ]
}

What does all that mean?

Class metadata:

  • name - display name for the class
  • class - underlying persistent class
  • displayProperty - object property to use, when displaying object
  • objpermissions - what can a user do with an object. In current case, user can create new objects, modify existing ones, delete existing objects and get the

Property metadata:

  • name - property name - same as in the class definition
  • type - property class
  • collection - is list/array collection
  • displayName - display property name
  • required - is this property  required
  • category - property type class category. Follows usual Caché class categories, except all RESTForms enabled classes are shown as "form"

In class definition it looks like this:

/// Test form: Person
Class Form.Test.Person Extends (%Persistent, Form.Adaptor, %Populate)
{

/// Form name, not a global key so it can be anything
/// Set to empty string (like here) to not have a class as a form 
Parameter FORMNAME = "Person";

/// Default permissions
/// Objects of this form can be Created, Read, Updated and Deleted
/// Redefine this parameter to change permissions for everyone
/// Redefine checkPermission method (see Form.Security) for this class
/// to add custom security based on user/roles/etc.
Parameter OBJPERMISSIONS As %String = "CRUD";

/// Property used for basic information about the object
/// By default getObjectDisplayName method gets its value from it
Parameter DISPLAYPROPERTY As %String = "name";

/// Use value of this parameter in SQL, as ORDER BY clause value 
Parameter FORMORDERBY As %String = "dob";

/// Person's name.
Property name As %String(COLLATION = "TRUNCATE(250)", DISPLAYNAME = "Name", MAXLEN = 2000);

/// Person's Date of Birth.
Property dob As %Date(DISPLAYNAME = "Date of Birth", POPSPEC = "Date()");

Property ts As %TimeStamp(DISPLAYNAME = "Timestamp") [ InitialExpression = {$ZDATETIME($ZTIMESTAMP, 3, 1, 3)} ];

Property num As %Numeric(DISPLAYNAME = "Number") [ InitialExpression = "2.15" ];

/// Person's age.<br>
/// This is a calculated field whose value is derived from <property>DOB</property>.
Property аge As %Integer(DISPLAYNAME = "Age") [ Calculated, SqlComputeCode = { set {*}=##class(Form.Test.Person).currentAge({dob})}, SqlComputed, SqlComputeOnChange = dob ];

/// This class method calculates a current age given a date of birth <var>date</var>.
ClassMethod currentAge(date As %Date = "") As %Integer [ CodeMode = expression ]
{
$Select(date="":"",1:($ZD($H,8)-$ZD(date,8)\10000))
}

/// Person's spouse.
/// This is a reference to another persistent object.
Property relative As Form.Test.Person(DISPLAYNAME = "Relative");

/// Person's home address. This uses an embedded object.
Property Home As Form.Test.Address(DISPLAYNAME = "House");

/// The company this person works for.
Relationship company As Form.Test.Company(DISPLAYNAME = "Company") [ Cardinality = one, Inverse = employees ];
}

RESTForms enabling a class

So, to make this class RESTForms enabled, I started with the usual persistent class and:

  1.  Extended it from Form.Adaptor
  2. Added FORMNAME parameter with the value - name of the class
  3. Added OBJPERMISSIONS parameter - CRUD for all permissions
  4. Added DISPLAYPROPERTY parameter - property name used to display object name
  5. Added FORMORDERBY parameter - default property to sort by for queries using RESTForms
  6. For each property I want to see in metadata I added DISPLAYNAME property parameter

That's all. After compilation, you can use the class with RESTForms.

As we generated some test data (see Installation, step 4), let's get Person with id 1. To get the object call:

http://localhost:57772/forms/form/object/Form.Test.Person/1

And, here's the response (generated data, may differ):

{
   "_class":"Form.Test.Person",
   "_id":1,
   "name":"Klingman,Rhonda H.",
   "dob":"1996-10-18",
   "ts":"2016-09-20T10:51:31.375Z",
   "num":2.15,
   "аge":20,
   "relative":null,
   "Home":{
      "_class":"Form.Test.Address",
      "House":430,
      "Street":"5337 Second Place",
      "City":"Jackson"
   },
   "company":{
      "_class":"Form.Test.Company",
      "_id":60,
      "name":"XenaSys.com",
      "employees":[
         null
      ]
   }
}

To modify the object (specifically, num property), call:

PUT http://localhost:57772/forms/form/object/Form.Test.Person

With this body:

{
   "_class":"Form.Test.Person",
   "_id":1,
   "num":3.15
}

Note that for better speed, only _class, _id and modified properties should be in the request body.

Now, let's create a new object. Call:

POST http://localhost:57772/forms/form/object/Form.Test.Person

With this body:

{
   "_class":"Form.Test.Person",
    "name":"Test person",
    "dob":"2000-01-18",
    "ts":"2016-09-20T10:51:31.375Z",
    "num":2.15,
    "company":{ "_class":"Form.Test.Company", "_id":1 }
}

If the object creation was successful, RESTForms would return an id:

{"Id": "101"}

Otherwise, an error would be returned in JSON format. Note that all persistent object properties should be referenced only by _class and _id  properties.

And finally, let's delete our new object. Call:

DELETE http://localhost:57772/forms/form/object/Form.Test.Person/101

That's full CRUD over Form.Test.Person class.

Demo

You can try RESTForms online here (user: Demo, pass: Demo) .

Additionally there is a RESTFormsUI application - editor for RESTForms data, check it out here (user: Demo, pass: Demo). Screenshot of the class list:

Conclusion

RESTForms does allmost of the work, required from the REST API as far as persistent classes are concerned.

What's next

In this article, I just started talking about RESTForms features. In the next article, I'd like to tell you about some advanced features - queries, that allow client to get slices of data safely, with no risk of SQL injections.

There is also a RESTFormsUI - editor for RESTForms data.

Links

Vote up!
Vote down!

Rating: 9

Comments: 18 Views: 1244

Comments

Eduard, thanks for the very promising article!

If I decide to use your framework, how can I deploy the library in my solution? What are the best practices for that?

Vote up!
Vote down!

Rating: 0

Deploy?

I think packed with the rest of the solution would be okay, and during installation you can automatically check GitHub for latest release and download it, if is's newer.

Vote up!
Vote down!

Rating: 0

@Eduard the RESTForms is a great idea!

 

Vote up!
Vote down!

Rating: 0

is it possible to adapt this RestAPI concept to work without using the CSP application ??

kevin

Vote up!
Vote down!

Rating: 0

What do you mean? Do you want to call RESTForms from some other (non-web) context? Can you please expand your question.

Vote up!
Vote down!

Rating: 0

the current implementation is CSP based and uses (user based) csp licences for the interaction. Because of the way csp retains that licence for a period after "finishing with the sesssion", it would be nice to be able to use a similar implementation that does make any use of the CSP licences

kevin

Vote up!
Vote down!

Rating: 0

Download and import from latest release on release page 20161.xml (for Caché 2016.1) or 201162.xml (for Caché 2016.2+) into any namespace

If you have 2016.2+ Forms would not be compiled,  $-methods should be renamed.
You can do it with the following:

Import and compile in NameSpace classes from SystemMethodsRemover 

Run in terminal:

And it works.

Or if you have CacheUpdater installed, import and compile from the Github repo can be reached with this one command:

d ##class(CacheUpdater.Task).Update("intersystems-ru","SystemMethodsRemover")

 

Vote up!
Vote down!

Rating: 0

Release page offers releases for both 2016.1 and 2016.2+. And in 2016.2+ $ methods are already removed.

If however you want to use latest commit from repo, then you need to run SMR.

Vote up!
Vote down!

Rating: 0

Ed!

Consider you have another Github repo in which you want to use REST Forms repo. What is the best way to achieve this?

Vote up!
Vote down!

Rating: 0

The best way to add RESTForms (or any other repository) into your repository is submodules. Submodule is a pointer to specific commit from another repository. On disk it looks like folder. More info on submodules. To add RESTForms execute in git cli:

git submodule add https://github.com/intersystems-ru/RESTForms.git

 

Vote up!
Vote down!

Rating: 1

Hi, did you check user name and/or password of Demo RestFormsUI? I cannot get access to that page...

Vote up!
Vote down!

Rating: 0

Reenabled Demo user.

Vote up!
Vote down!

Rating: 0

Hello,

I'm trying to open RestFormsUI from my localhost. And I have a problem with giving rights to certain user to login into application. In Readme for the project it says "Webapp or Unknown user should be able to access the namespace/database". How am I to do it? Whatever I do at this point I get "Incorrect username and/or password" error. Apparently I'm doing something wrong :(

Vote up!
Vote down!

Rating: 0

Have you tried providing user credentials with %All access?

If you're getting this error for user with %All, and it's a dev box, add %All to /forms application.

Vote up!
Vote down!

Rating: 0

Webapp roles:

Webapp roles

RestForms App:

Roles:

RestFormsUI App:

Vote up!
Vote down!

Rating: 0

Everything seems to be in order. Can you post an error from browser dev tools?

Vote up!
Vote down!

Rating: 0

New RESTForms release should fix that error.

Vote up!
Vote down!

Rating: 2

Thanks a lot for you assistance!

Vote up!
Vote down!

Rating: 0