How to use koa ctx body for multiple responses? - javascript

I am new in node with koa and postgresql. I have a created a user login api but i'm getting 404 not found error. My queries and checks are working as i checked on console but ctx.body not working. How i can handle multiple responses with koa ctx.body? Don't know why no ctx.body is working. How we can solve this issue?
Hope you understand my issue.
router.post('/userLogin', async (ctx) => {
var email = ctx.request.body.email;
var password = ctx.request.body.password;
if (
!email ||
!password
) {
ctx.response.status = 400;
ctx.body = {
status: 'error',
message: 'Please fill all the fields'
}
} else {
await ctx.app.pool.query("SELECT * FROM users WHERE email = $1",
[`${email}`],
async (err, result) => {
if(err){
console.log(err);
throw err;
}
if (result) {
await bcrypt.compare(password, result.rows[0].password).then(function (res) {
if (res === true) {
ctx.body = {
status: 200,
message: "User login successfully",
data: result.rows[0],
};
}else{
ctx.body = {
status: 400,
message: "Incorrect password",
}
}
});
}else{
ctx.body = {
status: 400,
message: "Invalid email",
}
}
});
}
});

In regards to your 404 issue:
HTTP 404 implies that your route does not exist yet. Please make sure your router.post('/userLogin') router is actually getting registered via app.use(router.routes()).
Refering to your question in regards to using ctx.body for multiple responses:
You can set ctx.body multiple times but only the last one will be used in the response.
For example:
ctx.body = 'Hello'
ctx.body = 'World'
This example will respond with World.
You can either concatenate your values in order to have them sent as one string/object, or use streaming where you control the read-stream-buffer. Check https://stackoverflow.com/a/51616217/1380486 and https://github.com/koajs/koa/blob/master/docs/api/response.md#responsebody-1 for documentation.

Related

Catch errors when using async/await

I have a register function inside my Express application to create a new user. Inside this function there are a few tasks: create the user in Auth0, send an email, send a response to the client.
I want to be able to catch the errors coming from Auth0 or Postmark to send back specific errors to the client and log them to the console. I though I could achieve this by adding a catch to an await function (I want to avoid a waterfall of .then() and .catch() blocks). This sends the error to the client but doesn't stop the code from executing. The email part is still trying to execute while the user object is undefined and I'm getting the error Cannot set headers after they are sent to the client.
How can I fix this by keeping the async/await functionality and keep the seperate error handling for each action?
Register function
export const register = asyncHandler(async (req, res, next) => {
// Create user in Auth0
const user = await auth0ManagementClient.createUser({
email: req.body.email,
password: generateToken(12),
verify_email: false,
connection: 'auth0-database-connection'
}).catch((error) => {
const auth0_error = {
title: error.name,
description: error.message,
status_code: error.statusCode
}
console.log(auth0_error);
if(error.statusCode >= 400 && error.statusCode < 500) {
return next(new ErrorResponse('Unable to create user', `We were unable to complete your registration. ${error.message}`, error.statusCode, 'user_creation_failed'));
} else {
return next(new ErrorResponse('Internal server error', `We have issues on our side. Please try again`, 500, 'internal_server_error'));
}
});
// Send welcome mail
await sendWelcomeEmail(user.email)
.catch((error) => {
const postmark_error = {
description: error.Message,
status_code: error.ErrorCode
}
console.log(postmark_error);
if(error.statusCode >= 400 && error.statusCode < 500) {
return next(new ErrorResponse('Unable to send welcome email', `We were unable to send a welcome email to you`, error.statusCode, 'welcome_email_failed'));
} else {
return next(new ErrorResponse('Internal server error', `We have issues on our side. Please try again`, 500, 'internal_server_error'));
}
});
res.status(201).json({
message: 'User succesfully registered. Check your mailbox to verify your account and continue the onboarding.',
data: {
user
}
});
});
asyncHandler.js
const asyncHandler = fn => ( req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
export default asyncHandler;
I'd use try/catch blocks, but declare the user variable outside the try scope.
async function handler(req, res, next) {
let user;
try {
user = await auth0ManagementClient.createUser(...);
} catch (error) {
return next(new ErrorResponse(...));
}
try {
await sendWelcomeEmail(user.email);
} catch (error) {
return next(new ErrorResponse(...));
}
res.status(201).json(...);
}
return will only terminate the current function. Here, the function that gets terminated by the return is the .catch() callback.
In your example and if you want to stick to using Promise.then().catch() you can check for the user value as the catch() callback will return its value in it.
The easier way would be to use try/catch blocks to interrupt the whole controller with the return statement.

Dealing with Promises and error handling in Next.js

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.

How do I send more than one error at a time when validating?

When I create a POST request for I need to validate the following fields: first_name, last_name, mobile_number, reservation_date, reservation_time and people(party size).
Right now I have a middleware function that checks if any of the fields are missing:
function hasProperties(...properties) {
return function (res, req, next) {
const { data = {} } = res.body;
try {
properties.forEach((property) => {
if (!data[property]) {
const error = new Error(`${property}`);
error.status = 400;
throw error;
}
});
next();
} catch (error) {
next(error);
}
};
}
Then in my controller:
const hasAllProps = hasProperties(
'first_name',
'last_name',
'mobile_number',
'reservation_date',
'reservation_time',
'people'
);
This is working great however I have to add additional validation to several of the fields. I have 2 additional functions: one is making sure the people field is a number, and the other is making sure the reservation_date is a date:
const validPeople = (req, res, next) => {
const { people } = req.body;
if (Number.isInteger(people)) {
return next();
}
next({ status: 400, message: 'people' });
};
const validDate = (req, res, next) => {
const { reservation_date } = req.body;
if (reservation_date instanceof Date) {
return next();
}
next({ status: 400, message: 'reservation_date' });
};
Then I pass them all in to my exports:
create: [hasAllProps, validDate, validPeople]
I am only ever able to send one error at a time, in this case its validDate because it comes before validPeople in the exports array. I am unable to throw all of my errors into an array because I need to response with:
status: 400, message: '<the-specific-field>'
Is there a way to individually send all these error messages?
As the other response has stated, if you're trying to send multiple responses, that's not possible. You can, however, construct an array of the errors.
You could technically pass data between middleware... (Can I send data via express next() function?)
... but my recommendation would be to be to try to merge them into a single middleware. For example, hasAllProps, validPeople, and validDate should ideally all take in a req and return null or an error. Then you could do:
function validDate(req) {
return null;
}
function validOtherProp(req) {
return 'error_here';
}
function anotherValidation(req) {
return 'second_error';
}
const errorCollectorMiddleware = (...validators) =>
(req, res, next) => {
const errors = validators.map(v => v(req)).filter(error => error !== null);
if (errors.length > 0) {
next({
status: 400,
errors
})
} else {
next();
}
}
// This is how you construct a middleware
const middleware = errorCollectorMiddleware(validDate, validOtherProp, anotherValidation);
// And here's a test. You wouldn't do this in your actual code.
console.log(middleware(null, null, console.log))
/*
{
"status": 400,
"errors": [
"error_here",
"second_error"
]
}
*/
With HTTP/S you cannot have one request two responses. The client system sends the request, receives the response and does not expect a second response.

Firebase/Telegram Post request

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.

What is the appropriate way of handling user (action) related errors?

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.

Categories