Getting Started Full Stack Tutorial Part 2: Web Services with ObjectScript

Part 2: Web Services with ObjectScript

time to complete
30 minutes

A well-designed system never allows business applications to operate directly on the database. Instead, we provide access via services so that we can control and monitor the actions taken. In part 2, we build out the suite of RESTful web services needed for the business to function.

With most databases, you have no choice but to use a middleware framework — for example, Java Spring, Python Flask, or Node.js Express, — and talk to the data layer via SQL. You can certainly do that with InterSystems IRIS as well, but you also have another easier and higher performance option:.

  • Code in ObjectScript: Get the performance of stored procedures, and the flexibility, power and ease-of-use of a real programming language.
  • No middleware required: The web server and middleware layer are built-in!

Once you get the hang of it, ObjectScript is the fastest way to build the back-end for web apps.

diagram of part 2 REST


tip
Need InterSystems IRIS?
Get a free, online development sandbox here. Log in with your InterSystems universal account, or register for one below.

ObjectScript database query

In part 1 you saw how to access the database using Python and SQL. Now let’s see how easy it is with ObjectScript, especially when you want to get a record using its primary key.

  1. Open the Sandbox IDE (setting requires sandbox - click here)
  2. From the InterSystems menu, select Web Terminal
  3. Log in with username tech and password demo
  4. Type the following commands to get the ICO.inventory record having a primary key of 1.
    set item = ##class(ICO.inventory).%OpenId(1)
    zwrite item
    set item.quantitykg = 300
    zwrite item
    do item.%Save()
    

Line by line, the code:

  1. Fetches record 1 from the database
  2. Prints the record’s data to the screen
  3. Changes the quantity in stock for this item to 300 kg
  4. Prints again to verify the change
  5. Writes changed data to the database
tip
Tip
The InterSystems Web Terminal works a lot like a normal command line terminal, but it also accepts ObjectScript commands

Database query with ObjectScript and SQL

Now let’s perform a more complex query using SQL right in your ObjectScript code. Go back to the Web Terminal and type:

set sqlquery = "SELECT * FROM ICO.inventory"
set resultset = ##class(%SQL.Statement).%ExecDirect(,sqlquery)
while resultset.%Next() { Write !, resultset.%Get("vendor_id") }

Line by line, the code:

  1. Sets a variable with a valid SQL SELECT statement
  2. Executes the SELECT and stores a pointer to results in resultset
  3. Iterates over resultset, printing the vendor_id property

Writing ObjectScript Classes

That wasn’t very pretty because we put a lot of code on a single line. Now let’s put our ObjectScript code in a file.

  1. Go back to the Sandbox IDE.
  2. Expand the services folder in the IDE, then cls, then ICO.
  3. Right-click on the ICO folder and select “New File”.
  4. Name it Test.cls.
  5. In that file, type:
    Class ICO.Test
    {
      ClassMethod QueryDB() As %Status
      {
        set sqlquery = "SELECT * FROM ICO.inventory"
        set resultset = ##class(%SQL.Statement).%ExecDirect(,sqlquery)
        while resultset.%Next() {
            Write !, resultset.%Get("vendor_id")
        }
      }
    }
    

Save the file, then back in the Web Terminal, run:

do ##class(ICO.Test).QueryDB()
tip
Tip
Learn more about when to use SQL vs. ObjectScript, and our multi-model capabilities in this article on our developer community.

Building Web services

That was a gentle introduction to ObjectScript. Now let’s use what you’ve learned to build the web services needed to power our coffee roasting business. We will:

  1. Copy and compile pre-written code to the database
  2. JSON-enable the database tables
  3. Examine how each RESTful API is built
  4. Deploy and test the web services using curl and the browser
  5. Use a web service to move coffee from inventory to the store
  6. Record sales

In part 1 we created database tables using standard SQL. What we didn’t mention is that behind the scenes, corresponding ObjectScript classes were created as well! This lets developers easily switch between SQL and code depending on which style makes sense for the task at hand.

SQL creates table and ObjectScript class

We need to make a few changes to the ObjectScript code that represents our database tables so that we can also output the data as JSON. But first let’s look at the code already in the database.

  1. In the Sandbox IDE, click on the InterSystems icon to view the ObjectScript Explorer. This shows you the code that’s been loaded and compiled on the database server, rather than the local version of code you work on.Theia ObjectScript icon
  2. Expand the folders Classes and ICO and you see two files with familiar names, catalog and inventory (with .cls extensions). When we created database tables in part 1 using SQL statements, companion ObjectScript classes were also created at the same time.
  3. Click on catalog.cls. Note that columns are called Properties, but the data type and range restrictions should look familiar. Also look at inventory.cls
  4. Close any files you have opened.

JSON-enable the Data Tables

We need to change these classes slightly to enable JSON output, and make that output look nice, so let’s do that now.

  1. Go back to the normal file explorer by clicking the documents icon Theia documents icon.
  2. Expand the folders services > cls > ICO.
  3. Right-click on ICO, select New File and call it catalog.cls
  4. Right-click on ICO, select New File and call it inventory.cls
  5. Expand the folders services > cls_Sample > ICO.
  6. Open catalog.cls. Select all, copy and paste that text into services > cls > ICO > catalog.cls.
  7. Open inventory.cls. Select all, copy and paste that text into services > cls > ICO > inventory.cls.
  8. Save both files, and choose the “Overwrite server” option. This uploads and compiles your new versions of these classes with the addition of %JSON.Adapter and %JSONFIELDNAME.
  9. We did 2 things in these files:
    1. Extend %JSON.Adapter to enable automatic JSON output of this table’s data.
    2. Add %JSONFIELDNAME “property parameters” to selected Properties, to change its name when used in JSON output.

Now that we have database tables represented as ObjectScript code, and we know how to query the database using SQL and ObjectScript, we’re ready to build Web APIs.

Your first RESTful service

listbeans API request routed to ListRawBeans method

With the basic JSON support set up, we can build a service. Repeat the process above to populate a file Handler.cls in services > cls > ICO:

  1. Go back to the normal file explorer by clicking the documents icon Theia documents icon.
  2. Expand the folders services > cls > ICO.
  3. Right-click on ICO, select New File and call it Handler.cls.
  4. Expand the folders services > cls_Sample > ICO.
  5. Open Handler.cls. Select all, copy and paste that text into services > cls > ICO > Handler.cls.
  6. Save the file (this time you won’t need the “Overwrite server” option because this class is new).

Now that we’ve written the middleware REST APIs, the final step in making the services work is to expose them to the web. InterSystems IRIS provides a tool to route web requests to ObjectScript code in much the same way as Node.js Express or Python Flask. Let’s use the Management Portal to hook this IRIS class into the web.

Go to (setting requires sandbox - click here) and navigate to System Administration > Security > Applications > Web Applications.

management portal web apps configuration

Once you’re there, follow these steps:

  1. Click the Create New Web Application button.
  2. Under Name, enter /api/coffeeco (the base endpoint).
  3. Under Namespace, select USER.
  4. Make sure Enable Application is checked.
  5. Under Enable, select REST.
  6. For the Dispatch Class, type ICO.Handler (the IRIS Class all URLs starting with /api/coffeeco will be dispatched to).
  7. Under Security Settings, check Unauthenticated and Password for Allowed Authentication Methods.
  8. Click Save next to the title Edit Web Application.

coffeeco web app config

Now you will see new tabs, one of which is Application Roles. Click that tab, then %All under the Available list, then click the right arrow to copy it to the Selected list. Click Assign.

coffee co web app assign roles

tip
Tip
You can either code your REST interfaces manually like we do here, or use a spec-first approach, creating an Open API v2.0 specification and generating code automatically. More on this in the docs.

Test the service

Now we can hit this REST endpoint. The full URL is:

server host and portApplication nameRoute
https://(setting requires sandbox - click here):(setting requires sandbox - click here)/api/coffeeco/inventory/listbeans

Here’s what the whole thing should look like:

https://(setting requires sandbox - click here)/api/coffeeco/inventory/listbeans

Put this URL into a new browser window to make the REST call.

It should return this (probably not so nicely formatted):

{
    "rowcount": 5,
    "items": [{
        "id": "1",
        "vendor_product_code": "ETHIOPA32",
        "date_arrival": "65541",
        "quantity_kg": 400
    }, {
        "id": "2",
        "vendor_product_code": "BRAZILPREM",
        "date_arrival": "65541",
        "quantity_kg": 200
    }, {
        "id": "3",
        "vendor_product_code": "GUATEMALAALT30",
        "date_arrival": "65559",
        "quantity_kg": 200
    }, {
        "id": "4",
        "vendor_product_code": "SUMATRA2",
        "date_arrival": "65559",
        "quantity_kg": 200
    }, {
        "id": "5",
        "vendor_product_code": "SUMATRA3",
        "date_arrival": "65559",
        "quantity_kg": 400
    }]
}

Roast coffee beans

Now that you can get a response from a simple URL request, we know the services are working. Let’s operate the coffee business now. First up is simulating taking raw coffee beans out of inventory and roasting them. We’ll use the /api/coffeeco/inventory/getbeans API to do this.

In Handler.cls, look at the <Routes> XData section at the end of the file. See that the Url path /inventory/getbeans/:id/:quantity calls the ClassMethod GetRawBeans. Notice the URL substitution technique to pass values in the URL.

Find the GetRawBeans ClassMethod in the file. Since we know the primary key of the bean record (it was passed as the :id in the URL), we can use ObjectScript’s easy way to query the database with the %OpenId(). The rest of the method:

  1. Checks that enough quantity exists
  2. Decrements the quantity requested from inventory
  3. Returns the new inventory quantity to the requestor

Let’s run an actual request to get beans out of inventory for roasting.

This request can’t be tested by pasting the URL into a browser because you can’t send POST requests that way, so let’s use curl. In the sandbox IDE’s terminal, type:

curl -X POST https://(setting requires sandbox - click here)/api/coffeeco/inventory/getbeans/1/2.4

Put coffee in the store

In this simple example, the quantity requested doesn’t get recorded anywhere. We will count on the application making the request to take care of roasting coffee, bagging it and putting it in the store for sale.

Let’s do that now. In the services/samples directory, you’ll find 2 scripts:

  • createproducts.sh: Creates 5 sample coffee products ready for sale. The first 3 were roasted today, and the last 2 were roasted 6 days ago. This gives us some relatively stale inventory to discount in the store.
  • loadproducts.sh: Runs a curl command that iterates through every JSON file in the directory and uses the web service you just wrote to load the data into ICO.catalog.
  1. In the Sandbox IDE’s terminal, type
    cd /home/project/quickstarts-full-stack/services/samples
    sh createproducts.sh
  2. In the Sandbox IDE’s file explorer, go to the services/samples directory.
  3. Open the loadproducts.sh script.
  4. Change the IRISDB variable to
    https://(setting requires sandbox - click here)
  5. Save the file.
  6. In the Sandbox IDE’s terminal, type
    cd /home/project/quickstarts-full-stack/services/samples
    sh loadproducts.sh

If you’re not comfortable running a script you didn’t write yourself (which is smart from a security perspective), you can manually run your own curl commands to load the data. Here’s an example:

curl -d "@product_brazil_dark.json" -H "Content-Type: application/json" -X POST https://(setting requires sandbox - click here)/api/coffeeco/catalog/catalogproduct

At this point, we’ve taken shipments of raw coffee beans, put them into inventory, taken some out to roast, packaged them, and put them in the ICO.catalog table so they can be sold.

Serve the coffee catalog

Now we will look at the services needed for the front-end online storefront. The web developers will need a set of services available to them for:

  1. Getting freshly roasted bags of coffee available for sale
  2. Getting older bags of coffee that should be sold at a discounted rate
  3. Recording sales of bags of coffee

The first two are easy. They are read-only services, so we can simply use GET requests. We can even use the same method, GetProducts for both, and just have an input argument that tells us whether to return fresh or older stock.

Here are the relevant routes in the <Routes> section. The first returns all fresh coffee. The second is the same except an extra parameter is added at the end to signal that we want older coffee (fresh should equal 0). The third is for the client to record the sale of a number of bags of a specific product.

 
     
    
    

In the GetProducts method we query using SQL, iterate through the returned records, and write each to an array. Finally, we put the array into a JSON object and return it to the caller. This is all the information a web developer needs to build a nice site showing the products available for sale!

Make a sale

Our final service records coffee sales, SellProduct. It takes as input the product ID and the quantity of bags being sold. This is extremely simplified, as we won’t do any error checking or special payment handling or shipping. We’ll just decrement the catalog’s quantity of coffee bags, assuming everything else is taken care of on the front end. We also will assume if the customer bought multiple products, the client will send a SellProduct request for each. Just like we did in the GetRawBeans method, we’ll take advantage of ObjectScript’s convenience methods for querying records when you know their ID: %ExistsId, %OpenId, and %Save. Since this method is so similar to GetRawBeans, there’s nothing new to explain.

Try out the services

Query for fresh products:

curl https://(setting requires sandbox - click here)/api/coffeeco/catalog/getproducts

Query for stale:

curl https://(setting requires sandbox - click here)/api/coffeeco/catalog/getproducts/0

Try selling products:

curl -X POST https://(setting requires sandbox - click here)/api/coffeeco/catalog/sellproduct/1/2

The response should look similar to this.

{"catalog_id":1,"product_code":"BRAZILDARK","quantity":38,"price":13.99,"time_roasted":"2021-02-03T09:00:00Z","roasting_notes":"Full bodied and low acidity. Thick, creamy, nutty and semi-sweet.","img":"brazil_dark.jpg"}
Part 3: Build the coffee store web app
Exercise
time to complete
20 minutes
Be a front-end developer and build the coffee selling storefront with a simple Vue.js web app.