- Create a folder for your project.
git init
if you want to keep a repo!serverless create --template aws-nodejs-typescript
- Voilá! You have the basic scaffolding!
- Now you can try with
aws-csharp
astemplate
- Notice the
serverless yml
file. - Notice the
webpack-plugin
configuration inside. We're using webpack! (Less things to learn!) - Let's add the
region
and thestage
parameters under theprovider
section.
region: ${opt:region, 'eu-west-1'}
stage: dev
memorySize: 512
${}
syntax:${opt:name}
for options from command line${self:section:name}
to reference the same file${env:varName}
for environment variables.
- Variables from other files can be referenced
- Run
serverless deploy
- Go to your AWS console!
- We create the source code of
handler.ts
in a new foldersrc/fetchAndUpload
import { APIGatewayProxyHandler } from 'aws-lambda';
import fetch from 'node-fetch';
import { S3 } from 'aws-sdk'
import { v4 as uuid } from 'uuid';
import 'source-map-support/register';
interface IFetchImageRequest {
imageUrl: string;
}
export const fetchAndUpload: APIGatewayProxyHandler = async (event, context) => {
console.log('event', event);
console.log('context', context);
const input: IFetchImageRequest = JSON.parse(event.body);
console.log('input:', input);
console.log('url:', input.imageUrl);
const s3 = new S3();
await fetch(input.imageUrl)
.then(
response => {
console.log('fetch success, response', response);
return response;
}).then(
response => {
console.log('buffering response');
return response.buffer();
}
).then(async buffer => {
const uploadParms = {
Bucket: process.env.UPLOAD_BUCKET,
Key: `${uuid()}.jpg`,
Body: buffer,
};
console.log('uploadParms', uploadParms);
await s3.putObject(uploadParms).promise();
console.log('object put in bucket');
});
return {
statusCode: 200,
body: JSON.stringify({
message: 'Go Serverless Webpack (Typescript) v1.0! Your function executed successfully!',
input: event
}, null, 2),
};
}
- We need some extra dependencies
yarn add uuid
yarn add --dev @types/uuid
yarn add node-fetch
yarn add --dev @types/node-fetch
- We add the new function to
serverless.yml
fetchAndUpload:
handler: src/fetchAndUpload/handler.fetchAndUpload
events:
- http:
method: post
path: fetch
- Example request body:
{
"imageUrl": "https://pisces.bbystatic.com/image2/BestBuy_US/images/products/6220/6220794_sa.jpg"
}
- Custom section for the name
custom:
bucketName: ${self:provider.stage}-fetch-and-upload
- Configure the environment variable so it can be read from code
environment:
UPLOAD_BUCKET: ${self:custom.bucketName}
- Add the bucket resource
resources:
Resources:
S3BucketImagesUpload:
Type: AWS::S3::Bucket
Properties:
BucketName: ${self:custom.bucketName}yml
- Add the IAM policy in provider section
iamRoleStatements:
- Effect: "Allow"
Action:
- s3:*
Resource:
- 'arn:aws:s3:::${self:custom.bucketName}/*'
- Create lambda code, with
S3Event
- Configure function with
s3
event referencing the bucket
publishEvent:
handler: src/publishEvent/handler.publishEventFromS3
events:
- s3:
bucket: ImagesUpload
event: s3:ObjectCreated:*
- Add the permission for the bucket to invoke the lambda. This is done in the resources. This is not in the
iamStatements
because is the bucket invoking the lambda, and not the lambda using the bucket
PublishEventLambdaPermissionImagesUploadS3:
Type: 'AWS::Lambda::Permission'
Properties:
FunctionName:
'Fn::GetAtt':
- PublishEventLambdaFunction
- Arn
Principal: 's3.amazonaws.com'
Action: 'lambda:InvokeFunction'
SourceAccount:
Ref: AWS::AccountId
SourceArn: 'arn:aws:s3:::${self:custom.bucketName}'
- Modify the code of the lambda
import { S3Event, Context } from 'aws-lambda';
import { SNS } from 'aws-sdk';
interface IEventPayload {
objectKey: string;
objectSize: number;
timestamp: string;
}
export const publishEventFromS3 = async (event: S3Event, context: Context) => {
console.log('s3 event handled!', event);
console.log('s3 event context', context);
const sns = new SNS({ region: 'eu-west-1' });
await Promise.all(event.Records.map(async r => {
try {
console.log('processing record', r);
const payload: IEventPayload = {
objectKey: r.s3.object.key,
objectSize: r.s3.object.size,
timestamp: r.eventTime
};
const message = JSON.stringify(payload);
console.log('message', message);
const inputRequest: SNS.PublishInput = {
Message: message,
TopicArn: process.env.UPLOAD_EVENT_TOPIC_ARN
}
console.log('request', JSON.stringify(inputRequest));
let response = await sns.publish(inputRequest).promise();
console.log('done!', response);
} catch (e) {
console.log('exception!', e);
}
}));
}
- Configure topic name in custom section
topicName: ${self:provider.stage}--upload-publish
- Configure env variable with topic ARN using pseudo-parameters plugin
topicArn: 'arn:aws:sns:#{AWS::Region}:#{AWS::AccountId}:${self:custom.topicName}'
- Configure topic resource
UploadEventTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: ${self:custom.topicName}
- Configure
iamStatement
so lambdas can publish in SNS (and create the topic first)
- Effect: "Allow"
Action:
- sns:Publish
- sns:CreateTopic
Resource:
- 'Fn::Join':
- ':'
- - 'arn:aws:sns'
- Ref: 'AWS::Region'
- Ref: 'AWS::AccountId'
- ${self:custom.topicName}
- Define queue and deadletter queue
Type: AWS::SQS::Queue
Properties:
QueueName: ${self:custom.queueName}-DeadLetters
MessageRetentionPeriod: 1209600 # 14 days in seconds
- Define policy for SNS to publish to SQS
UploadEventQueuePolicy:
Type: AWS::SQS::QueuePolicy
Properties:
Queues:
- Ref: UploadEventQueue
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal: "*"
Action:
- sqs:SendMessage
Resource: "*"
Condition:
ArnEquals:
aws:SourceArn: ${self:custom.topicArn}
- Define subscription
UploadEventSubscription:
Type: AWS::SNS::Subscription
Properties:
TopicArn: ${self:custom.topicArn}
Endpoint:
Fn::GetAtt:
- UploadEventQueue
- Arn
RawMessageDelivery: true
Protocol: sqs
- Define lambda handler code. In this case we'll just read the event
import { SQSEvent, Context } from 'aws-lambda'
export const processQueue = async (event: SQSEvent, context: Context) => {
console.log('event', event);
console.log('context', context);
// Here we can, for example, store info in a DynamoDB table
}
- Define function in
serverless.yml
linked to SQS event
processQueue:
handler: src/processQueue/handler.processQueue
events:
- sqs:
batchSize: 1
arn:
Fn::GetAtt:
- UploadEventQueue
- Arn