Deploy a Function to Lambda Using the Node.js AWS SDK
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:
- Create a new REST API
- Add a resource to the REST API
- Add a
GET
method to the resource - 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'