Architecture
Advantage
- No provision
- Horizontal scaling
- Cheaper: No pay when idle
- More reliable
Deployment
1. Create a DynamoDB table
- Table Name = mika-movie-db
- Partition key = movieId (S)
2. Create a Lambda Function
- mika-movie-function
- Use node.js 16.x
- Architecture: x86_64
- Execution role: select “Create a new role from AWS policy templates”
- mika-movie-role
- CloudWatchLogsFullAccess
- AmazonDynamoDBFullAccess
- In Advanced setting, if relevant, select the VPC, Subnets, SG ..
- Handler: index.handler
- Deploy
index.js
const AWS = require('aws-sdk');
AWS.config.update( {
region: 'us-east-1'
});
const dynamodb = new AWS.DynamoDB.DocumentClient();
const dynamodbTableName = 'mika-movie-db';
const healthPath = '/health';
const moviePath = '/movie';
const moviesPath = '/movies';
exports.handler = async function(event) {
console.log('Request event: ', event);
let response;
switch(true) {
case event.httpMethod === 'GET' && event.path === healthPath:
response = buildResponse(200);
break;
case event.httpMethod === 'GET' && event.path === moviePath:
response = await getMovie(event.queryStringParameters.movieId);
break;
case event.httpMethod === 'GET' && event.path === moviesPath:
response = await getMovies();
break;
case event.httpMethod === 'POST' && event.path === moviePath:
response = await saveMovie(JSON.parse(event.body));
break;
case event.httpMethod === 'PATCH' && event.path === moviePath:
const requestBody = JSON.parse(event.body);
response = await modifyMovie(requestBody.movieId, requestBody.updateKey, requestBody.updateValue);
break;
case event.httpMethod === 'DELETE' && event.path === moviePath:
response = await deleteMovie(JSON.parse(event.body).movieId);
break;
default:
response = buildResponse(404, '404 Not Found');
}
return response;
}
async function getMovie(movieId) {
const params = {
TableName: dynamodbTableName,
Key: {
'movieId': movieId
}
}
return await dynamodb.get(params).promise().then((response) => {
return buildResponse(200, response.Item);
}, (error) => {
console.error('!!! ERROR\nlogs: ', error);
});
}
async function getMovies() {
const params = {
TableName: dynamodbTableName
}
const allMovies = await scanDynamoRecords(params, []);
const body = {
movies: allMovies
}
return buildResponse(200, body);
}
async function scanDynamoRecords(scanParams, itemArray) {
try {
const dynamoData = await dynamodb.scan(scanParams).promise();
itemArray = itemArray.concat(dynamoData.Items);
if (dynamoData.LastEvaluatedKey) {
scanParams.ExclusiveStartkey = dynamoData.LastEvaluatedKey;
return await scanDynamoRecords(scanParams, itemArray);
}
return itemArray;
} catch(error) {
console.error('!!! ERROR\nlogs: ', error);
}
}
async function saveMovie(requestBody) {
const params = {
TableName: dynamodbTableName,
Item: requestBody
}
return await dynamodb.put(params).promise().then(() => {
const body = {
Operation: 'SAVE',
Message: 'SUCCESS',
Item: requestBody
}
return buildResponse(200, body);
}, (error) => {
console.error('!!! ERROR\nlogs: ', error);
})
}
async function modifyMovie(movieId, updateKey, updateValue) {
const params = {
TableName: dynamodbTableName,
Key: {
'movieId': movieId
},
UpdateExpression: `set ${updateKey} = :value`,
ExpressionAttributeValues: {
':value': updateValue
},
ReturnValues: 'UPDATED_NEW'
}
return await dynamodb.update(params).promise().then((response) => {
const body = {
Operation: 'UPDATE',
Message: 'SUCCESS',
UpdatedAttributes: response
}
return buildResponse(200, body);
}, (error) => {
console.error('!!! ERROR\nlogs: ', error);
})
}
async function deleteMovie(movieId) {
const params = {
TableName: dynamodbTableName,
Key: {
'movieId': movieId
},
ReturnValues: 'ALL_OLD'
}
return await dynamodb.delete(params).promise().then((response) => {
const body = {
Operation: 'DELETE',
Message: 'SUCCESS',
Item: response
}
return buildResponse(200, body);
}, (error) => {
console.error('!!! ERROR\nlogs: ', error);
})
}
function buildResponse(statusCode, body) {
return {
statusCode: statusCode,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
}
}
3. Build an API Gateway
- Build new REST API
- API Name = mika-movie-api
- Endpoint Type = Regional
- In Actions / Create Resource Name = health
- Check Enable API Gateway CORS
- Select health resource, create a GET Method
- Check Use Lambda Proxy integration
- Link to Lambda Function (type the function name)
- From root (/), create resource = movies
- Check Enable API Gateway CORS
- Select movies resource, create a GET Method
- Check Use Lambda Proxy integration
- Link to Lambda Function (type the function name)
- From root (/), create another resource = movie
- Check Enable API Gateway CORS
- In movie resource, create GET Method
- In movie resource, create POST Method
- In movie resource, create PATCH Method
- In movie resource, create DELETE Method
- Go to root (/) and Action / Deploy API and provide stage
- API Gateway generates the Invoke URL
Back to Lambda, check the triggers