I am new to React and Next.js I am trying to send an email via sendGrid from a contact form. I have combined a couple of tutorials to get what I want but I am clearly not understanding something.
Using Next.js I have a contact form /pages/contact.js onSubmit calls /pages/api/sendMail which imports a function sendMailToMe() from utils/sendMailToMe.js
The code works and sends the email but I cannot seem to pass the result from sendMailToMe() back to sendMail.js
/utils/sendMailToMe.js
const sendMailToMe = async (
fullName,
formMessage,
email
) => {
const mail = require('#sendgrid/mail');
mail.setApiKey(SENDGRID_API_KEY);
const msg = {
to: 'mike#mydomain.com',
from: 'mike#mydomain.com',
templateId: 'd-3481ff06ea924128baa7c16a5a7f4840',
dynamicTemplateData: {
subject: 'Testing Templates',
fullName: fullName,
message: formMessage,
},
};
mail.send(msg)
.then((response) => {
console.log('in response')
console.log(response[0].headers)
return response[0].statusCode
})
.catch((error) => {
console.log("there was an error")
console.error(error)
return 'test'+error
})
//return response;
}
export { sendMailToMe };
This is imported and called as follows to pages/api/sendMail.js
import { sendMailToMe } from "../../utils/sendMailToMe";
export default async function handler(req, res) {
if (req.method === "POST") {
const { email, fullName, message,test } = req.body;
if (
typeof (email || fullName || test || message) === "undefined"
) {
console.log(" ************* Invalid Data received ************ ");
return res
.status(400)
.send({ error: "bad request, missing required data!" });
} else {
// Data received as expected
console.log('Calling sendMailToMe')
const sendGridResult = await sendMailToMe(
fullName,
message,
email
)
.then((response)=>{console.log(response)}) //res.status(200).send({test: 'test'})})
.catch((err) =>{ console.log(err)})//res.status(400).send({error:"Error in Sendgrid", errMsg:err})})
// API returns here regardless of outcome
res.status(200).send({test: 'returning here on error or success'})
}
}else{
res.status(400).send({ error: "Must use POST method" });
}
//res.status(400).send({ error: "bad request somehow" });
}
I am trying to get the result of mail.send() back to the api so I can return the proper response. Right now sendMail.js returns 200 even if mail.send() fails. The console logs the response or error in sendMailToMe() but I can't get the response or error back to sendmail.js. Any pointers in the right direction appreciated.
Related
1.I'm working on an backend API but at some point I need to get user data from another API. I am trying to use Axios to make http request in order to do that. The request return the result in the browser as expected but the problem is that I can't display console log in the terminal. It doesn't show anything even though I asked the program to do so. Is there a problem probably with my code?
2.Error message =>>> POST http://localhost:8000/api/register 400 (Bad Request) Error: Request failed with status code 400`
const handleSubmit = async () => {
//e.preventDefault();
try
{
// console.log(name, email, password, secret);
const { data } = await axios.post("http://localhost:8000/api/register", {
name,
email,
password,
secret,
});
setOk(data.ok); //useState component
}
catch (error) {
**strong text**
console.log(error.response.data);
}
}
import User from '../models/user'
//import{ hashPassword, comparePassword } from '../helpers/auth'
export const register = async (req,res) => {
//console.log('Register endpoint =>', req.body)
//to make this work make express.json is applied in the above middleware
//console.log error to debug code
const {name, email, password, secret} = req.body;
//validation
if(!name) return res.status(400).send('Name is required')
if(!password || password.length < 6) return res.status(400).send('Password is
short
or password is not entered')
if(!secret) return res.status(400).send('Answer is required')
//The above code is for validation purpose to make sure data is correctly
entered
const exist = await User.findOne({email })
if(exist) return res.status(400).send('Email is taken')
}
.catch(error => {
console.log(error)
})
May be catching error on your axios is wrong try this
I'm sending push messages using FCM through Firebase Functions. The messages are being sent properly, but I'm getting the 408 time-out error after the message is sent. I'm suspecting it might have to do with the unregistered tokens not being cleaned up because:
if I were to send another message to the same device, the same timeout occurs and
the only error message I get from the Firebase log is Function execution took 60002 ms, finished with status: 'timeout'.
exports.sendMessage = functions.https.onRequest(async (request, response) => {
const {
sender,
recipient,
content,
docID
} = request.body
functions.logger.log(
"docID:",
docID,
);
// Get the list of device notification tokens.
let deviceTokens; let ref;
try {
ref = admin.firestore().collection("deviceToken").doc(recipient);
const doc = await ref.get();
if (!doc.exists) {
console.log("No such document!");
response.status(500).send(e)
} else {
console.log("doc.data():", doc.data());
deviceTokens = doc.data().token;
}
} catch (e) {
response.status(500).send(e)
}
let senderProfile;
try {
senderProfile = await admin.auth().getUser(sender);
console.log("senderProfile", senderProfile);
} catch (e) {
console.log(e);
response.status(500).send(e)
}
// Notification details.
let payload = {
notification: {
title: senderProfile.displayName,
body: content,
sound: "default",
},
data: {
uid: senderProfile.uid,
displayName: senderProfile.displayName,
docID,
messageType: "status"
}
};
functions.logger.log(
"deviceTokens", deviceTokens,
"payload", payload,
);
// Send notifications to all tokens.
const messageResponse = await admin.messaging().sendToDevice(deviceTokens, payload);
// For each message check if there was an error.
messageResponse.results.forEach((result, index) => {
const error = result.error;
if (error) {
functions.logger.error(
"Failure sending notification to",
deviceTokens[index],
error,
);
// Cleanup the tokens who are not registered anymore.
if (error.code === "messaging/invalid-registration-token" ||
error.code === "messaging/registration-token-not-registered") {
const updatedTokens = deviceTokens.filter((token) => token !== deviceTokens[index]);
console.log("updatedTokens", updatedTokens);
ref.update({
token: updatedTokens,
})
.catch(function(e) {
console.error("Error removing tokens", e);
response.status(500).send(e)
});
}
}
});
response.status(200)
});
I'm unsure why the following isn't cleaning up the unregistered tokens:
const updatedTokens = deviceTokens.filter((token) => token !== deviceTokens[index]);
ref.update({
token: updatedTokens,
})
You always have to end HTTP functions with response.status(200).send() or response.status(200).end(). In the above function, you have response.status(200) so you have to end it either with response.status(200).send() or response.status(200).end(). Please check the documentation if it helps.
So basically, I'm trying to separate my code that handles data (mongoose) from my express Router code, since I might want to use it elsewhere too.
The first thing I did was, I got rid of the res.json() calls, since I don't want the code to only work returning a http response. I want it to return data, so I can then return that data from my router as a http response, but still use it as regular data elsewhere.
Here is a function I wrote to get data from mongoose.
module.exports.user_login = data => {
console.log(data);
ModelUser.findOne({email: data.email}).then(user => {
if(!user){
console.log({email: 'E-mail address not found'});
return {
status: response_code.HTTP_404,
response: {email: 'E-mail address not found'}
}
}
bcrypt.compare(data.password, user.password).then(isMatch => {
if(!isMatch){
console.log({password: 'Invalid password'});
return {
status: response_code.HTTP_400,
response: {password: 'Invalid password'}
}
}
const payload = {
id: user.id,
email: user.email
};
jwt.sign(
payload,
config.PASSPORT_SECRET,
{
expiresIn: "1h"
},
(err, token) => {
console.log({
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
});
return {
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
}
}
);
});
});
};
When this code gets executed in my route like so:
router.post("/login", (req, res) => {
const { errors, isValid } = validateLogin(req.body);
if(!isValid) return res.status(400).json(errors);
console.log("ret", dm_user.user_login(req.body));
});
The log says the return value of user_login() is undefined, even though right before the return statement in user_login() I am logging the exact same values and they are getting logged.
Before I changed it to a log, I tried to store the return value in a variable, but obviously that remained undefined as well, and I got the error: 'Cannot read propery 'status' of undefined' when trying to use the value.
I am definitely missing something..
Well you have an small callback hell here. It might be a good idea to go with async / await and splitting up your code into smaller chunks instead of putting everyhing in 1 file.
I rewrote your user_login function:
const { generateToken } = require("./token.js");
module.exports.user_login = async data => {
let user = await ModelUser.findOne({ email: data.email });
if (!user) {
console.log({ email: "E-mail address not found" });
return {
status: response_code.HTTP_404,
response: { email: "E-mail address not found" }
};
}
let isMatch = await bcrypt.compare(data.password, user.password);
if (!isMatch) {
console.log({ password: "Invalid password" });
return {
status: response_code.HTTP_400,
response: { password: "Invalid password" }
};
}
const payload = {
id: user.id,
email: user.email
};
let response = await generateToken(
payload,
config.PASSPORT_SECRET,
response_code
);
return response;
};
I have moved your token signing method into another file and promisfied it:
module.exports.generateToken = (payload, secret, response_code) => {
return new Promise((res, rej) => {
jwt.sign(
payload,
secret,
{
expiresIn: "1h"
},
(err, token) => {
if (err) {
rej(err);
}
res({
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
});
}
);
});
};
Now you need to change your router function into an async:
router.post("/login", async (req, res) => {
const { errors, isValid } = validateLogin(req.body);
if(!isValid) return res.status(400).json(errors);
let result = await dm_user.user_login(req.body);
console.log(result);
});
In addition: You get undefined because you return your value to an callback function
I also would seperate your routes from your controllers instead of writing your code inside an anonymous function
Please notice that whenever you are trying to return any value you are always present in the callback function and that is definitely not going to return any value to its intended place.
There are a couple of things you can improve about your code :
1.Donot use jwt inside your code where you are making database calls, instead move it where your routes are defined or make a separate file.
2.If you are intending to re-use the code, I would suggest you either use async-await as shown in the answer above by Ifaruki or you can use something like async.js. But the above shown approach is better.
Also always use 'error' field when you are making db calls like this:
ModelUser.findOne({email: data.email}).then((error,user) => {
I'm currently trying to build a firebase cloud function (using express) where:
- I check if a use exists in database
- Sends a message to the telegram API depending on whether it exists or not
The issue is, when I try to run the function, Firebase logs is able to get the console.log telling me if the user exists, but will not send to telegram. The error log says:
[2020-02-15T10:41:34.568Z] #firebase/database: FIREBASE WARNING:
Exception was thrown by user callback. Error: Can't set headers after
they are sent.
at validateHeader (_http_outgoing.js:491:11)
at ServerResponse.setHeader (_http_outgoing.js:498:3)
at ServerResponse.header (/srv/node_modules/express/lib/response.js:771:10)
at ServerResponse.send (/srv/node_modules/express/lib/response.js:170:12)
at ServerResponse.json (/srv/node_modules/express/lib/response.js:267:15)
at ServerResponse.send (/srv/node_modules/express/lib/response.js:158:21)
at firebase.database.ref.child.once.snapshot (/srv/index.js:59:40)
at onceCallback (/srv/node_modules/#firebase/database/dist/index.node.cjs.js:4933:51)
at /srv/node_modules/#firebase/database/dist/index.node.cjs.js:4549:22
at exceptionGuard (/srv/node_modules/#firebase/database/dist/index.node.cjs.js:698:9)
Could anyone please help? Thank you!
app.post("/", async (req, res) => {
const isTelegramMessage =
req.body &&
req.body.message &&
req.body.message.chat &&
req.body.message.chat.id &&
req.body.message.from &&
req.body.message.from.first_name &&
req.body.update_id;
const user_id = req.body.message.from.id
firebase.initializeApp(firebaseConfig);
let myUser;
const chat_id = req.body.message.chat.id;
const {
first_name
} = req.body.message.from;
// Check User Exists
firebase
.database()
.ref("/telegramUsers")
.child(req.body.message.from.id)
.once("value", snapshot => {
if (snapshot.exists()) {
myUser = true;
console.log("exists!", myUser);
return res.status(200).send({
method: "sendMessage",
chat_id,
text: `Welcome Back ${first_name}`
});
} else {
myUser = false;
console.log("does not exist!");
return res.status(200).send({
method: "sendMessage",
chat_id,
text: `Hello ${first_name}`
});
}
});
return res.status(200).send({
status: "not a telegram message"
});
});
As others have commented, you're returning and writing a response to the caller twice. Since send starts writing the body of the HTTP response, you can't call status (or even send) after you've already called it on res before.
In code that'd look something like this:
app.post("/", async (req, res) => {
const isTelegramMessage =
req.body &&
req.body.message &&
req.body.message.chat &&
req.body.message.chat.id &&
req.body.message.from &&
req.body.message.from.first_name &&
req.body.update_id;
const user_id = req.body.message.from.id
firebase.initializeApp(firebaseConfig);
let myUser;
const chat_id = req.body.message.chat.id;
const {
first_name
} = req.body.message.from;
// Check User Exists
if (isTelegramMessage) {
return firebase
.database()
.ref("/telegramUsers")
.child(req.body.message.from.id)
.once("value")
.then(snapshot => {
if (snapshot.exists()) {
myUser = true;
console.log("exists!", myUser);
return res.status(200).send({
method: "sendMessage",
chat_id,
text: `Welcome Back ${first_name}`
});
} else {
myUser = false;
console.log("does not exist!");
return res.status(200).send({
method: "sendMessage",
chat_id,
text: `Hello ${first_name}`
});
}
});
} else {
return res.status(200).send({
status: "not a telegram message"
});
}
});
The changes:
Now only checks if the user exists if isTelegramMessage is true.
Now returns the result from the database read operation.
Use once().then(), so that the return res.status().send()... bubbles up. This ensures that Cloud Functions will not terminate the function before both the database load and the sending of the response are done.
Though the second and third bullets are not strictly needed for HTTPS triggered Cloud Functions (as those terminate when you send a response), I still recommend to use them, to make it easier to port/copy-paste the code to Cloud Functions types that are triggered by other events.
I'm scratching my head trying to figure out the best way to handle errors from specific user actions. I'm using Express as my web server and even though it works, for the most part, I am getting not-so-useful, generic error messages. For instance, in the code below, I get the Request failed with status code 400 error message on the client side for the first two conditions/exceptions in the try block.
How do I approach this in the following example?
Express Server-side Controller
async function voteInPoll (req, res) {
const { category, pollId } = req.params;
const { name, choiceId, voterId } = req.body;
try {
const poll = await Poll.findById(pollId);
// Check if user has already voted in poll
const hasVoted = poll.votedBy.some(voter => voter.equals(voterId));
if (!voterId) { // Check if user is authenticated
res
.sendStatus(400)
.json({ message: 'Sorry, you must be logged in to vote' });
} else if (voterId && hasVoted) {
res
.sendStatus(400)
.json({ message: 'Sorry, you can only vote once' });
} else {
const choice = await poll.choices.id(choiceId);
const votedChoice = { name, votes: choice.votes + 1 };
await choice.set(votedChoice);
await poll.votedBy.push(voterId);
poll.save();
res
.sendStatus(200)
.json({
message: 'Thank you for voting. Find other polls at: ',
poll,
});
}
} catch (error) {
throw new Error(error);
}
}
React/Redux Action
export const voteInPoll = (category, pollId, votedItem, voterId) => async dispatch => {
try {
const response = await axios.post(
`http://localhost:3050/polls/${category}/${pollId}/vote`,
{
...votedItem,
voterId,
}
);
dispatch({ type: store.polls.VOTE_SUCCESS, payload: response.data.poll });
} catch (error) {
console.log(error);
dispatch({ type: store.polls.VOTE_FAILURE, payload: error.message });
}
};
Edit
What I find rather bizarre is I get the expected error response sent, as seen below under the Network tab of Chrome's Developer tools.
You should not be using res.sendStatus(statusCode) because of the following as defined in the docs here:
Sets the response HTTP status code to statusCode and send its string representation as the response body.
The key thing about the above is:
and send its string representation as the response body.
So doing: res.sendStatus(400).json({ message: 'Oops 400!'}) will not give you a JSON response which is what you're expecting, but simply display:
Bad Request
Which is the string representation of the 400 HTTP status code: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_Client_errors
What you need to do is replace all of your res.sendStatus(..).json(..) with res.status(...).json(...) like so:
if (!voterId) { // Check if user is authenticated
res
.status(400)
.json({ message: 'Sorry, you must be logged in to vote' });
} else if (voterId && hasVoted) {
res
.status(400)
.json({ message: 'Sorry, you can only vote once' });
} else {
// ...
}
and so on.