Issue getting simple Fetch working in Netlify Functions - node.js

I've been working through the workshops for Netlify functions and have stumbled getting a simple Fetch response to work. When running the sample at:
https://github.com/DavidWells/netlify-functions-workshop/blob/master/lessons-code-complete/use-cases/5-fetching-data/functions/node-fetch/node-fetch.js
const fetch = require('node-fetch')
const API_ENDPOINT = 'https://cat-fact.herokuapp.com/facts'
exports.handler = async (event, context) => { let response
try {
response = await fetch(API_ENDPOINT)
// handle response
} catch (err) {
return {
statusCode: err.statusCode || 500,
body: JSON.stringify({
error: err.message
})
} }
return {
statusCode: 200,
body: JSON.stringify({
data: response
}) } }
but I just get the following response:
{"data":{"size":0,"timeout":0}}
The build process is working fine, and I've tried other end points but all give the same results.

Here is a working version of your original code using node-fetch and the callback.
// import fetch from 'node-fetch';
const fetch = require('node-fetch')
const checkStatus = (res) => {
if (res.ok) { // res.status >= 200 && res.status < 300
return res.json()
} else {
throw new Error(res.statusText);
}
}
exports.handler = async function(event, context, callback) {
try {
const response = await fetch('https://cat-fact.herokuapp.com/facts')
const data = await checkStatus(response)
callback(null, {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
} catch (error) {
callback(error)
}
}
Note: Netlify functions are just AWS functions and you can read about the AWS Lambda Function Handler in the AWS docs
For async functions, you return a response, error, or promise to the runtime instead of using callback.
I like to use the method recommended in the docs, since it is supported and save the callback for non-async functions.
Here is a working version returning a promise to the async function instead.
// import fetch from 'node-fetch';
const fetch = require('node-fetch')
exports.handler = async (event, context) => {
return new Promise((resolve, reject) => {
fetch('https://cat-fact.herokuapp.com/facts')
.then(res => {
if (res.ok) { // res.status >= 200 && res.status < 300
return res.json();
} else {
resolve({ statusCode: res.status || 500, body: res.statusText })
};
})
.then(data =>{
const response = {
statusCode: 200,
headers: { 'content-type': 'application/json' },
body: JSON.stringify(data)
}
resolve(response);
})
.catch(err => {
console.log(err)
resolve({ statusCode: err.statusCode || 500, body: err.message })
})
})
}
This is just an example and there are other ways to handle the errors, but this example resolved the response rather than rejecting. It is not tested as a production example, but gives you an idea of the difference.
Here are some examples of Netlify functions.

On the handle response line you’ll want to await the json result:
response = await response.json();
Check the MDN article for Using Fetch for more details.

In the end I switched to the Axios library and it worked first time:
const axios = require('axios')
const API_ENDPOINT = 'https://jsonplaceholder.typicode.com/todos/1'
exports.handler = async (event, context) => {
let response
try {
response = await axios.get(API_ENDPOINT)
} catch (err) {
return {
statusCode: err.statusCode || 500,
body: JSON.stringify({
error: err.message
})
}
}
return {
statusCode: 200,
body: JSON.stringify({
data: response.data
})
}
}

Related

Error sending file from Slack to WhatsAPp

I am trying to send files from Slack to WhatsApp using Node.js. For Slack, I am using Bolt and for WhatsApp API I'm using WATI.
This is the code that retrieves the file information as soon as it is uploaded to slack. I am retrieving the file's permalink and passing it to the load function. I tried using file permalink_public it still gives the same error.
index.js
const wa = require("./wati")
const request = require('request-promise')
url = file_permalink
async function load(uri, path, number) {
const options = {
uri: uri,
encoding: null,
};
const body = await request(options)
try {
await wa.sendMedia(body, path, number).then().catch(e => console.log(e))
}
catch (e) {
console.log(e)
}
}
load(url, "file.png", phone_number)
wati.js
var request = require('request');
const sendMedia = async (file, filename, senderID) => {
var options = {
'method': 'POST',
'url': 'https://' + process.env.URL + '/api/v1/sendSessionFile/' + senderID,
'headers': {
'Authorization': process.env.API,
},
formData: {
'file': {
'value': file,
'options': {
'filename': filename,
'contentType': null
}
}
}
};
request(options, function (error, response) {
if (error) throw new Error(error);
console.log(response.body);
});
}
Error
{"result":false,"info":"Failed to send file"}
I tried with axios as well but it throws
TypeError: Cannot read properties of undefined (reading 'name')
index.js
let body = await axios.get(url, {
responseType: 'stream',
}).then(async () => {
// console.log(body)
}).catch(function (error) {
if (error.response) {
// Request made and server responded
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// The request was made but no response was received
console.log(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
});
await wa.sendMedia(body.data, "file.png", phone_number).then().catch(e => {
console.log(e)
})

Cannot get data from backend while updating

Here's the code in react that I am using to get the data from database.
const getData = async (e) => {
const res = await fetch(`${process.env.REACT_APP_BASE_URL}/edit/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const data = await res.json();
console.log(data);
if (res.status === 422 || !data) {
console.log("Error");
} else {
setValues(data);
console.log("Data Edited successfully");
}
};
useEffect(() => {
getData();
}, []);
Here's the patch request
router.patch("/edit/:id", async (req, res) => {
try {
const { id } = req.params;
const updateUser = await Crud.findByIdAndUpdate(id, req.body, {
new: true,
});
console.log(updateUser);
res.status(201).json(updateUser);
} catch {
res.status(422).json(error);
}
});
I want to update the data in my application but I cannot get the data from the database. So can anyone tell what the problem is
From frontend, you are calling GET request and from your backend, you're receiving as a patch how it works pls do the same method on both hands

Forward requests to internal service lambda AWS

I need to forward a http request recieved to a lambda function to another url (ECS service) and send back the response.
I manage to achieve this behaviour with the following code:
exports.handler = async (event) => {
const response = {
statusCode: 302, // also tried 301
headers: {
Location: 'http://ec2-xx-yy-zz-ww.us-west-x.compute.amazonaws.com:2222/healthcheck'
}
};
return response;
};
It seems to work, but this changes the original url (which is like toing.co:5500) to the redirected aws url.
So I tried to create an async request inside lambda that would query and return the response:
const http = require('http');
const doPostRequest = () => {
const data = {};
return new Promise((resolve, reject) => {
const options = {
host: 'http://ec2-xx-yy-zz-ww.us-west-x.compute.amazonaws.com:5112/healthcheck',
port: "2222",
path: '/healthcheck',
method: 'POST'
};
const req = http.request(options, (res) => {
resolve(JSON.stringify(res.statusCode));
});
req.on('error', (e) => {
reject(e.message);
});
//do the request
req.write(JSON.stringify(data));
req.end();
});
};
exports.handler = async (event) => {
await doPostRequest()
.then(result => console.log(`Status code: ${result}`))
.catch(err => console.error(`Error doing the request for the event: ${JSON.stringify(event)} => ${err}`));
};
but I get a bad gateway (502) error for this. How can I implment a simple forwarder for post requests (with a message body)?
The issue was that the response from the lambda function was a plain json string and not html (as pointed out by #acorbel), hence the load balancer could not process the response, resulting in a 502 error.
The solution was to add http headers and a status code to the response:
const http = require('http')
let response = {
statusCode: 200,
headers: {'Content-Type': 'application/json'},
body: ""
}
let requestOptions = {
timeout: 10,
host: "ec2-x-xxx-xx-xxx.xx-xx-x.compute.amazonaws.com",
port: 2222,
path: "/healthcheck",
method: "POST"
}
let request = async (httpOptions, data) => {
return new Promise((resolve, reject) => {
let req = http.request(httpOptions, (res) => {
let body = ''
res.on('data', (chunk) => { body += chunk })
res.on('end', () => { resolve(body) })
})
req.on('error', (e) => {
reject(e)
})
req.write(data)
req.end()
})
}
exports.handler = async (event, context) => {
try {
let result = await request(requestOptions, JSON.stringify({v: 1}))
response.body = JSON.stringify(result)
return response
} catch (e) {
response.body = `Internal server error: ${e.code ? e.code : "Unspecified"}`
return response
}
}

Request Promise unhandled reject

Basically I have to do a POST request an try to save this data on an API after I get the return of this POST and save this information on a database.
So, I did a call to a promise (saveProcessedData or saveErrorData) inside another promise, works as expected although is trowing out a warning:
Unhandled rejection StatusCodeError: 400
const sendData = async(data, item) => {
let payload = [],
dataProcessed = [];
let options = {
method: data.endpoint.method,
url: api._url + data.endpoint.url,
headers:
{
'Postman-Token': api._postmanToken,
'cache-control': api._cache,
'x-ccasset-language': 'en',
Authorization: data.token
},
body: item,
json: true
};
return new Promise((resolve, reject) => {
api._request(options, async (error, response, body) => {
if(!response.body.errorCode && response.statusCode === 200) {
payload = {
body: response.body,
type: data.req.body.type
}
dataProcessed = await db.saveProcessedData(payload);
} else {
payload = {
body: item,
type: data.req.body.type,
description: response.body.message
}
dataProcessed = await db.saveErrorData(payload);
}
if (error) {
reject(error)
}
resolve(dataProcessed);
});
});
}
How Can I catch this error?
It's better if you don't mix callbacks & promises the way you're doing it. You're getting that error because you're not handling the errors correctly inside api._request callback.
Wrap your code inside the callback in a try/catch, because that's how you handle exceptions in async functions.
new Promise(async resolve => {
await Promise.reject() // Unhandled Promise Rejection
}).catch(console.error) // not catched
Should be:
new Promise(async resolve => {
try {
await Promise.reject()
resolve();
} catch(e) {
reject(e)
}
}).catch(console.error) // catched
In any case, since you're wrapping api._request in a Promise, it would be better to do:
const sendData = async(data, item) => {
// ...
const { error, response, body } = await new Promise((resolve, reject) => {
api._request(options, (error, response, body) => resolve({ error, response, body }))
})
if(!response.body.errorCode && response.statusCode === 200) {
payload = {
body: response.body,
type: data.req.body.type
}
dataProcessed = await db.saveProcessedData(payload);
} else {
payload = {
body: item,
type: data.req.body.type,
description: response.body.message
}
dataProcessed = await db.saveErrorData(payload);
}
return dataProcessed;
}
And attach a .catch handler to .sendData
Aside from that in your code, if error is truthy, you're rejecting and then resolving, nothing wrong will happen in that case, but it's better to use return reject(). A Promise, can't be rejected & resolved.
if (error) {
return reject(error)
}
resolve(dataProcessed);
If you're using request, you can use request-promise or request-promise-native which are Promise wrappers around request package.
Just formatting
const sendData = async (data, item) => {
const options = {
method: data.endpoint.method,
url: api._url + data.endpoint.url,
headers: {
'Postman-Token': api._postmanToken,
'cache-control': api._cache,
'x-ccasset-language': 'en',
Authorization: data.token
},
body: item,
json: true
};
return Promise.resolve()
.then(() =>
new Promise((resolve, reject) => {
api._request(options, (error, response) => {
if (!response.body.errorCode && response.statusCode === 200) {
return resolve(
{
body: response.body,
type: data.req.body.type
}
);
}
return reject({
body: item,
type: data.req.body.type,
description: error || response.body.message
});
})
}))
.then(payload => db.saveProcessedData(payload))
.catch(payload => db.saveErrorData(payload))
}
When you call sendData() you can use Promise.prototype.catch() to catch exceptions that occurred in your promise, including any reject():
sendData(myData, myItem)
.then(result => {
//do stuff with result
}
.catch(error => {
//handle error
};

Throwing errors/response outside exports.handler

I've just started using AWS Lambda/Node.JS/API Gateway with API Lambda Proxy integration, and I'm struggling with something that should be simple. Handling error from outside exports.handler.
I've developed all my code, it's all working fine, however, I'm struggling to handle errors. I'm using async/await, no callback.
Basically, my code looks like something like this:
exports.handler = async (event, context) => {
const sampleVar = await sampleFunction();
const response = {
statusCode: 200,
body: JSON.stringify(sampleVar),
isBase64Encoded: false
};
return response;
}
Great, right? My problem is that all my "real code" is being developed outside the exports.handler in different functions, something like:
async function sampleFunction() {
const test = 123;
return anotherFunction(test);
}
async function anotherFunction() {
const test2 = 'blablabla';
return test2;
}
Now, let's suppose that an error happens on my sampleFunction and I need to throw the error right away. I could just go throw { statusCode: 400, etc, etc };, however, if I throw the error, I get a "500 Internal Server Error" as my response, no matter what. I can only proper break my code if I handle the error from within my exports.handler function. In that case, I could go return { statusCode: 400, etc, etc }; or context.fail(), etc, and everything would be alright.
My question is: is there any way to "break" my code and send a proper response from outside my exports.handler? Or maybe externally call my exports.handler to return a specific response?
so, i would write this in the comments, but the markup is a bit poor. I would do something like the below:
exports.handler = async (event, context) => {
return await sampleFunction().then(craftOk).catch(craftErr);
}
function craftOk(res) {
return {
statusCode: 200,
body: JSON.stringify(res),
isBase64Encoded: false
};
}
function craftBad(err) {
return {
statusCode: 400,
body: err.message,
isBase64Encoded: false
}
}
for fun, I wrote a test script to validate return from catch on async/await...maybe it will help
function test(toggle) {
return (toggle) ? Promise.resolve("yay") : Promise.reject(new Error("boo"))
}
async function main1() {
let res = await test(true).then( res => {
return {
statusCode: 200,
body: res,
isBase64Encoded: false
}
})
.catch(err => {
return {
statusCode: 400,
body: err.message,
isBase64Encoded: false
}
})
return res
}
async function main2() {
let res = await test(false).then( res => {
return {
statusCode: 200,
body: res,
isBase64Encoded: false
}
})
.catch(err => {
return {
statusCode: 400,
body: err.message,
isBase64Encoded: false
}
})
return res
}
main1().then(console.log)
main2().then(console.log)

Resources