Home Anatomy of SuiteScript: Restlet
Post
Cancel

Anatomy of SuiteScript: Restlet

Introduction

Restlets offer a way to add custom endpoints to NetSuite. The can be used internaly in NetSuite, or externally by other systems.

When using a restlet internally, say with the https.get function, no authentication is needed. When using a restlet externally, you will need to authenticate using either Oauth 1 or Oauth 2. With Oauth 1, you will need to generate an access token and secret. You will be logged in as the same user every time, the user set when you generate the token and secret. With Oauth 2, the user of the external application will be redirected to NetSuite to log in.

More details on Oauth 1 and 2 will be covered in a future post.

Here is a restlet generated by the VS Code SDF extension.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
 * @NApiVersion 2.1
 * @NScriptType Restlet
 */
define([],

    () => {
        /**
         * Defines the function that is executed when a GET request is sent to a RESTlet.
         * @param {Object} requestParams - Parameters from HTTP request URL; parameters passed as an Object (for all supported
         *     content types)
         * @returns {string | Object} HTTP response body; returns a string when request Content-Type is 'text/plain'; returns an
         *     Object when request Content-Type is 'application/json' or 'application/xml'
         * @since 2015.2
         */
        const get = (requestParams) => {

        }

        /**
         * Defines the function that is executed when a PUT request is sent to a RESTlet.
         * @param {string | Object} requestBody - The HTTP request body; request body are passed as a string when request
         *     Content-Type is 'text/plain' or parsed into an Object when request Content-Type is 'application/json' (in which case
         *     the body must be a valid JSON)
         * @returns {string | Object} HTTP response body; returns a string when request Content-Type is 'text/plain'; returns an
         *     Object when request Content-Type is 'application/json' or 'application/xml'
         * @since 2015.2
         */
        const put = (requestBody) => {

        }

        /**
         * Defines the function that is executed when a POST request is sent to a RESTlet.
         * @param {string | Object} requestBody - The HTTP request body; request body is passed as a string when request
         *     Content-Type is 'text/plain' or parsed into an Object when request Content-Type is 'application/json' (in which case
         *     the body must be a valid JSON)
         * @returns {string | Object} HTTP response body; returns a string when request Content-Type is 'text/plain'; returns an
         *     Object when request Content-Type is 'application/json' or 'application/xml'
         * @since 2015.2
         */
        const post = (requestBody) => {

        }

        /**
         * Defines the function that is executed when a DELETE request is sent to a RESTlet.
         * @param {Object} requestParams - Parameters from HTTP request URL; parameters are passed as an Object (for all supported
         *     content types)
         * @returns {string | Object} HTTP response body; returns a string when request Content-Type is 'text/plain'; returns an
         *     Object when request Content-Type is 'application/json' or 'application/xml'
         * @since 2015.2
         */
        const doDelete = (requestParams) => {

        }

        return {get, put, post, delete: doDelete}

    });

Entry Points

There are a few different entry points for restlets. Each entry point corresponds to a different http verb.

GET

The get entry point is used for retrieving data. It is called when a GET request is made to the restlet.

The requestParams parameter is an object containing the query parameters from the request url. If the header Content-Type is set to application/json, the requestParams parameter will be a JSON object. If the header Content-Type is not set or is set to text/plain, the requestParams` parameter will be a string.

The return value of the get function is the response body.

post

The post entry point is used for creating new records. It is called when a POST request is made to the restlet.

The requestBody parameter is the request body. Similar to the requestParams parameter, if the header Content-Type is set to application/json, the requestBody parameter will be a JSON object.

The return value of the post function is the response body.

put

This is similar to the post entry point, but is used for updating existing records.

The requestBody parameter is as described above for a post request. For more information about the difference between post and put, see this stack overflow question.

delete

This is used for deleting records.

Example

Here is a simple restlet that will return the work order variance report detiailed in this post for given a work order id.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/**
 * @NApiVersion 2.1
 * @NScriptType Restlet
 */

define(['N/query'], (query) => {

  const get = (requestParams) => {

    const workorderid = requestParams.workorderid
    const report = getReport(workorderid)
    return report

  }


  const getReport = (workorderid) = {
    var theQuery = `

                      WITH allitems AS (
                        SELECT
                          WorkorderLines.item AS item
                          FROM TransactionLine AS WorkorderLines
                          WHERE WorkorderLines.transaction = ${workorderid}
                          and WorkorderLines.mainline = 'F'
                        UNION
                        SELECT
                          BomRevisionComponentMember.item AS item,
                          FROM BomRevisionComponentMember
                          INNER JOIN Transaction ON Transaction.id = ${workorderid}
                          AND Transaction.billofmaterialsrevision = BomRevisionComponentMember.bomrevision
                      ), WorkorderLines AS (
                        SELECT
                          WorkorderLines.item AS item,
                          WorkorderLines.quantity * -1 AS quantity
                          FROM TransactionLine AS WorkorderLines
                          WHERE WorkorderLines.transaction = ${workorderid}
                          and WorkorderLines.mainline = 'F'
                      ), AssemblyBuildLines AS (
                        SELECT
                          AssemblyBuildLines.item AS item,
                          AssemblyBuildLines.quantity * -1 AS quantity
                          FROM TransactionLine AS AssemblyBuildLines
                          WHERE AssemblyBuildLines.createdFROM = ${workorderid}
                          and AssemblyBuildLines.mainline = 'F'
                      ), BomRevisionLines AS (
                        SELECT
                          BomRevisionComponentMember.item AS item,
                          BomRevisionComponentMember.quantity * TransactionLine.quantity * UnitsTypeUom.conversionrate AS quantity
                          FROM BomRevisionComponentMember
                          INNER JOIN Transaction ON Transaction.id = 1234
                          AND Transaction.billofmaterialsrevision = BomRevisionComponentMember.bomrevision
                          INNER JOIN UnitsTypeUom ON UnitsTypeUom.internalid = BomRevisionComponentMember.units
                          INNER JOIN TransactionLine ON TransactionLine.transaction = transaction.id and TransactionLine.mainline = 'T'
                      )
                      SELECT
                        item.itemid AS itemid,
                        (COALESCE(BomRevisionLines.quantity, 0))/ConsumptionUom.conversionrate AS bomquantity,
                        (COALESCE(BomRevisionLines.quantity, 0) - COALESCE(WorkorderLines.quantity, 0))/ConsumptionUom.conversionrate AS workordervariance,
                        (COALESCE(BomRevisionLines.quantity, 0) - COALESCE(AssemblyBuildLines.quantity, 0))/ConsumptionUom.conversionrate AS assemblyvariance,
                        ConsumptionUom.pluralAbbreviation AS consumptionunit,
                        (COALESCE(AssemblyBuildLines.quantity, 0) * item.averagecost)/ConsumptionUom.conversionrate AS assemblycost
                        FROM allitems
                        INNER JOIN Item ON Item.id = allitems.item
                        INNER JOIN UnitsTypeUom AS ConsumptionUom ON ConsumptionUom.internalid = Item.consumptionunit
                        LEFT JOIN WorkorderLines ON workorderlines.item = allitems.item
                        LEFT JOIN AssemblyBuildLines ON AssemblyBuildLines.item = allitems.item
                        LEFT JOIN BomRevisionLines ON bomrevisionlines.item = allitems.item
    `


    return query.runSuiteQL({
      query: theQuery
    }).asMappedResults()
  }

  return {
    get
  }

})

And the request (using fetch) would look like this:

1
2
3
4
5
6
7
8
9
fetch('https://tstdrv123456.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=1234&deploy=1&workorderid=1234', {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json'
    'Accept': 'application/json'
  }
})


Conclusion

Restlets are invaluable for building external applications that interact with NetSuite. While SuiteTalk is a full featured API, for custom data you want to pull from NetSuite, restlets are a great option.

This post is licensed under CC BY 4.0 by the author.

Anatomy of SuiteScript: Client Script

Anatomy of SuiteScript: Suitelet