Search code examples
aws-lambdanodesaws-api-gatewayalexa-skills-kit

Single AWS Lambda function to respond to Alexa skill requests, and return a JSON object, depending on how it is called


I am trying to use the same AWS Lambda function to do two things with the same DynamoDB dataset.

(a) Provide Alexa Skill Responses

I have already implemented this, and the Skill is operating correctly. I am using NodeJS and the Amazon Skills Kit version 2. The last few lines of my index.js are as follows:

const skillBuilder = Alexa.SkillBuilders.standard();
exports.handler = skillBuilder
  .addRequestHandlers(
    LaunchRequest,
    HelpIntent,
    // .... various other intents
    UnhandledIntent
  )
  .addErrorHandlers(ErrorHandler)
  .lambda();

(b) Provide a JSON object summarising some database contents

The NodeJS code I have set up for the Alexa skill, processes DynamoDB data in order to provide the skill responses. I want to use the same codebase (i.e. the same Lambda function) to produce summaries for my own benefit. I don't want to copy-paste pieces of the Lambda function into a separate function. I would much rather have the same code do both tasks, in order to keep everything in step.

My problem is that the structure of the Lambda code for returning this JSON response to my request is as follows, from https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html:

 'use strict';
 console.log('Loading hello world function');

 exports.handler = function(event, context, callback) {
    let name = "you";
    let city = 'World';
    // etc ... more code ...
    callback(null, response);
 };

Both pieces of code assign a function to `exports.handler`

I think I want to achieve this effect:

if ( /* test whether being called by Alexa or by API Gateway */ )
  { /* Make the Alexa Response */ }
else
  { /* Construct the JSON data summary response  */ }

From what I can make out, each Lambda function (in the sense of each Amazon Resource Number for a Lambda function) has to have only one entry file, i.e. I can't make Lambda start index_Alexa.js versus index_JSON.js.

I am open to any suggestion on how to get the same Lambda function (in the sense of the same JS file or package of files) do both things.


Solution

  • I question the usefulness of the approach somewhat, and the following has some potential for optimization remaining, but one way of accomplishing this is to declare exports.handler as a simple wrapper that invokes the correct previously-declared handler function based on a condition you can test in the request.

    // set up handler for alexa
    
    const skillBuilder = Alexa.SkillBuilders.standard();
    const alexa_handler = skillBuilder
      .addRequestHandlers(
        LaunchRequest,
        HelpIntent,
        // .... various other intents
        UnhandledIntent
      )
      .addErrorHandlers(ErrorHandler)
      .lambda();
    
    // set up handler for API Gateway
    
    const api_gateway_handler = function(event, context, callback) {
        let name = "you";
        let city = 'World';
        // etc ... more code ...
        callback(null, response);
     };
    
    // for each invocation, choose which of the above to invoke
    
    exports.handler = function(event, context, callback) {
        if(/* some test that is true only for API Gateway */)
        {
            api_gateway_handler(event, context, callback);
        }
        else
        {
            alexa_handler(event, context, callback);
        }
    };
    

    The line if(/* some test that is true only for API Gateway */) is something you'll need to work out, but I suspect something like this might work:

    if(event.input && event.input.requestContext && event.input.requestContext.apiId)
    

    The API Gateway docs suggest that this value would always be present.