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
Related
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.
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 });
}
}
I am trying to call a field from mysql within my node js file using template literals but am unable to obtain the value. Please take a look at my post.controller.js file below where is says message: Post ${body.post_id} was successfully created where post_id is a field within my mysql database.
//The following code is in post.service.js file
const pool = require("../../config/database");
module.exports = {
//Create new post
createPost: (data, callBack) =>{
pool.query(
`insert into posts(userhandle, post_body)
values(?,?)`,
[
data.userhandle,
data.post_body
],
(error, results, fields) =>{
if(error){
return callBack(error);
}
return callBack(null, results);
}
);
}
}
//The following code is in post.controller.js file
const {
createPost,
} = require("./post.service");
module.exports = {
//Controller for creating new post
createPost: (req, res) =>{
const body = req.body;
createPost(body, (err, results) => {
if(err){
console.log(err);
return res.status(500).json({
success:0,
message:"Error. Unable to create post"
});
}
return res.status(200).json({
success: 1,
message: `Post ${body.post_id} was successfully created`,
data: results
});
});
}
}
I'm guessing post_id is a PK auto incremented, if that so try results.post_id, since this is an object retuned from callback.
If this won't work do the console.log(results) and see if post_id is in it.
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'm using SailsJS as an API with Waterline connected to a MongoDB. I'm trying to put together an endpoint to edit existing DB entries but can't seem to get it to work and I'm hitting a wall as to why.
My route:
'post /edit/safety/:id': {
controller: 'SafetyController',
action: 'editSafety'
},
My controller function:
editSafety: function editSafety(req, res) {
var id = req.params.id;
Safety.findOneById(id).then((err, safety) => {
if (err) {
res.send(500, err);
return;
}
if (!safety) {
res.send(404, err);
return;
}
safety.title = req.body.title;
safety.description = req.body.description;
safety.status = req.body.status;
safety.save((err, updatedSafety) => {
if (err) {
re.send(500, err);
return;
}
res.send(200, updatedSafety);
});
});
},
Any push in the right direction would be greatly appreciated.
I don't recognize the Safety.findOneById method - is this something you have custom built? If not, then it is likely your problem.
Try swapping it for either:
Safety.findOne(id)
or
Safety.findOne({id: id})
Note that the returned object will be a model instance if the record exists, and undefined otherwise. If you decide to go with Safety.find instead then the returned value will be an array containing all models matching the query.
Looks like the main issue was transposing the response and err objects. It was successfully completing the query, but loading it into the err object which gets caught and a 500 error is thrown. So I changed that and simplified in a few other places.
editSafety: function editSafety(req, res) {
var id = req.params.id;
Safety.findOne(id).then((response, err) => {
var safety = response;
if (err) {
res.send(500, err);
return;
}
if (!response) {
res.send(404, err);
return;
}
safety.title = req.body.title;
safety.description = req.body.description;
safety.status = req.body.status;
Safety.update({
id: id
}, safety)
.then((result) => {
res.json(200, 'Ok!');
})
.catch((err) => {
sails.log.error('SafetyController.editSafety', err);
})
});
},