To simplify the situation I'll just post the following controller for an express route which interactions with a Postgres Database. My question is about error handling. If an error occurs it will be caught within the catch clause. But how can I access the errors thrown by the database queries itself. If I make several await several queries and one of them fails I need probably to restore stuff in the database? For example if the insertion of the user in the user table is a success, but the following query of inserting the user in another table fails, I need to delete the user from the user table again. How does one model such flows?
//
// Register User
//
export const registerUser = async (request, response, next) => {
try {
const usersWithSameMail = await client.query(`SELECT * FROM public.users WHERE email = '${user.email}'`);
if(usersWithSameMail.rows.length > 0){
return response.status(403).json({"code": "ERROR", "message": "Email is already registered"})
} else {
await client.query(`
INSERT INTO public.users(first_name, last_name, email, password)
VALUES ('${user.first_name}', '${user.last_name}', '${user.email}', crypt('${user.password}', gen_salt('bf', 8)));
`);
// more await statements...
return response.status(200).json({"code": "INFO", "message": "Verification mail sent to user"});
}
} catch (error) {
return response.status(500).json({"code": "ERROR", "message": "Error occured while registering the user. Please try again."});
}
}```
You can use middlewares chaining your routes handler. In order to it work, you will have to change your current working code to use Single-responsibility principle. Do only one responsability per middleware and chain all handlers to work as one.
Lets say you want to insert new user, to perform this operation we should:
lookup if email is unique
hash password
Insert new user
return inserted data in postgres back as a response
Following the middleware chaining we should implement a function for each action and chain each action in route definition:
const postgres = require('../../lib/postgres');
const crypto = require('crypto');
exports.insertedData = (req, res) => {
res.status(200).json(req.employee);
};
exports.hashPassword = (req, res, next) => {
crypto.scrypt(req.body.password.toString(), 'salt', 256, (err, derivedKey) => {
if (err) {
return res.status(500).json({ errors: [{ location: req.path, msg: 'Could not hash password'}] });
}
req.body.kdfResult = derivedKey.toString('hex');
next();
});
};
exports.lookupEmailUnique = (req, res, next) => {
const sql = 'SELECT e.email FROM public.users e WHERE e.email=$1';
postgres.query(sql, [req.body.email], (err, result) => {
if (err) {
return res.status(500).json({ errors: [{ location: req.path, msg: 'Could not query database' }] });
}
if (result.rows.length > 0) {
return response.status(403).json({"code": "ERROR", "message": "Email is already registered"})
}
next()
});
}
exports.insertNewUser = (req, res, next) => {
const sql = 'INSERT INTO public.users(first_name, last_name, email, password) VALUES ($1,$2,$3,$4} RETURNING *';
postgres.query(sql, [req.body.first_name, req.body.last_name, req.body.email, req.body.kdfResult], (err, result) => {
if (err) {
return res.status(500).json({ errors: [{ location: req.path, msg: 'Could not query database'}] });
}
req.employee = result.rows[0];
next();
});
};
here is your route declaration:
const router = require('express').Router();
const userService = require('../controllers/user.controller');
router.post('/register', userService.lookupEmailUnique, userService.hashPassword, userService.insertNewUser, userService.insertedData);
module.exports = router;
Here in routes you are using the middeware to do the chaning, you only pass the control to next middleware if all conditions are met and has full control from database erros.
In my example I do not used the async/await but I can change my example to have a version using async/await.
example middleware with transaction
exports.deletePostagem = async (req, res, next) => {
try {
await postgres.query('BEGIN');
const sql2 = 'UPDATE comentario SET postagem = null WHERE postagem = $1';
await postgres.query(sql2, [req.params.id]);
const sql3 = 'DELETE FROM postagem WHERE id = $1';
await postgres.query(sql3, [req.params.id]);
await postgres.query('COMMIT');
res.status(204).json();
res.end();
} catch (err) {
await postgres.query('ROLLBACK');
return res.status(500).json({ errors: [{msg: 'Could not perform operation' }]})
}
}
I used this only as an example, but in my projects I always have a middeware for validate/sanitize the data that comes in request before using in database query prepared statements.
based on transaction documentation in node.js you can use rollback
export const registerUser = async (request, response, next) => {
try {
let error = null;
const client; // create a client, connect to the db
try {
await client.query("begin");
await client.query("first query");
await client.query("second query");
await client.query("third query");
await client.query("commit"); //do commit when is finished all queries
} catch (error) {
error = error;
await client.query("rollback");
} finally {
client.release(); // close the connection
}
if (error) {
return response.status(500).json({ message: error }); // error message
}
return response.status(200).json({ message: "My message" }); // success message
} catch (err) {
return response.status(500).json({ message: err });
}
}
Related
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.
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.
I have the code below. Its a standard blog type of setup with users which have posts and comments. Comments are the child of both users and post . Posts belong just to users. Im having a problem posting to comments table. IM not getting any errors when using the insert function , however, when I post a comment to the database nothing gets saved to the comments table . If i do a request to retrieve the comments table , the table still shows empty. What am i doing wrong here .
server.post("/users/:id/posts/:id2/comments", async (req, res) => {
const userID = req.params.id;
const postID = req.params.id2;
db("users")
.where({ id: Number(userID)})
.then((user) => {
db('posts') .where({ id: Number(postID)})
.then((post) => {
//verify if post and user exists
if (post && user) {
req.body.content ? insertComment({
content: req.body.content,
user: userID,
post: postID
})
.then(
res.status(201).json(req.body)
)
.catch((err) => {
console.log(err);
})
: res.status(400).json({
errorMessage: "Please insert text .",
});
} else {
res.status(404).json({
message: "user not found",
});
}
})
})
.catch((err) => {
res.status(500).json({
err,
message: "Error processing request",
});
});
});
function insertComment(comment) {
return db("comments").insert(comment).where({
user: comment.user,
post: comment.post
});
}
since you're already using async function i'd first recommend to use async/await, second notice is that knex returns an array and not an object for example
db("users")
.where({ id: Number(userID)})
.then((user) => {
// user is an array
});
you can chain a query with .first() to retrieve the first object and not an array
Reference from knex documentation
using async/await could save you from callback hell
server.post("/users/:id/posts/:id2/comments", async (req, res) => {
const userID = req.params.id;
const postID = req.params.id2;
try {
const user = await db("users").where("id", Number(userID)).first();
const post = await db("posts").where("id", Number(postID)).first();
if (post && user) {
if (req.body.content) {
await insertComment({
content: req.body.content,
user: userID,
post: postID,
});
return res.status(201).json(req.body);
} else {
return res.status(400).json({
errorMessage: "Please insert text .",
});
}
} else {
return res.status(404).json({
message: "user or post not found",
});
}
} catch (err) {
return res.status(500).json({
err,
message: "Error processing request",
});
}
});
async function insertComment(comment) {
return db("comments").insert(comment).where({
user: comment.user,
post: comment.post,
});
}
and if you have lots of relationships in your application you might find it useful if you want to use an ORM like Objection as it is built on knex.
I have a fairly extensive Node.js back-end with Express and pg-promise. Most of it is working perfectly as I expected it to.
Currently I am trying to create a function that deletes from multiple tables if I pass in a value for it to do so. My problem is that when I try to use req.body.cont (cont is a variable to continue) I get undefine
I have tried changing the names of the variables, trying to mimic how I sent data to the server before.
function removeUser(req, res, next) {
console.log(req.body.cont);
if (req.body.cont) {
console.log('hello');
deleteLocCustId(req, res, next, req.params.id);
}
db.result('delete from customer where id = $1', req.params.id)
.then(function(result) {
if (!req.body.cont) {
res.status(200).json({
status: 'success',
message: `Removed ${result.rowCount} customer`
});
}
})
.catch(function(err) {
console.log(err);
return next(err);
});
}
When I use this same method to create a customer it works perfectly.
function createCustomer(req, res, next) {
const newCustomer = new PQ(
'insert into customer(customer_name, ID, parent, symID) values ($1, $2, $3, $4)'
);
newCustomer.values = [
req.body.customerName,
req.body.cId,
req.body.parentName,
req.body.symPID
];
db.none(newCustomer)
.then(() => {
if (req.body.locationObject) {
return createLocation(req, res, next);
}
return null;
})
.catch(function(err) {
return next(err);
});
}
When I try to console.log(req.body.cont); I get undefined from my server.
I have an ExpressJS app that when a user makes a POST request to a route, it should lookup the ID in the MongoDB using req.params.formId
I have some console.log statements tfor debugging and so I can see what info is being returned.
The route should lookup the ID passed and when it finds it, use the req.body data and also a field from the MongoDB document but this just seems to return as undefined
Here is the code for the route:
app.post("/api/v1/forms/:formId", (req, res) => {
const { name, email, message } = req.body;
console.log(req.body);
Form.findById(req.params.formId, Form.recipient, err => {
if (err) {
res.send(err);
} else {
const formRecipient = Form.recipient;
const newForm = {
name,
email,
message,
recipient: formRecipient
};
console.log(newForm);
const mailer = new Mailer(newForm, contactFormTemplate(newForm));
try {
mailer.send();
res.send(req.body);
} catch (err) {
res.send(err);
}
}
});
});
So an example, if I make a POST request to localhost:5000/api/v1/forms/5ad90544883a6e34ec738c19 the console.log of newForm shows { name: ' Mr Tester',
email: 'person#example.com',
message: 'Hi there',
recipient: undefined }
The forms Mongoose schema has a field named recipient
the correct way is to provide the fields you want to get as the second argument:
Form.findById(req.params.formId, 'recipient', (err, form) => {
if (err) {
// error handling code
} else {
const formRecipient = form.recipient;
}
...
});
here's the Docs