Serverless computing is a software design pattern where applications are hosted by a third-party service, eliminating the need for server software and hardware management by a developer. Applications are broken up into individual functions that can be invoked and scaled individually.

The Freshworks app development platform includes a serverless environment to enable you to create apps that run in response to events that can originate from the Freshworks products, the app setup lifecycle, as well as events from any compatible service on the Internet.

Sample use cases

And yes, as you know already, serverless doesn't mean there are no servers..

Serverless apps primarily respond to events and run custom business logic. The following event types are supported.

App Set-up Events

App set-up events are triggered when an app is installed or uninstalled. The two corresponding events are,

Product Events

Product events are triggered by the Freshworks products, such as ticket-create, ticket-update, and lead-delete, that an app can subscribe to.

External Events
Apps can be invoked in response to events that occur in an external product or service, by creating webhooks in that product or service and subscribing to the webhooks in the app. Every time the webhook is triggered, an external event invokes the app in response.

Scheduled Events

One-time or recurring scheduled events can be created to invoke serverless apps at an appropriate time.

In this tutorial, we are going to build a serverless app that creates a GitHub issue every time a Freshdesk ticket is created. The app will use GitHub webhooks to close the ticket when the corresponding issue is closed.

The following image describes a high-level flow diagram for the app's business logic.

  1. Clone the sample apps repository https://github.com/freshworks/marketplace-sample-apps
  2. Navigate to the directory - Play-Along/Exercises/fundamentals/level3
cd Play-Along/Exercises/fundamentals/level3

The app source code in the directory is a skeleton on top of which the complete app will be built in the subsequent steps. Files referred to in this tutorial are relative to the Play-Along/Exercises/fundamentals/level1 directory.

Make sure you have the iparam_test_data.json and oauth_config.json setup based on the learnings from the previous tutorial. Based on where the app runs, the OAuth callback URL in the Github app needs to be updated.
In case of :
Testing 👉 http://localhost:10001/auth/callback

Production 👉 https://oauth.freshdev.io/auth/callback

Let us now extend the boilerplate server.js to create issues in GitHub.

  1. Add the following object in the events array, this time to introduce an onTicketCreate event handler.
{ event: "onTicketCreate", callback: "onTicketCreateHandler" }
  1. Copy and paste the code for onTicketCreateHandler method with the code from the following function snippet under the place holder.
 onTicketCreateHandler: function (args) {
   $request.post(`https://api.github.com/repos/${args.iparams.github_repo}/issues`, {
     headers: {
       Authorization: 'token <%= access_token %>',
       'User-Agent': 'FreshHuddle Sample User Agent'
     },
     isOAuth: true,
     json: {
       title: args.data.ticket.subject,
       body: args.data.ticket.description_text,
       labels: [
         "bug"
       ]
     }
   }).then(data => {
     console.info('Successfully created the GitHub issue for the Freshdesk ticket');
     saveMapping({ ticketID: args.data.ticket.id, issueID: data.response.id, issueNumber: data.response.number }).then(function () {
       console.info('Successfully set the mapping in the db');
     }, error => {
       console.error('Error: Failed to set the mapping in the db');
       console.error(error);
     });
   }, error => {
     console.error('Error: Failed to create the GitHub issue for the Freshdesk ticket');
     console.error(error);
   });
 },
  1. Add the following helper code at L9 of server.js, to save the mapping data of the ticket and the GitHub issue in Data Storage.
function saveMapping(data) {
 var dbKey = String(`fdTicket:${data.ticketID}`).substr(0, 30);
 var dbKey2 = String(`gitIssue:${data.issueNumber}`).substr(0, 30);
 return Promise.all([$db.set(dbKey, { issue_data: data }), $db.set(dbKey2, { issue_data: data })])
}

We will revisit this function later to understand why it stores and creates two mappings.

  1. Use the fdk run command to run the app.
  2. Navigate to the simulation page on http://localhost:10001/web/test .
  3. Choose the onTicketCreate event from the drop-down list.
  4. Update the sample payload with your test data as necessary.
  5. Click the Simulate button, to simulate the event. An issue is created in GitHub for the ticket referenced in the simulation payload.


When the app is installed in the account, this is how it will look like :

  1. Add the following JSON objects, in the server.js file, in the events array, just before the onTicketCreate event mapping object. This will add two new event handlers (app event handlers) to our serverless app.
   { event: 'onAppInstall', callback: 'onInstallHandler' },
   { event: 'onAppUninstall', callback: 'onUnInstallHandler' },
  1. Add the following method after the onTicketCreateHandler method. This method registers a webhook with Github by using a unique target URL generated by the app. It also stores the target URL in Data Storage. We will shortly see why!
 /**
  * Handler for onAppInstall event
  *
  * A webhook url is created through generateTargetUrl function.
  * The generated url is registered with GitHub for "issues" related events and the same is triggered when an issues is created or modified.
  * On successful registration, the webhook URL is stored using $db
  *
  * @param {object} args - payload
  */
 onInstallHandler: function (args) {
  
   generateTargetUrl().then(function (targetUrl) {
    
     $request.post(`https://api.github.com/repos/${args.iparams.github_repo}/hooks`, {
       headers: {
         Authorization: 'token <%= access_token %>',
         'User-Agent': 'FreshHuddle Sample User Agent'
       },
       isOAuth: true,
       json: {
         name: 'web',
         active: true,
         events: [
           'issues'
         ],
         config: {
           url: targetUrl,
           content_type: 'json'
         }
       }
     }).then(data => {
       $db.set('githubWebhookId', { url: data.response.url }).then(function () {
         console.info('Successfully stored the webhook in the db');
         renderData();
       }, error => {
         console.error('Error: Failed to store the webhook URL in the db');
         console.error(error);
         renderData({ message: 'The webhook registration failed' });
       });
     }, error => {
       console.error('Error: Failed to register the webhook for GitHub repo');
       console.error(error);
       renderData({ message: 'The webhook registration failed' });
     })
   })
     .fail(function () {
       console.error('Error: Failed to generate the webhook');
       renderData({ message: 'The webhook registration failed' });
     });
 },
  1. Add the following method immediately after the installHandler method. The method will fetch the webhook URL from Data Storage when the app is uninstalled and request GitHub to delete the webhook. This ensures that GitHub stops sending notifications to the app after it is uninstalled.
 /**
  * Handler for onAppUninstall event
  *
  * Gets the webhook URL from the data storage through $db that was stored during installation
  * Then deregister the webhook from GitHub with the URL over REST API
  *
  * @param {object} args - payload
  */
 onUnInstallHandler: function () {
   $db.get('githubWebhookId').then(function (data) {
     $request.delete(data.url, {
       headers: {
         Authorization: 'token <%= access_token %>',
         'User-Agent': 'freshdesk',
         Accept: 'application/json'
       },
       isOAuth: true
     }).then(() => {
       console.info('Successfully deregistered the webhook for GitHub repo');
       renderData();
     }, () => renderData())
   }, error => {
     console.error('Error: Failed to get the stored webhook URL from the db');
     console.error(error)
     renderData({ message: 'The webhook deregistration failed' });
   });
 },

Upon successful registration, we will be able to see this:

  1. Add the following object in the events array, this time to introduce an external event handler.
{ event: 'onExternalEvent', callback: 'onExternalEventHandler' }
  1. Add the following onExternalEventHandler method after the onTicketCreateHandler method. This method fetches the ticket-id corresponding to the GitHub issue that was closed, from Data Storage. This is why we stored the second mapping from the Github issue to the Freshdesk ticket, when the issue was created . We can then close the ticket by a Request API invocation to Freshdesk.
 /**
  * Handler for onExternalEvent event
  *
  * Checks if the received issue event is of action 'opened' which is received for new issue creation.
  * Creates a ticket in freshdesk with the issue title and description.
  *
  * @param {object} payload - payload with the data from the third-party applications along with iparams and other metadata
  */
 onExternalEventHandler: function (payload) {
  
   const payloadData = typeof payload.data === 'string' ? JSON.parse(payload.data) : payload.data;
   if (payloadData.action === 'closed') {
     lookupTicketId(payloadData.issue.number).then(data => {
       $request.post('https://'+payload.domain + "/api/v2/tickets/" + data.issue_data.ticketID,
         {
           headers: {
             Authorization: '<%= encode(iparam.freshdesk_api_key) %>'
           },
           json: {
             status: 5
           },
           method: "PUT"
         }).then(() => {
           console.info('Successfully closed the ticket in Freshdesk');
         }, error => {
           console.error('Error: Failed to close the ticket in Freshdesk');
           console.error(error)
         })
     }, error => {
       console.error('Error: Failed to get issue data. Unable to create ticket');
       console.error(error);
     });
   } else {
     console.error('The action of the GitHub issue is not defined');
   }
 }

We are now ready to test the app thoroughly.

  1. Use the fdk run command to run the app.
  2. Navigate to the simulation page on http://localhost:10001/web/test .
  3. Choose onExternalEvent from the drop-down list.
  4. Replace the JSON object to simulate, with the following code snippet.
{
  "account_id": 12345,
  "event": "onExternalEvent",
  "timestamp": 1500351361762,
  "domain": "https://sample.freshdesk.com",
  "options": "freshdesk.onExternalEvent",
  "data": "{ \"action\": \"closed\", \"issue\": { \"id\": 1, \"number\": 2, \"body\": \"a new issue created for a bug\", \"title\": \"bug issue\"  } }",
  "headers": {
    "Content-Type": "application/json"
  }
}
  1. In the data property, in the issue object replace the id value with the id you created through an earlier simulation of the app. You should be able to locate the issue from the Issues page of the GitHub repository.
  2. Click on the Simulate button to simulate the event.
  3. Navigate to your Freshdesk portal and verify if the ticket is closed.

Yay! 🎉 You have successfully completed and tested your first advanced serverless app that reacts to product events, app setup events, and external events, as well as leverages OAuth for secure API requests and Data Storage to maintain mappings. Give yourself a pat on the back.👏