# APEX on the Edge

# Overview

*Photo by* [*Alan Tang*](https://unsplash.com/@itspootie?utm_source=Hashnode&utm_medium=referral) *on Unsplash*

This past week at the [KScope 23](https://kscope23.odtug.com) conference, I presented a session called "APEX on the Edge". This session explored using APEX to integrate with non-ORDS-based web services and the complexities & nuances involved in getting them to work with APEX.

This post will walk you through the steps that I took to create each of the three main demonstrations.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💾</div>
<div data-node-type="callout-text">The slides from that session can be downloaded from <a target="_blank" rel="noopener noreferrer nofollow" href="https://objectstorage.us-ashburn-1.oraclecloud.com/p/LIoUy7UQ3psnNZ6JOZQfxZ9EiBkDsacNZL0R_S3uLNrNsl6YbjES-xYCWuRG8JE8/n/idrmltuyzbbd/b/presentations/o/APEX%20on%20the%20Edge.pdf" style="pointer-events: none">here</a>.</div>
</div>

## OpenWeather

The first site that I built an integration to was openweathermap.org. OpenWeather offers a blend of free and paid APIs that you can use for your purposes on your site. For my demonstration, I stuck to the free one, specifically the [16-day forecast](https://openweathermap.org/forecast16) API.

Using this API, I created a combination chart in APEX that showed the high & low forecast as a bar chart and the percent chance of clouds & rain as a bar chart.

<div data-node-type="callout">
<div data-node-type="callout-emoji">🌩</div>
<div data-node-type="callout-text">Create your own API key for OpenWeather <a target="_blank" rel="noopener noreferrer nofollow" href="https://home.openweathermap.org/users/sign_up" style="pointer-events: none">here</a>.</div>
</div>

### Create a REST data source for OpenWeather

The first thing we will do is create the REST Data Source in APEX.

1. Use Postman to explore the URL for the Movie DB:  
    **https://api.openweathermap.org/data/2.5/forecast/daily?q=Austin&cnt=14&units=imperial&appid=\[API KEY\]**  
    *Be sure to replace \[API KEY\] with your API Key*
    
2. Create a new REST data source for the Movie DB using the same URL.
    
3. Take all defaults during the creation.
    

### Add a new column to the data profile to convert the DT column to a date

Once we've created the REST data source, we need to add an additional column. This column will convert the "DT" column - which stores the current data in epoch time - to an Oracle DATA datatype. This way, we will be able to use this column in a chart and it will be more readable.

1. Edit **The Movie DB** REST data source
    
2. Edit the **Data Profile**
    
3. Click the **Add Column** button
    
4. Set the following options:
    
    Column Type: **SQL Expression**
    
    Name: **FORECAST\_DATE**
    
    Data Type: **DATE**
    
    SQL Expression: `TO_DATE('01-jan-1970', 'dd-mon-yyyy') + DT/86400`
    
5. Click **Create** to add the new column.
    

### Create a chart based on the OpenWeather REST data source

Now that we have the REST data source defined, let's create a chart based on it.

1. Create a new page in your application for the chart. We'll use page 10 in this example.
    
2. Edit **Page 10** of your application.
    
3. Create a new **Chart** region and set the type to **Combination**.
    
4. Under Settings, set the Time Axis Type to **Enabled**.
    
5. Set Legend to **Show** and set Position to **Bottom**.
    
6. Next, create two new Series for High and Low using the following values:
    

| **Identification** | **Name** | High | Low |
| --- | --- | --- | --- |
|  | **Type** | Line | Line |
| **Source** | **Location** | REST Source | REST Source |
|  | **Source** | OpenWeatherMap | OpenWeatherMap |
| **Column Mapping** | **Label** | FORECAST\_DATE | FORECAST\_DATE |
|  | **Value** | MIN | MAX |
| **Appearance** | **Color** | #ff0000 | #0000ff |
| **Label** | **Show** | Enabled | Enabled |
|  | **Position** | Below Marker | Below Marker |

1. Create two additional series for Cloud and Rain using the following values:
    

| **Identification** | **Name** | Cloud | Rain |
| --- | --- | --- | --- |
|  | **Type** | Bar | Bar |
| **Source** | **Location** | REST Source | REST Source |
|  | **Source** | OpenWeatherMap | OpenWeatherMap |
| **Column Mapping** | **Label** | FORECAST\_DATE | FORECAST\_DATE |
|  | **Value** | CLOUDS | RAIN |
| **Appearance** | **Color** | #dddddd | #0000ff |
|  | **Assigned to Y2 Axis** | Enabled | Enabled |
| **Label** | **Show** | Enabled | Enabled |
|  | **Position** | Below Marker | Below Marker |

1. Make any additional changes to the chart's attributes to your liking.
    

### Create a page item & map it to the parameter for each series in the chart

Finally, let's enable users to enter any city they want to see a forecast for. We'll do this by adding a page item.

1. Create a new page item – **P10\_SEARCH**.
    
2. Expand all four **Series** and then expand the **Parameters** section for each of them.
    
3. Select all four instances of “**q**” and set the Type to **Item** and the Item to **P10\_SEARCH**.
    
4. Save and run the page.  You should now be able to enter city names into the Search field and get their respective forecasts.
    

## The Movie DB

The Movie DB (TMDB) is a community-driven website that catalogs all movies, TV shows and other content globally. With a catalog in the millions, many developers turn to IMDB for their integration needs.

TMDB also offers a free and paid tier. We'll use the free tier for this demonstration.

<div data-node-type="callout">
<div data-node-type="callout-emoji">📺</div>
<div data-node-type="callout-text">Create your own API key for TMDB <a target="_blank" rel="noopener noreferrer nofollow" href="https://www.themoviedb.org/signup" style="pointer-events: none">here</a>.</div>
</div>

### Create a REST data source & Cards region for The Movie DB REST data source

1. Create a new REST data source called **The Movie DB** using the following URL: [**https://api.themoviedb.org/3/search/movie?query=Star Wars&region=en&api\_key=\[API\_KEY\]**](https://api.themoviedb.org/3/search/movie?query=StarWars&region=en&api_key=API_KEY)  
    *Be sure to replace \[API KEY\] with your API Key*
    
2. Take all the default options.
    
3. Switch over to APEX, create a new page (in this example, we'll use page 20) and create a cards report based on the web service using the following mappings:
    

| Primary Key Column 1 | **ID** |
| --- | --- |
| **Title** | TITLE |
| **Subtitle** | RELEASE\_DATE |
| **Body** | OVERVIEW |
| **Badge Column** | VOTE\_AVERAGE |
| **Media Source** | Image URL |
| **URL** | [**https://image.tmdb.org/t/p/w500/&BACKDROP\_PATH**](https://image.tmdb.org/t/p/w500/&BACKDROP_PATH)**.** |

1. Run the report and show what it looks like.
    

Notice that since we hard-coded "Star Wars" into the URL, that's what the report is searching for. We will change that shortly.

### Configure pagination for The Movie DB REST data source

Since we did not configure pagination when we created the REST Data Source, we can only see the first 20 records. Let's configure paginations so that APEX will know how to request more records from the web service.

1. Edit The Movie DB REST data source.
    
2. Change the pagination to the following settings:
    

| **Pagination Type** | Page Number |
| --- | --- |
| **Page Number URL Parameter** | page |
| **Index of First Page** | 1 |
| **Fixed Page Size** | 20 |
| **Total Rows Selector** | total\_results |

1. Re-run the report and notice that there are far more than 20 records.
    

Since we're using a Cards report, the data will automatically scroll by default, so there's no need for a next & previous button.

### Create a page item & parameter for The Movie DB REST data source

1. Edit the report page and add a new item – **P20\_SEARCH**.
    
2. Map the new item to the **query** parameter in the Cards report.
    
3. Re-run the report and search for any movie or television show you can imagine!
    

### Change The Movie DB to use a bearer token & web credential

Since putting the API key in the URL is not very secure, we should use an alternative method when possible. TMDB offers an alternative way to authenticate to their web services using a read-access token or JWT.

The value of this token is static, which is not typical. Most sites that use JWTs will require you to get a new token periodically. In any case, it's far safer than using the URL to pass the API key.

We can use a Web Credential in APEX and set that to the value of our token. As long as we set this Web Credential's value after authentication, we should be good.

1. Edit the **Shared Components** of your application.
    
2. In the **Workspace Objects** region, click **Credentials**.
    
3. Click **Create**.
    
4. Enter the following values:
    

| **Name** | TMDB |
| --- | --- |
| **Static ID** | TMDB |
| **Authentication Type** | HTTP Header |

Now that we have a web credential, we need to point the REST Data Source at it.

1. Edit The Movie DB REST data source and remove the **api\_key** parameter.
    
2. Set the Credentials to **TMDB**
    
3. Save your changes.
    

Lastly, we need to pass our JWT to the web credential so that when the web service is called, APEX can present the token. We only need this to happen once, so we can use an Application Process that occurs after authentication.

1. Create an **Application Process – After Authentication**
    
2. Enter the following PL/SQL code:
    

```sql
apex_credential.set_session_credentials
  (
   p_credential_static_id => 'TMDB'
  ,p_key                  => 'Authorization'
  ,p_value                => 'Bearer ' || 'aaa.bbb.ccc'
  );
```

Be sure to replace aaa.bbb.ccc with your API read access token from TMDB, which can be found here: [https://www.themoviedb.org/settings/api](https://www.themoviedb.org/settings/api)

You will need to logout & login to set the credentials. Once you do that, the search should work as it was before.

## Customers

The third and final example is a simple web service based on a table called CUSTOMERS. I've exposed this web service via ORDS, but for this example, we're going to assume that it's not ORDS.

By doing this, we will need to map the pagination manually, as well as use the syntax that ORDS requires to perform server-side searching and sorting.

To keep as real-world as possible, the web service requires OAuth2 credentials to run, so it is not available to the public.

The CUSTOMERS table looks like this:

| CUSTOMER\_ID | NUMBER | N |
| --- | --- | --- |
| FIRST\_NAME | VARCHAR2(50 BYTE) | Y |
| LAST\_NAME | VARCHAR2(50 BYTE) | Y |
| EMAIL | VARCHAR2(50 BYTE) | Y |
| JOB\_TITLE | VARCHAR2(50 BYTE) | Y |
| HIRE\_DATE | DATE | Y |
| SALARY | NUMBER | Y |

To make searching a little easier, I created a view called CUSTOMERS\_V. This view concatenates all values into a single column so that we can perform our searches easier, and is what the web service actually references. The SQL for CUSTOMERS\_V is:

```sql
create or replace view customers_v 
as
select 
  customer_id, 
  first_name, 
  last_name,
  email,
  job_title,
  hire_date,
  salary, 
  customer_id || ' ' 
    || first_name || ' ' 
    || last_name || ' ' 
    || email || ' '
    || job_title || ' ' 
    || hire_date || ' '
    || salary as search 
from 
  customers
/
```

### Create a Web Credential

Before we can create the REST data source, we need to create the Web Credential that we will use to talk to the Customers web service. The steps are similar to what we did for TMDB.

1. Edit the **Shared Components** of your application.
    
2. In the **Workspace Objects** region, click **Credentials**.
    
3. Click **Create**.
    
4. Enter the following values:
    

| **Name** | Customers |
| --- | --- |
| **Static ID** | customers |
| **Authentication Type** | OAuth2 Client Credentials Flow |
| **Client ID or Username** | WqZcWG4OpbgrJZX1MW2afg.. |
| **Client Secret or Password** | dVnKMY5T1AkJaNfa0TJoSg.. |

<div data-node-type="callout">
<div data-node-type="callout-emoji">🛑</div>
<div data-node-type="callout-text"><strong>NOTICE! You should never, ever share any type of credential on the public internet like this! </strong>I am doing this because this is a demonstration environment, and the credential referenced only allows you to read from the CUSTOMERS table, which is populated with fictitious data. Should this get abused, I can simply disable it and move on.</div>
</div>

### Create a REST Data Source in APEX from the Customers web service

Now that we have a new Web Credential, we can create the new REST Data Source for the CUSTOMERS web service.

1. Create a new REST data source using the following URL: **https://bzinxd8saxpiakh-atp.adb.us-ashburn-1.oraclecloudapps.com/ords/kscope/customers/customers**
    
2. Leave Pagination Type alone for now; we can come back to that later.
    
3. Set Authentication Required to enabled; then, set Credentials to **Customers**.
    
4. For the OAuth Token URL, enter the following:  
    **https://bzinxd8saxpiakh-atp.adb.us-ashburn-1.oraclecloudapps.com/ords/kscope/oauth/token**
    
5. Click **Discover**.
    
6. Click **Create REST Data Source**.
    

Let's fix the pagination while we're here.

1. Edit the **Customers** REST Data Source.
    
2. Set the Pagination Type to **Page Size and Fetch Offset**.
    
3. Next, fill out the following values:
    

| **Page Size URL Parameter** | limit |
| --- | --- |
| **Row Offset URL Parameter** | offset |
| **Row Offset Type** | Rows to Skip |
| **Has More Rows Selector** | hasMore |
| **Has More Rows When Value** | Equals |
| **Has More Rows Attribute Value** | true |

1. Click **Apply Changes.**
    

### Create a Classic Report on the Customers REST Data Source

Now that we have the REST Data Source created, we can create a new report that maps to it to try things out.

1. Create a new page - we'll use Page 30 for this example.
    
2. On **Page 30**, create a new Classic Report.
    
3. For the Source of the Classic Report, set Location to **REST Source** and then REST Source to **Customers**.
    
4. Run the page to test drive the Customers web service.
    

You should see a report on the CUSTOMERS table, complete with working pagination.

### Adding Sorting & Searching

Even though it looks like you can sort the report, you're not sorting the entire dataset, only the rows that you have viewed in APEX.

Thus, we need to initiate the sorts (and searches) on the server side. This way, the records are sorted server-side and sent over in blocks of 25 pre-sorted result sets. Thus, we will see the data in the correct sorted order when it's displayed in APEX.

Since the web service is hosted by ORDS, we can refer to its documentation to determine how to call server-side sorting and searching. That document can be found here: https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/23.1/orddg/developing-REST-applications.html#GUID-091748F8-3D14-402B-9310-25E6A9116B47

Let's add the page items:

1. Edit **Page 30** of your application.
    
2. Create a new page item called **P30\_SEARCH** in the Customers region.
    
3. In the Settings section, set Submit when Enter Pressed to **enabled**.
    
4. Next, create another page item called **P30\_SORT**. This should be a Select List.
    
5. In the Settings section, set Page Action on Selection to **Submit**.
    
6. In the Layout section, set Start New Row to **disabled**.
    
7. In the List of Values section, set the Type to **SQL Query**.
    
8. Enter the following SQL Query:
    

```sql
select
  heading,
  lower(column_alias) as col
from 
  APEX_APPLICATION_PAGE_RPT_COLS
where
  page_id = :APP_PAGE_ID
  and application_id = :APP_ID
  and region_name = 'Customers'
  and column_is_hidden = 'No'
order by
  display_sequence
```

1. Set both Display Extra Values and Display Null Value to **disabled**.
    
2. Create another page item called **P30\_SORT\_DIRECTION**. This should be a Radio Group.
    
3. In the Settings section, enter **2** for Number of Columns and set Page Action on Selection to **Submit Page**.
    
4. In the Layout section, set Start New Row to **disabled**.
    
5. In the List of Values, set the Type to **Static Values**.
    
6. Enter the following values and click OK:
    

| Display Value | Return Value |
| --- | --- |
| ASC | asc |
| DESC | desc |

1. Set both Display Extra Values and Display Null Value to **disabled**.
    
2. Create one more item - **P30\_JSON**. This should start on a new line.
    

Now that we have the page items, let's create a computation that will take their values of them and convert them to JSON before sending it to the web service.

1. In the Page Processing tree, create a new Computation that fires After Submit.
    
2. In the Identification section, enter **P30\_JSON** for the Item Name.
    
3. In the Computation section, set Type to **Function Body** and Language to **PL/SQL**.
    
4. Enter the following code for PL/SQL Function Body:
    

```sql
if :P30_SEARCH is not null then
  return '{"search":{"$instr":"' 
    || apex_escape.json(:P30_SEARCH) 
    || '"},"$orderby":{"' || :P30_SORT || '":"' 
    || :P30_SORT_DIRECTION || '"}}';
else
  return '{"$orderby":{"' || :P30_SORT || '":"' 
    || :P30_SORT_DIRECTION || '"}}';
end if;
```

1. Save and run the page.
    

### Adding and Mapping a REST Web Source Parameter

Lastly, we need to add a parameter to the REST Web Source and then map that parameter to the JSON that we generate when we submit the page. This will enable both the sort and search functionality.

1. Edit the **Shared Components** of your application.
    
2. Click REST Data Sources, and then click **Customers** to edit it.
    
3. Click the **Add Parameter** button.
    
4. Set the Type to **URL Query String**, enter **q** for the Name, and make sure that Omit when value is empty is **enabled**.
    
5. Click **Apply Changes**.
    
6. Navigate back to Page 30 of your application.
    
7. Under the **Customers** region, expand the Parameters tree and select the node labeled **q**.
    
8. In the Value section, set the Type to **Item** and then enter **P30\_JSON** for the Item.
    
9. Save the page and run your application.
    

At this point, you should be able to search the Customers web service, as well as select which column & order to sort by.

There are just a couple more things that we'll want to clean up before we can call this 100% done.

First, we need to disable APEX-level sorting, as we're doing that server-side now:

1. Under the **Customers** region, expand the Columns tree and select all columns.
    
2. In the Sorting section, set Sortable to **disabled**.
    

Next, we want to hide the SEARCH column. Its only purpose is to make searching easier, so we don't need to render it.

1. Next, select only **SEARCH.**
    
2. In the Identification section, set the Type to **Hidden Column**.
    
3. **Save** your changes.
    

# Conclusion

The world of web services is largely standards-based, but implementations will vary greatly from site to site. Learning the basic components and how to interact with them is a critical first step to consuming them in your APEX application.

The APEX team has done a tremendous job in creating a robust set of tooling that should be able to handle anything thrown at it. And when that does fail, you can fall back to the API layer - something we did not need to do with these examples.

Integration with web services is only going to get more important, as an organization's SaaS platforms need to exchange and consolidate data to help people make sound decisions. APEX is an ideal tool to act as the "hub" when data consolidation is a key goal.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💻</div>
<div data-node-type="callout-text">Want to see these demos in action? Click <a target="_blank" rel="noopener noreferrer nofollow" href="https://bzinxd8saxpiakh-atp.adb.us-ashburn-1.oraclecloudapps.com/ords/r/blog/apex-on-the-edge/home" style="pointer-events: none">here</a>.</div>
</div>
