Update(2019-12-01) @hayd Andy Hayden created deno-lambda which is a very good starting point for writing Lambda function in Deno. The repository is well maintained. I highly recommend you try that tool as well.
This guide describes how to write AWS Lambda function in deno at the time of this writing (deno v0.4.0). Many things could be improved later and you'll be able to skip some of these steps in future.
AWS Lambda supports Custom Runtimes. You can write your own runtime in any language and use it in AWS Lambda. In this guide, I'll show you how to write a custom runtime in deno and deploy it to AWS.
Prerequisites
This guide describes the 2 ways (Part 1 and Part 2) to create a lambda function in deno. In both cases, you need the followings:
- AWS IAM user which can create a lambda function
- AWS Role for lambda function (You need a role which has "AWSLambdaBasicExecutionRole" policy)
- In this article, I suppose it has the name
arn:aws:iam::123456789012:role/lambda-role
. Please replace it with your own one on your side.
- In this article, I suppose it has the name
- AWS CLI installed
See the Prerequisites section of AWS Lambda Custom Runtime tutorial for more details.
Part 1. Do everything on your own
Build Custom Deno
The current official deno binary doesn't run on the operating system of Lambda because of the glibc compatibility issue. You need to build your own deno for it. What you need to do is to build deno in this image which is the exact image Lambda uses. (In addition, you need to set use_sysroot = false
flag on .gn
file. I don't understand this flag, but anyway it works. See the comment in the above issue if you're interested in details.)
If you want to avoid building deno on your own, please download the binary from here which I built based on a recent version of deno with the above settings. I confirmed this works in the Lambda environment.
Write Custom Runtime
You need to write a custom runtime in deno. A custom runtime is a program which is responsible for setting up the Lambda handler, fetching events from Lambda runtime API, invoking the handler, sending back the response to Lambda runtime API, etc. The entrypoint of a custom runtime have to be named bootstrap
. The example of such program is like the below (This is Deno program wrapped by Bash script.)
#!/bin/sh
set -euo pipefail
SCRIPT_DIR=$(cd $(dirname $0); pwd)
HANDLER_NAME=$(echo "$_HANDLER" | cut -d. -f2)
HANDLER_FILE=$(echo "$_HANDLER" | cut -d. -f1)
echo "
import { $HANDLER_NAME } from '$LAMBDA_TASK_ROOT/$HANDLER_FILE.ts';
const API_ROOT =
'http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/';
(async () => {
while (true) {
const next = await fetch(API_ROOT + 'next');
const reqId = next.headers.get('Lambda-Runtime-Aws-Request-Id');
const res = await $HANDLER_NAME(await next.json());
await (await fetch(
API_ROOT + reqId + '/response',
{
method: 'POST',
body: JSON.stringify(res)
}
)).blob();
}
})();
" > /tmp/runtime.ts
DENO_DIR=/tmp/deno_dir $SCRIPT_DIR/deno run --allow-net --allow-read /tmp/runtime.ts
import { $HANDLER_NAME } from '$LAMBDA_TASK_ROOT/$HANDLER_FILE.ts';
In this line, you import lambda function from the task directory. $LAMBDA_TASK_ROOT
is given by Lambda environment. $HANDLER_NAME
and $HANDLER_FILE
are the first and second part of handler
property of your lambda which you'll set through AWS CLI. If you set the handler property function.handler
, for example, then the above line becomes import { handler } from '$LAMBDA_TASK_ROOT/function.ts'
. So your lambda function need to be named function.ts
and it needs to export handler
as the handler in that case.
(async () => {
while (true) {
...
}
})();
This block creates the loop of event handling of Lambda. A single event is processed on each iteration of the loop.
const next = await fetch(API_ROOT + 'next');
const reqId = next.headers.get('Lambda-Runtime-Aws-Request-Id');
These 2 lines fetches the event from Lambda runtime API and stores the request id.
const res = await $HANDLER_NAME(await next.json());
This line invokes the lambda handler with the given event payload and stores the result.
await (await fetch(
API_ROOT + reqId + '/response',
{
method: 'POST',
body: JSON.stringify(res)
}
)).blob();
This line sends back the result to Lambda runtime API.
DENO_DIR=/tmp/deno_dir $SCRIPT_DIR/deno run --allow-net --allow-read /tmp/runtime.ts
This line starts the runtime script with net
and read
permissions. If you want to more permissions, you can add here the options you want. DENO_DIR=/tmp/deno_dir
part is very important. Because Lambda environment doesn't allow you to write to the file system except /tmp
, you need to set DENO_DIR
somewhere under /tmp
.
Write Lambda function
Now you need to write your lambda function in deno. The example looks like the below:
export async function handler(event) {
return {
statusCode: 200,
body: JSON.stringify({
version: Deno.version,
build: Deno.build
})
};
}
This lambda function returns a simple object which contains status code 200 and deno's version information as body.
Deploy
Now you have 3 files deno
, bootstrap
(bash script), and function.ts
(deno script). These are all files you need to run your Lambda function. You need to zip them:
$ zip function.zip deno bootstrap function.ts
Then you can deploy it like the below:
$ aws lambda create-function --function-name deno-func --zip-file fileb://function.zip --handler function.handler --runtime provided --role arn:aws:iam::123456789012:role/lambda-role
(Note: Replace arn:aws:iam::123456789012:role/lambda-role
to your own role's arn.)
--runtime provided
option means this lambda uses a custom runtime.
Test
You can invoke the above lambda like the below:
$ aws lambda invoke --function-name deno-func response.json
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
$ cat response.json
{"statusCode":200,"body":"{\"version\":{\"deno\":\"0.4.0\",...}}}
Part 2. Use the shared layer
AWS Supports the Lambda Layer. A lambda layer is a ZIP archive that contains libraries, a custom runtime, or other dependencies. I published the above deno
binary and bootstrap
script as a public layer. You can reuse it as a custom deno runtime.
In this case, what you need to do is just to write a lambda function in deno and deploy it to AWS.
Create Deno Lambda Function using Public Deno Runtime
An example function.ts
looks like the below (The same as the above):
export async function handler(event) {
return {
statusCode: 200,
body: JSON.stringify({
version: Deno.version,
build: Deno.build
})
};
}
Then zip it and deploy it:
$ zip function-only.zip function.ts
$ aws lambda create-function --function-name deno-func-only --layers arn:aws:lambda:ap-northeast-1:439362156346:layer:deno-runtime:13 --zip-file fileb://function-only.zip --handler function.handler --runtime provided --role arn:aws:iam::123456789012:role/lambda-role
(Note: Replace arn:aws:iam::123456789012:role/lambda-role
to your own role's arn.)
Where the arn arn:aws:lambda:ap-northeast-1:439362156346:layer:deno-runtime:13
is a public lambda layer which implements deno runtime. The --layers arn:aws:lambda:ap-northeast-1:439362156346:layer:deno-runtime:13
option specifies this lambda function uses it as the shared layer.
Test it
You should be able to invoke the above lambda function like the below:
$ aws lambda invoke --function-name deno-func-only response.json
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
$ cat response.json
{"statusCode":200,"body":"{\"version\":{\"deno\":\"0.4.0\",...}}}
That's it. Thank you for reading.
References
All examples are available in this repository.
Top comments (4)
Thanks so much for the work here. I noted a few things that I've bumped into while trying to get this to work.
See github.com/hayd/deno-lambda#deno_d...
It's wonderful to see people working with Deno out in the wild.
I've ported this code to support error handling and the latest deno:
github.com/hayd/deno-lambda