Using the REST Library
The simple example of a RESTful service set out in the article A Simple RESTful Service involved quite a bit of work to produce an API for only one table.
An alternative approach is to use the REST library I (Mike) have created, which can be downloaded from File:RESTLibrary.zip (that one now obsolete) here on GitHub/DataFlexCode.
To use it, unzip that file download it (via a Git "pull" request) into some directory, then, from the workspace you are developing in, use that as a library; in the Studio do: Tools --> Maintain Libraries... --> Add Library, then navigate to where you unzipped downloaded it and within it select the "RESTLibrary.sws" file.
That will add a new section to the Studio's Class Palette entitled "REST".
With the WebApp project selected in the Studio, do: File --> New --> Other --> DataFlex Source File, naming it, for this example, "WebOrderAPI", which will create a blank package file. Into that, from the new REST section of the class palette, drag an object of the cRESTfulService class. Rename the object oWebOrderAPI.
Then again do: File --> New --> Other --> DataFlex Source File, this time naming it ApiCustomersHandler. Into that blank file drag an object of the cRestResourceHandler class. Change the object's name to oApiCustomersHandler and set its psInterfacePath property (an empty string by default) to "customers".
Then in the DDO Explorer pane (lower-right in the Studio by default) click the "+" book icon (Add DDO:
) and select the cCustomerDataDictionary.
Drag a cRESTApiObject from the class palette to below the comment "Add your cRESTApiObjects here". Rename it to "oCustomers". Set its phoDD property to oCustomer_DD, its psCollName (collection name) property to "customers" (plural) and its psInstName (instance name) property to "customer" (singular).
Uncomment one of the "Send AddListColumn" lines and change its "Table.Column" to Customer.Customer_Number, then copy that line and make the copy for Customer.Name. (You can keep adding those to your heart's content, but lists should be sparing of such detail.)
Go back to your WebOrderAPI file and under the comment line: 'Add "Use" statements for your cResourceHandler objects here:' add the line: "Use APICustomersHandler.pkg".
For development you may want to change the pbVerboseErrors setting to True, although for deployment it should probably be set back to False, to avoid confusing users with a pile of technical detail, which they neither should, nor want to, be exposed to.
You should perhaps, for future-proofing a real API, change the psPath setting from its default of "api" to "api/v1" (to provide for v2, v3 and so on in the future) - Note: this will change the URL which you use to make calls to it.
That should leave you with (some additional comments are mine):
WebOrderAPI.pkg:
Use cRESTfulService.pkg
Object oWebOrderAPI is a cRESTfulService
Set psPath to "api/v1" // Changed to provide for future versions
Set pbVerboseErrors to True // For development only, return to False for deployment
// Add "Use" statements for your cResourceHandler objects here:
Use APICustomersHandler.pkg // You add this line
Procedure APIRoot
Handle hoResp
Get CreateJsonObject to hoResp
Send AddRegisteredCollections hoResp
Send OutputJson hoResp
End_Procedure
Procedure ProcessHttpRequest String sVerb
String sPart0
Get PathPart 0 to sPart0
Move (Lowercase(sPart0)) to sPart0
Case Begin
Case ((sVerb = C_httpGet) and (sPart0 = ""))
Send APIRoot
Case Break
Case (IsRegisteredPath(Self, sPart0))
Send AutoProcess sPart0
Case Break
Case Else
Send UnrecognisedOperation
Case Break
Case End
End_Procedure
End_Object
APICustomersHandler.pkg:
Use cRESTResourceHandler.pkg
Use cCustomerDataDictionary.dd
Use cRESTAPIObject.pkg
Object oAPICustomersHandler is a cRESTResourceHandler
// Add your data-dictionary objects here
// Use the DDO Explorer in the Studio to select DDOs:
Object oCustomer_DD is a cCustomerDataDictionary
End_Object
Set Main_DD to oCustomer_DD
Set psInterfacePath to "customers" // We have to set this
// Add your cRESTApiObjects here (below the Set Main_DD line)
// Simply drag cRESTApiObjects here from the Studio's Class Palette
Object oCustomer is a cRESTApiObject
Set phoDD to oCustomer_DD // We have to set these three
Set psCollName to "customers" // properties for the object
Set psInstName to "customer" // to work properly
// Send AddListColumn File_Field Table.Column
Send AddListColumn File_Field Customer.Customer_Number // These lines determine
Send AddListColumn File_Field Customer.Name // the columns in the list
Procedure PostProc Handle hoResp Integer iMode
Forward Send PostProc hoResp iMode
// Add dependant collections (or other links) in here, e.g.:
// Send AddCollection hoResp "{name}" (InstURL(Self, iMode) + "/{collectionName}")
// Send AddLink hoResp "{name}" {identifier} {URL}
End_Procedure
End_Object
End_Object
Now you can immediately see the saving: 81 lines (most of which were written for you and a couple of which are comments I have added for clarification) vs. the 574 lines in the Simple example, while most of the functionality is essentially the same (a careful examination will reveal a bit more "HATEOAS"-type information in some of the returned JSON in the former).
In addition, this approach has much more generality and extensibility: additional cRESTApiObjects, and the corresponding DDOs, can be added to the cApiCustomersHandler to provide for dependant sub-collections (since customers have orders, and orders have details), while other cRESTResourceHandler objects can be added to handle other top-level collections (orders, vendors, inventory, salespeople and so on).
The library (in the cRESTApiObjects) provides mechanisms to exclude specific fields from the interface (Send AddExcludeColumn Table.Column), or mark them as read-only through it (Send AddReadOnlyColumn Table.Column), include parent columns in collections or instances (through the AddListColumn and AddInstanceColumn procedures) and even provide calculated pseudo-columns (AddListFunctionColumn and AddInstanceFunctionColumn). Entire collections can be set as read-only (pbReadOnly), no-update (pbNoUpdate) or no-delete (pbNoDelete).
In your browser, or testing tool such as Postman or the DataFlex RESTTest view in another article in this series, you can access this one as ".../api/v1" (or just ".../api" if you didn't make the versioning change to the psPath setting).
Security
This example does not have any security built into it yet, but the cRESTfulService class does understand a bit about Basic Auth, so we can add the following short OnPreRequest procedure to the oWebOrderAPI object:
Procedure OnPreRequest String sVerb String sPath
tBasicAuthCredentials tCreds
Boolean bOK
Set psUsername to "" // Reset it to blank at the start of *every* request
Get BasicAuthCredentials to tCreds
If (tCreds.sUserName <> "") Begin
Clear WebAppUser
Move tCreds.sUserName to WebAppUser.LoginName
Find Eq WebAppUser by Index.1
If (Found) Begin
Move (ComparePasswords(ghoWebSessionManager, Trim(WebAppUser.Password), ;
tCreds.sPassword)) to bOK
If bOK ;
Set psUsername to tCreds.sUserName
End
End
End_Procedure
As you can see, that relies on the ComparePasswords function of the WebSessionManager object, which will not be present in a Basic Web Project, so if using such, you would have to replace that, perhaps with a simple:
Move (Trim(WebAppUser.Password) = Trim(tCreds.sPassword)) to bOK
Which assumes that you are not using encrypted passwords, however how to handle those is left as an exercise for the reader!
Then at the top of the file add "Open WebAppUser", while at the top of the oWebOrderAPI object add "Property String psUsername".
Finally, right at the beginning of the ProcessHttpRequest procedure add:
If (psUsername(Self) = "") Begin
Send BasicAuthRequired "Web Order API"
Procedure_Return
End
Fuller instructions for creating an API using the REST library can be found in my course notes from Synergy 2019, although complete documentation for the library has sadly yet to make it to the top of my ToDo pile (it will be a lot of work).