Sign up to receive SuiteScript examples and advice directly in your email inbox.

SuiteScript Design - REST API Modules

Created: December 7, 2020

Over the past couple weeks, we've been building up some patterns I use for REST integrations. Now let's look at how I interact with a resource endpoint. We've been using the SurveyMonkey Apply Connect API( SMA) as our example integration, so let's interact with their Users endpoint.

Generally, REST APIs organize their endpoints by resource type (e.g. Users, Applications, Assignments, etc), and so I like to make a module for each resource endpoint that will encapsulate the actions we can take for each resource.

If we look at the documentation for the Users endpoint, we'll see that we can perform all of the basic CRUD operations. For this example, we'll just show the actions of retrieving a specific User or the list of Users, but in practice I would be adding all of the operations to this same module.

Follow along with the code here.

I create the Users API module within an api directory. It's a custom module that leverages both our REST URL module and our Token Management module from the previous example.

// /api/sma_api_user.js
/**
 * API Module for interacting with SMA Users endpoint
 * @NApiVersion 2.1
 * @see https://connect.smapply.io/pages/resources.html#users
 */
define([
  '../lib/rest-utility',
  '../configuration/sma_config',
  'N/https'
], (rest, cfg, https) => {
  // ...
})

For the List Users call, we need to send a GET request to the /users/ endpoint, which can be optionally filtered with some query parameters, which we'll use as the input to our function. We will of course also need to provide our OAuth Token for Authorization. Using the two modules we've already created, the code for this is pretty slim.

function list (query) {
  let config = cfg.read()
  let request = {
    url: rest.generatePath({ resource: 'users', query }),
    headers: {
      'accept': 'application/json',
      'Authorization': `Bearer ${config.accessToken}`
    }
  }
  
  let response = https.get(request)
  
  if (response?.code !== 200) {
    throw response
  }
  
  return JSON.parse(response.body)
}

We start by reading our configuration record to get our Access Token, then we prepare a request Object for the N/https.get() method, employing our convenient URL generator along the way, and then we send off the request.

When the response comes back, I prefer to throw exceptions if we don't get a good response. For this particular endpoint anything except a 200 OK code is a bad thing, so we throw the response as an exception, and let the caller handle the error however it sees fit.

If all goes well, we parse the response body into an Object and return it for further processing by the caller.

Because of the patterns we've established, the request for retrieving a single User is not much different. We can follow exactly the same principle, leveraging our URL generator slightly differently.

function get (id) {
  let config = cfg.read()
  let request = {
    url: rest.generatePath({ resource: 'users', id }),
    headers: {
      'accept': 'application/json',
      'Authorization': `Bearer ${config.accessToken}`
    }
  }

  let response = https.get(request)

  if (response?.code !== 200) {
    throw response
  }

  return JSON.parse(response.body)
}

All we've changed is that we pass a numeric ID for the specific User we want to access, instead of a query Object as we did in the List request. Everything else is handled exactly the same way.

If your DRY eye is twitching, I feel you; there is definitely some repetition here that we can reduce. First, do remember that DRY applies to knowledge, not specifically to code( if you're not familiar with DRY that's OK for now, you can safely just move on). Second, I do typically extract these repeated parts into a separate module for reuse across multiple endpoint modules, but I'll save that for another day.

Now we have a reusable module that the rest of our application can leverage whenever it wants to interact with the Users endpoint in SMA. This module can then act as our template for stamping out additional endpoint modules.

HTH

-EG