Node.js : How to do error handing in AWS Lambda? - javascript

I am developing a REST API with AWS Lambda, API Gateway. Language is Node.js.
Please check the below code. There I am accepting a parameter called id then get data from the database and return back as the response.
const mysql = require('mysql2');
const PropertiesReader = require('properties-reader');
const prop = PropertiesReader('properties.properties');
const con = mysql.createConnection({
host : prop.get('server.host'),
user : prop.get("server.username"),
password : prop.get("server.password"),
port : prop.get("server.port"),
database : prop.get("server.dbname")
});
exports.getMilestoneStatusByID = (event, context, callback) => {
const { id } = event.queryStringParameters;
console.log("id", id);
// allows for using callbacks as finish/error-handlers
context.callbackWaitsForEmptyEventLoop = false;
const sql = "select * from milestone_status where idmilestone_status = ?";
con.execute(sql, [id], function (err, result) {
if (err) throw err;
var response = {
"statusCode": 200,
"headers": {
"Content-Type": "application/json"
},
"body": JSON.stringify(result),
"isBase64Encoded": false
};
callback(null, response)
});
};
However, in any case the id is null or no parameter provided, then the user gets the standard AWS error output, "message": "Internal server error". But what I need is to pass 404 status code, with the message Missing Parameters.
I tried to put the whole block inside the handler into a try-catch and tried to fire the following code inside the catch
var response = {
"statusCode": 404,
"headers": {
"Content-Type": "application/json"
},
"body": JSON.stringify("Missing Parameters"),
"isBase64Encoded": false
};
callback(null, response)
But non of these stopped me from getting the standard AWS error message, "message": "Internal server error".
How can I implement the error that I am looking for?

I am answering my own question. The issue was not with the returning of the error, but with the null check. This worked.
exports.getMilestoneStatusByID = (event, context, callback) => {
const params = event.queryStringParameters;
if (!params || params.id == null) {
var response = {
"statusCode": 404,
"headers": {
"Content-Type": "application/json"
},
"body": JSON.stringify({ error: "Missing Parameters" }),
"isBase64Encoded": false
};
callback(null, response)
} else {
const { id } = params;
// ...
}

Related

How to assert an error being thrown by mocked method using Jest in AWS Lambda

I have an AWS Lambda function called getTables that runs when you hit an API Gateway endpoint.
I would like to mock an exception being thrown by a section of my code that uses de AWS Glue SDK.
How can I properly mock an exception being thrown by a method and how should I assert it?
The test that I have doesn't seem to be working cause I get an unexpected exception which indicates that another part of the code is trying to use the response of the mocked code (meaning it didn't throw?):
it('should throw on db not found', async () => {
mockGetTables.mockReturnValue(new Error("EntityNotFoundException"));
await getTables(MOCK_REQUEST_EVENT_FAIL, mockContext, mockCallback);
expect(mockCallback).rejects.toEqual('EntityNotFoundException');
});
Here's my Lambda code:
export const getTables = async (
event: APIGatewayProxyEvent,
_context: Context,
callback: Callback<APIGatewayProxyResult>
) => {
console.log('Executing /getTables Activity.');
console.log(`/getTables event: ${JSON.stringify(event)}.`);
try {
const { queryStringParameters } = event;
let catalogIdParam: string = null;
let databaseNameParam: string = null;
let maxResultsParam: number = null;
let nextTokenParam: string = null;
if (queryStringParameters) {
const { catalogId, databaseName, maxResults, nextToken } = queryStringParameters;
catalogIdParam = catalogId || null;
databaseNameParam = databaseName || null;
maxResultsParam = maxResults ? parseInt(maxResults) : null;
nextTokenParam = nextToken || null;
}
const glueClientInstance: GlueClient = GlueClient.getInstance();
//I'd like to mock the following async method and throw "EntityNotFoundException"
//just like API Gateway (or lambda) would do.
const { TableList, NextToken }: GetTablesResponse = await glueClientInstance.getTables(
databaseNameParam,
catalogIdParam,
maxResultsParam,
nextTokenParam
);
const pandaUITableList: PandaUIGlueTable[] = convertToPandaUITableList(TableList);
callback(null, {
statusCode: 200,
body: JSON.stringify({
TableList: pandaUITableList,
NextToken,
}),
});
} catch (error) {
console.log(`An error ocurred while executing /getTables activity: ${JSON.stringify(error)}`);
if (error.code === 'EntityNotFoundException') {
callback(null, {
statusCode: 400,
body: JSON.stringify({
error: error.message,
}),
});
}
//Generic/CatchAll handler that I'll test later once I figure this one out
handlerApiError(error, callback);
}
};
For reference, this is the atual error I'm trying to mock and throw:
{
"message": "Database fdv_lin not found.",
"code": "EntityNotFoundException",
"time": "2022-03-29T00:47:07.475Z",
"requestId": "fff5d84c-59de-441d-a204-e08ede830931",
"statusCode": 400,
"retryable": false,
"retryDelay": 76.52610613917457
}
You can use jest's toHaveBeenCalledWith() or toHaveBeenLastCalledWith() to assert that the callback is called with the appropriate payload in the catch clause. In this case, it is the below example.
callback(null, {
statusCode: 400,
body: JSON.stringify({
error: error.message,
}),
});
Possible Solution
const mockCallback:Callback<APIGatewayProxyResult> = jest.fn();
it('should throw on db not found', async () => {
mockGetTables.mockReturnValue(new Error('EntityNotFoundException'));
await getTables(MOCK_REQUEST_EVENT_FAIL, mockContext, mockCallback);
expect(mockCallback).toHaveBeenCalledWith(null, {
statusCode: 400,
body: JSON.stringify({
error: {
message: 'Database fdv_lin not found.',
code: 'EntityNotFoundException',
time: '2022-03-29T00:47:07.475Z',
requestId: 'fff5d84c-59de-441d-a204-e08ede830931',
statusCode: 400,
retryable: false,
retryDelay: 76.52610613917457,
},
}),
});

POST http://localhost:3000/api/message 400 (Bad Request) xhr.js:210

declared Post request but it showing error "bad request .i want to send the message and restore all the old message but now i am not able to send the Post request:
const sendMessage = async(event) => {
if(event.key === "Enter" && newMessage) {
try {
const config ={
headers:{
"Content-Type": "application/json",
Authorization: `Bearer ${user.token}`,
},
};
setNewMessage("");
const {data} = await axios.post("/api/message", {
content: newMessage,
chatId: selectedChat,
}, config);
console.log(data);
setMessages([...messages, data])
} catch (error) {
toast({
title: "Error Occured!",
description: "Failed to send the Message",
status: "error",
duration: 5000,
isClosable: true,
position: "bottom",
});
}
}
};
the Message controller declared in backend as shown below and it contains the separate chatId and content but the frontend api is not directing to this showing it as error:
const sendMessage = asyncHandler(async(req,res) =>{
const { content , chatId} = req.body;
if(!content || !chatId) {
console.log("Invalid data passed into request");
return res.sendStatus(400);
}
var newMessage = {
sender : req.user._id,
content: content,
chatId:chatId,
}
try {
var message = await Message.create(newMessage);
message = await message.populate("sender" ,"firstname pic");
message = await message.populate("chat").execPopulate();
message = await User.populate(message,{
path: "chat.users",
select:"firstname pic email",
});
await Chat.findByIdAndUpdate(req.body.chatId, {
latestMessage:message,
});
res.json(message);
} catch (error) {
res.status(400);
throw new Error(error.message);
}
});
chat Routes in serverjs to route the chat from backend to frontend given as:
app.use("/api/message",messageRoutes);

Why my NULL check is always failing in Node.js?

I am developing a REST API with AWS Lambda, API Gateway, and Node.js.
Here is my code:
const mysql = require('mysql2');
const PropertiesReader = require('properties-reader');
const prop = PropertiesReader('properties.properties');
const con = mysql.createConnection({
host : prop.get('server.host'),
user : prop.get("server.username"),
password : prop.get("server.password"),
port : prop.get("server.port"),
database : prop.get("server.dbname")
});
exports.getMilestoneStatusByID = (event, context, callback) => {
const { id } = event.queryStringParameters;
if(id==null)
{
var response = {
"statusCode": 404,
"headers": {
"Content-Type": "application/json"
},
"body": JSON.stringify("Missing Parameters"),
"isBase64Encoded": false
};
callback(null, response)
}
else{
console.log("id", id);
// allows for using callbacks as finish/error-handlers
context.callbackWaitsForEmptyEventLoop = false;
const sql = "select * from milestone_status where idmilestone_status = ?";
con.execute(sql, [id], function (err, result) {
if (err) throw err;
var response = {
"statusCode": 200,
"headers": {
"Content-Type": "application/json"
},
"body": JSON.stringify(result),
"isBase64Encoded": false
};
callback(null, response)
});
}
};
In my code I am accepting a parameter called id, then get data from the database and return back as the response. If the id is not provided, i am sending an error response.
But, in any case the id is null or no parameter provided, then the user gets the standard AWS error output, "message": "Internal server error". In console, th But what I need is to pass 404 status code, with the message Missing Parameters.
e below error get printed.
Lambda returned empty body!
Invalid lambda response received: Invalid API Gateway Response Keys: {'trace', 'errorMessage', 'errorType'} in {'errorType': 'TypeError', 'errorMessage': "Cannot destructure property 'id' of 'event.queryStringParameters' as it is null.", 'trace': ["TypeError: Cannot destructure property 'id' of 'event.queryStringParameters' as it is null.", ' at Runtime.exports.getMilestoneStatusByID [as handler] (/var/task/source/milestone-status/milestonestatus-getbyid.js:17:11)', ' at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)']}
How can I implement null check and send the error that I am looking for?
The error says that event.queryStringParameters is undefined, not the .id in it. You can extend your check to also check for event.queryStringParameters, although you should check the documentation if that's even normal:
exports.getMilestoneStatusByID = (event, context, callback) => {
const params = event.queryStringParameters;
if (!params || params.id == null) {
var response = {
"statusCode": 404,
"headers": {
"Content-Type": "application/json"
},
"body": JSON.stringify("Missing Parameters"),
"isBase64Encoded": false
};
callback(null, response)
} else {
const { id } = params;
// ...
}

How to send a html email in AWS Lambda node js returning a well formed response for AWS api gateway

I need to create an api that sends html emails through amazon SES. I created properly my ses credentials and I want to create an AWS lambda in javascript (nodejs). Due the lack of good AWS documentation for sending an email using javascript, I need to know how to create a lambda that sends an email and responds with a proper message to AWS api gateway.
The following was code written in javascript. Works perfect in AWS lambda and works great when is invoked from AWS Api gateway (no malformed message errors):
var aws = require("aws-sdk");
var ses = new aws.SES({
accessKeyId: "Your SES Access Key",
secretAccesskey: "Your Secret key",
region: "us-east-1" // CHANGE with the region where you configured SES
});
exports.handler = function(event, context, callback) {
var requestPath = JSON.parse(JSON.stringify(event.pathParameters));
var requestBody = JSON.parse(event.body);
var responseBody = {};
var response = {};
if (
requestBody &&
requestBody.emailFrom &&
requestBody.subject &&
requestBody.htmlBody
) {
var emailTo = requestPath.emailto;
var emailFrom = requestBody.emailFrom;
var subject = requestBody.subject;
var htmlBody = requestBody.htmlBody;
} else {
responseBody = {
result: "fail",
resultCode: 400,
description:
"Incorrect Parameters. Mandatory: emailFrom, subject and bodyHTML"
};
response = {
statusCode: 400,
headers: {
"Access-Control-Allow-Origin": "*"
},
body: JSON.stringify(responseBody),
isBase64Encoded: false
};
callback(null, response);
}
var emailParams = {
Destination: {
ToAddresses: [emailTo]
},
Message: {
Body: {
Html: {
Data: htmlBody
}
},
Subject: {
Data: subject
}
},
Source: emailFrom
};
var email = ses.sendEmail(emailParams, function(err, data) {
var resultCode = 200;
if (err) {
var responseBody = {
result: "FAIL",
resultCode: 500,
description: "Error sending email: " + err
};
resultCode = 500;
} else {
var responseBody = {
result: "OK",
resultCode: 200,
description: "Email sent successfully"
};
}
response = {
statusCode: resultCode,
headers: {
"Access-Control-Allow-Origin": "*"
},
body: JSON.stringify(responseBody),
isBase64Encoded: false
};
callback(null, response);
});
};

Creating an Asana Task using a POST http request

I'm trying to use the asana-api to create a Task using a POST http request but I keep getting a 400 bad request as a response.
I managed to get data from the Asana-api using ( a GET request ), but I'm having trouble sending data to Asana with ( a POST request )
I'm using the 'request' module to do the api call
here's the error message :
`{"errors":[{
"message":"Could not parse request data,invalid JSON",
"help":"For more information on API status codes and how to handle them,
read the docs on errors: https://asana.com/developers/documentation/getting-started/errors"}
]}`
Here's my code:
testTask(){
var taskName = "Test Name for a Test Task"
var workspaceID = "123456789"
var projectID = "123456789"
var assigneeID = "123456789"
var parentID = null
this.createTask(taskName, workspaceID, projectID, assigneeID, parentID)
}
createTask(taskName, workspaceID, projectID, assigneeID, parentID){
var token = "0/1234abcd5678efgh9102ijk"
var bearerToken = "Bearer " + token
var task = {
data: {
assignee: "me",
notes: "test test test test",
workspace: workspaceID,
name: taskName,
projects: [projectID],
parent: parentID
}
}
var options = {
"method" : "POST",
"headers" : {"Authorization": bearerToken},
"contentType": "application/json",
"payload" : JSON.stringify(task)
}
try {
var url = "https://app.asana.com/api/1.0/tasks";
request.post(url, options, function optionalCallback(err, httpResponse, body) {
if (err) {
return console.error('upload failed:', err);
}
console.log('Upload successful! Server responded with:', body);
});
}
catch (e) {
console.log(e);
}
}
I also tried a different implementation :
createTask(){
var token = "0/1234abcd5678efgh9102ijk"
var bearerToken = "Bearer " + token
var options = {
"method" : "POST",
"headers" : {"Authorization": bearerToken},
}
try {
request.post("https://app.asana.com/api/1.0/tasks?workspace=1234567&projects=765534432&parent=null&name=taskName&assignee=me", options, function optionalCallback(err, httpResponse, body) {
if (err) {
return console.error('upload failed:', err);
}
console.log('Upload successful! Server responded with:', body);
});
}
catch (e) {
console.log(e);
}
}
Based on the examples provided by the request module, it appears that your options object uses payload as a key, but it should be body.

Categories

Resources