Deploy a Function to Lambda Using the Node.js AWS SDK

Feb 19, 2020

The AWS API has an endpoint for deploying a function on Lambda. With a little bit of work, you can upload a Lambda function using the AWS SDK for Node.js. Here's how you can upload and run a Lambda function in 3 steps:

1. Upload the Function Bundle to S3

Unfortunately, the AWS API requires you to store your bundled Lambda function as a .zip file on S3, and that S3 bucket needs to be in the same AWS region as your Lambda function.

You can read more about uploading objects to S3 in Node.js here. Here's the abridged version.

First, suppose you have a simple test.js file that contains a handler function:

exports.handler = async function(event, context) {
  return { statusCode: 200, body: 'Hello, World' };
};

Lambda will execute this function for you and return "Hello World". But first, you need to archive this test.js file into a .zip file and upload it to S3. To bundle a zip file, you can use the adm-zip package on npm:

const AdmZip = require('adm-zip');
const AWS = require('aws-sdk');

const file = new AdmZip();
file.addFile('test.js', Buffer.from(`
  exports.handler = async function(event, context) {
    return { statusCode: 200, body: 'Hello, World' };
  };
`));

file.writeZip('./test.zip');

// Make sure the configs are set!
AWS.config.update({
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  region: 'us-east-1'
});

const s3 = new AWS.S3();
await new Promise((resolve, reject) => {
  s3.upload({
    Bucket: awsBucket, // Make this your AWS bucket
    Body: fs.createReadStream('./test.zip'),
    Key: 'test.zip'
  }, (err, data) => err == null ? resolve(data) : reject(err));
});

2. Create a Lambda Function

Now that the file is on S3, you can create a Lambda function and invoke it using the AWS.Lambda() helper:

const AWS = require('aws-sdk');
const promisify = require('util').promisify;

AWS.config.update({
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  region: 'us-east-1'
});

const lambda = new AWS.Lambda();

// Actually create the function with the given name and runtime.
const opts = {
  FunctionName: 'nodetest',
  Runtime: 'nodejs12.x',
  // Whatever role, doesn't matter
  Role: 'add actual role that starts with `arn:aws:iam::` here',
  // `test` is for `test.js`, and `handler` is for `exports.handler`.
  Handler: 'test.handler',
  Code: {
    'S3Bucket': awsBucket,
    'S3Key': 'test.zip'
  }
};
const fn = await promisify(lambda.createFunction).call(lambda, opts);
functionArn = fn.FunctionArn; // The "id" of the lambda function

// Let API Gateway call this Lambda function
await promisify(lambda.addPermission).call(lambda, {
  FunctionName: 'nodetest',
  StatementId: 'doesntmatter',
  Action: 'lambda:InvokeFunction',
  Principal: 'apigateway.amazonaws.com'
});

const res = await promisify(lambda.invoke).call(lambda, {
  FunctionName: 'nodetest'
});
res.Payload; // '{"statusCode":200,"body":"Hello, World"}'

For convenience, the above code uses Node.js' util.promisify() helper, since the AWS SDK doesn't currently support promises. Learn more about util.promisify() here.

3. Create an API Gateway to Access the Function via HTTP

So now you have a Lambda function that you can invoke via the AWS SDK. But what about invoking it via HTTP? That's what you need the AWS API Gateway API for. You need to create a new REST API and add an integration to it.

Step by step, you need to:

  1. Create a new REST API
  2. Add a resource to the REST API
  3. Add a GET method to the resource
  4. Hook up the GET method to call Lambda

Here's the full script:

this.timeout(5000);
const AWS = require('aws-sdk');
const promisify = require('util').promisify;

AWS.config.update({
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  region: 'us-east-1'
});

const gateway = new AWS.APIGateway();

// Create a new API
const api = await promisify(gateway.createRestApi).call(gateway, {
  name: 'My Test API'
});
const restApiId = api.id;

// Create a new endpoint (resource) at `/test/
const resources = await promisify(gateway.getResources).call(gateway, { restApiId });
const resource = await promisify(gateway.createResource).call(gateway, {
  restApiId,
  // Parent resource is the "root" resource
  parentId: resources.items[0].id,
  pathPart: 'test'
});
await promisify(gateway.putMethod).call(gateway, {
  restApiId,
  resourceId: resource.id,
  httpMethod: 'GET',
  authorizationType: 'NONE'
});

// Configure the endpoint to use the Lambda function
await promisify(gateway.putIntegration).call(gateway, {
  restApiId,
  resourceId: resource.id,
  httpMethod: 'GET',
  integrationHttpMethod: 'POST',
  type: 'AWS_PROXY',
  uri: `arn:aws:apigateway:us-east-1:lambda:path//2015-03-31/functions/${functionArn}/invocations`
});
await promisify(gateway.createDeployment).call(gateway, { restApiId, stageName: 'prod' });

await promisify(gateway.putMethodResponse).call(gateway, {
  restApiId,
  resourceId: resource.id,
  httpMethod: 'GET',
  statusCode: '200'
});
await promisify(gateway.putIntegrationResponse).call(gateway, {
  restApiId,
  resourceId: resource.id,
  httpMethod: 'GET',
  statusCode: '200'
});

// Now call the function using Axios!
const axios = require('axios');

const res = await axios.get(`https://${api.id}.execute-api.us-east-1.amazonaws.com/prod/test`);
res.data; // 'Hello, World'

Did you find this tutorial useful? Say thanks by starring our repo on GitHub!

More Node Tutorials