Access request body in check function of express-validator v4 - javascript

I just started using express.js with express-validator to validate some input data and I have problems accessing the request body in the new check API that was introduced in version 4.0.0.
In older versions, you simply added express-validator as middleware in your app.js somewhere after body-parser:
// ./app.js
const bodyParser = require("body-parser");
const expressValidator = require("express-validator");
const index = require("./routes/index");
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(expressValidator());
Then in my index route, I could check the fields in the final callback function of the post method.
// ./routes/index.js
const express = require("express");
const router = express.Router();
router.post("/submit", (req, res, next) => {
// check email
req.check('email','Invalid email address').isEmail()
// check if password is equal to password confirmation
req.check('password', 'Invalid password')
/* Access request body to compare password
field with password confirmation field */
.equals(req.body.confirmPassword)
// get errors
const errors = req.validationErrors();
// do stuff
});
Like in this example, I could easily check whether the values of my password field and the password confirmation field of my form are equal. However, since version 4, they have a new API which requires you to load the express-validator directly in your router file and pass the check functions as array of functions before the final callback in the post method, like this:
// ./routes/index.js
const express = require("express");
const router = express.Router();
const { check, validationResult } = require("express-validator/check");
router.post(
"/submit",
[
// Check validity
check("email", "Invalid email").isEmail(),
// Does not work since req is not defined
check("password", "invalid password").isLength({ min: 4 })
.equals(req.body.confirmPassword) // throws an error
],
(req, res, next) => {
// return validation results
const errors = validationResult(req);
// do stuff
});
This doesn't work since req is not defined. So my quetsion is: how can I access the request object in a check() chain to compare two different fields with the new express-validator API? Thanks very much in advance!

After fiddling around for a while, I found a way to achieve this by using custom validators. The validator function passed to the custom method accepts an object containing the request body:
router.post(
"/submit",
[
// Check validity
check("email", "Invalid email").isEmail(),
check("password", "invalid password")
.isLength({ min: 4 })
.custom((value,{req, loc, path}) => {
if (value !== req.body.confirmPassword) {
// trow error if passwords do not match
throw new Error("Passwords don't match");
} else {
return value;
}
})
],
(req, res, next) => {
// return validation results
const errors = validationResult(req);
// do stuff
});

Related

detect a request using NodeJS without express or accessing the request object outside an express middleware [duplicate]

This question already has answers here:
Access current req object everywhere in Node.js Express
(2 answers)
Closed 8 months ago.
This post was edited and submitted for review 8 months ago and failed to reopen the post:
Original close reason(s) were not resolved
I have the following lines of code:
const express = require('express');
const app = express()
// ... defining the routes, app.get('/api/users', (req, res, next)=>{ }) ...etc
app.listen(3000, ()=> console.log('Listening on port 3000...'))
module.exports = app
I want to be able to read the request object outside an express middleware.
I have another file called mongoose_models.js, inside that file, I don't have the access to the express middleware arguments (req, res, next).
And the only option I have for reading the request body from that file is to import the app and somehow read the request Object.
NodeJs is event-driven, so there must be a way somehow to do so, for instance, inside the file mongoose_models.js I would have maybe something like this code:
// mongoose_models.js
// ... some code
const app = require('../app.js')
app.on('request', (req)=>{
// here I have the request
})
or maybe if express supports:
// mongoose_models.js
// ... some code
const { req } = require('express')
console.log(req.body) // ? maybe something like that ?
or maybe if express supports too:
// mongoose_models.js
// ... some code
const app = require('../app.js')
app.onRequest((req, res) => {
// here I have the access to the request object
})
Is there a way to reach the request object without having to be inside an express middleware in NodeJS?
edit:
Some of you asked me to provide the source code, unfortunately, I wanted to provide a stackblitz or code sandbox instance, but I didn't know how to set up the connections to the database.
Anyway, the following is the file structure of the sample project:
app.js file (full code):
const express = require('express')
const app = express()
const mongoose = require('mongoose')
const RoomModel = require('./mongoose_models')
app.use((req, res, next) => {
// this middleware is the "protect" middleware, it validates a JWT (JSON web token), decodes it, and then stores the user it finds to the req object:
// .... etc some code
// decode the JWT .. some code
// find the user in the DB const userDoc = await UserModel.findOne({ _id: decodedJWT.id )})
const userDoc = {
id: 'abc-123-edf-cds-123-321-qu5-eu4-dc9-182',
name: 'Murat',
// and some other fields ... etc
}
req.$loggedInUser = userDoc
})
app.get('/rooms', async(req, res, next) => {
const docs = await RoomModel.find({})
res.status(200).json({
message: 'here are all the rooms',
results: docs.length,
data: docs,
})
})
app.post('/rooms', async(req, res, next) => {
const doc = await RoomModel.create(req.body)
res.status(201).json({
message: 'the new room which got created:',
data: doc,
})
})
// connecting to the database:
mongoose.connect(
'mongodb+srv://USERNAME:PASSWORD#YOUR_CLUSTER.mongodb.net/?retryWrites=true&w=majority'
)
// starting the HTTP service:
app.listen(3000, () => console.log('app listening on port 3000...'))
mongoose_models.js file (full code):
const mongoose = require('mongoose')
const roomSchema = new mongoose.Schema({
name: String,
by: mongoose.Schema.ObjectId,
})
roomSchema.pre('save', function(next) {
// Here I want to make the by field be the req.$loggedInUser.id but I can't because I have no way to read the request object
const doc = this
// doc.by = req.$loggedInUser.id // < ----- 👈👈👈 HERE, I can't reach the req object
next()
})
const RoomModel = mongoose.model('Room', roomSchema, 'rooms')
module.exports = RoomModel
NodeJS is event driven, so there must be a way somehow to do so, for
instance, inside the file mongoose_models.js I would have maybe
something like this code:
// mongoose_models.js
// ... some code
const app = require('../app.js')
app.on('request', (req)=>{
// here I have the request
})
This approach is, essentially, middleware. So write is as middleware.
const myMiddleware = (req, res, next) => {
// here you have the request
next(); // go to next middleware
}
module.exports = myMiddleware
Attaching something to listen for requests is done with use (for non-method specific functions) and post, get, etc. There is no on method or onRequest method.
// mongoose_models.js
// ... some code
const { req } = require('express')
console.log(req.body) // ? maybe something like that ?
The request object doesn't exist until the client makes a request to the server.
You get a new request object each time a request is made.
The server might be handling multiple requests at the same time.
So no, you can't do anything like that.
Is there a way to reach the request object without having to be inside an express middleware in NodeJS?
No.

Express - Validation express-validator does not show errors

I'm sure this is not a problem instead I'm not using it the right way.
I want to validate user request body and according to documentation, all work is done in the route file. And I divided my work into routes and controllers files. I passed a validation middleware and it needs to be validated but it's not. Something wrong in between.
My route is:
router.post(
'/signup',
userController.validateRequest,
userController.hashPassword,
userController.signup
);
Instead of this, if I did this way:
router.post(
'/signup',
body('email').isEmail().withMessage('Please enter a valid email'),
body('password')
.equals('confirmPassword')
.withMessage('Passwords do not match'),
userController.hashPassword,
userController.signup
);
That will work But this will makes code messy if several validation requests are there.
I just want to know what is the correct way, if I separate requests and validation middleware OR do I have to pass validation in route file ONLY. Please correct me if I'm doing it the wrong way.
My validation middleware is:
exports.validateRequest = (req, res, next) => {
if (req.body.email) {
body('email').isEmail().withMessage('Please enter a valid email');
}
if (req.body.password_confirm) {
body('password')
.equals('confirmPassword')
.withMessage('Passwords do not match');
}
next();
};
And my controller where all error messages are displaying
exports.signup = async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
............
Well i prefer Joi the most powerful validator
async createAccountSchema(req, res, next) {
// create schema object
const schema = Joi.object({
email: Joi.string().email().required(),
password: Joi.string()
.min(8)
.required(),
confirmPassword: Joi.string()
.valid(Joi.ref('password'))
.required()
.label('Passwords')
.messages({ 'any.only': '{{#label}} are not the same' }),
})
// schema options
const options = {
abortEarly: false, // include all errors
allowUnknown: true, // ignore unknown props
stripUnknown: true, // remove unknown props
}
// validate request body against schema
const result = schema.validate(req.body, options)
if (result.error) {
return res.status(400).json(result.error.details)
} else {
result.value.email = result.value.email.trim().toLowerCase()
result.value.password = result.value.password
req.body = result.value
next()
}
}
Thats an example for the validator. You pass req.body to the validator and it do the job.
After validating you set your req.body to the validation result and use next()
app.use("/", createAccountSchema, userController.signup)

Why axios post in react is not read by the node server, when params are sent as object?

The params are sent. I can see the endpoint triggered, and dev tools display them. However, query params are not received by the server or interpreted incorrectly.
Simple axios.post:
async function login(username, password) {
return await axios.post(LOGIN_URL, {
username,
password
},
);
}
This situation is completely different if I'm just writing the query by hand, everything is received:
axios.post(LOGIN_URL + `?username=${username}&password:${password}`);
I want to use my bodyParser in educational purposes.
Interpretation is as simple as possible with just 4 logs:
function bodyParser (req, res, next) {
const body = url.parse(req.url).query;
console.log(req.url); // /auth
console.log(req.query); // {}
console.log(req.body); // undefined
console.log(req.params); // {}
res.body = body;
next();
}
The app:
import express from 'express';
import cors from 'cors';
import bodyParser from './middlewares';
import auth from "./routes/auth.route";
const app = express();
app.use('/', cors());
app.use('/', bodyParser);
app.use('/auth', auth);
export default app;
async function login(username, password) {
return await axios.post(LOGIN_URL, {
username,
password
},
);
}
That's sending username and password in the req.body. So in Node, you need to be checking req.body
A Url like so:
http://someurl.com/api?username=a&password=b
You pick those variables up in req.query.
A Url like so:
http://someurl.com/api/people/some_id_here
You pick that ID up in req.params.
I believe there is an issue with how you're calling axios.post(...). Assuming that when you call axios this way and it works:
axios.post(LOGIN_URL + `?username=${username}&password:${password}`);
The username and password variables passed to axios.post(LOGIN_URL, {username, password}) are string variables. Therefore, you forgot to key the values like so,
async function login(username, password) {
return await axios.post(LOGIN_URL, {
'username': username,
'password': password
},
);
}
Now in your bodyParser function, you should be able to access these variables passed in via req.params.username and req.params.password.

Restful API: How to display specific data in Node/Express app?

I am building a simple Node/Express app to login a user. Before user can login the app must check if the email provided exists in the database.
The structure of my app is like this:
* db/data.js
* app.js // server
I want to login a user
const data = [
{
id: 1,
email: 'xyz#xyz.com',
fist_name: 'hekw',
last_name: 'xyz',
password: 'usr$#',
},
];
export default data;
import express from 'express';
import bodyParser from 'body-parser';
import data from './db/data';
// set up the express app
const app = express();
// Parse incoming requests data
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
/**
* Sign in a user
* #param {object} req
* #param {object} res
*/
app.post(`/login`, (req, res) => {
const findUserByEmail = (email) => {
for (let i = 0; i < data.length; i++) {
return data[i]['email'] === email ? true : false;
}
};
if (findUserByEmail(req.body.email)) {
return res.status(409).send({
success: false,
message: 'email already exists',
//findUserByEmail(req.body.email)
//data,
});
}
const port = 5000;
app.listen(port, () => {
// console.log(`The server running on port ${PORT}`);
});
export default app;
I tried but I couldn't display info of a signed user. How can I achieve it?
This is what I need:
"status":"success"
"data": {
"id": 1,
"email":"xyz#xyz.com",
"first_name": "hekw",
"last_name": "xyz",
"password": "usr$#"
}
Edit
I've implemented the code below, but I want now to check for both email and password.
const findUserByEmail = (email) => data.find(user => user.email === email);
const foundUser = findUserByEmail(req.body.email);
if (!foundUser) {
return res.status(404).send({
status: 'error',
error: 'user does not exist, register first',
});
}
if (foundUser) {
// if password OK then diplay success message. How do I access pwd field here?
return res.status(200).send({
status: 'success',
data: foundUser,
});
}
First of all, I highly recommend using the MVC pattern and create a model for each separate data model. Also, an encryption method such as Bcrypt to encrypt the passwords before storing them to the database and using a token-based approach to handle user authentication.
For the purpose of the example, I provide a solution with the JWT and Bcrypt to help understand the process better, also for people who are looking for a more detailed answer. We can pass a middleware into routes to check the user is authenticated or not then fetch the proper data for the user.
const express = require('express');
const app = express();
const router = express.Router();
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
// This user model can be replaced with your data file, in your sample
const User = require('../models/userModel');
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json()); // Always return JSON for the rest api
// Awlays set headers to controll the access
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
if (req.method === 'OPTIONS') {
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE');
return res.status(200).json({});
}
next();
});
// This is the user controller, below return it inside the route
const loginUserController = (req, res) => {
User.findOne({ email: req.body.email }) // find just one record by the email received from the user
.exec() // Use this to make a promise
.then(user => {
if (user.length < 1) { // check if the user found
return res.status(401).json({ // Check if email is not valid
message: 'Authentication Failed! Wrong login information used!'
})
}
// If status code is not 401 and user is found, then compare the password with DB version and pass "err" and "success" parameters
// user.password is the db password
bcrypt.compare(req.body.password, user.password, (err, success) => {
if (err) {
return res.status(401).json({
message: 'Authentication Failed! Wrong login information used!'
})
}
if (success) {
// Then we sign JWT if password matched
// process.env.JWT_KEY is our server jwt token
const token = jwt.sign({
email: user.email,
userId: user._id
}, process.env.JWT_KEY, {
expiresIn: '2d' // we can set the expire date (see th e docs for more info)
});
// Finally we return our token to store into user's browser cookie
// or we can just return the data, but its better to use jwt token and use it everywhere you want to get user data
return res.status(200).json({
message: 'Welcome to My App!',
data: user
token
});
}
// Here we return another 401 if the were no err or success
res.status(401).json({
message: 'Authentication Failed! Wrong login information used!'
})
})
})
.catch(err => {
// Use can create an error controller and put a switch inside of it to check response status code then return proper message
errorController(req, res, res.status, 'ANY');
})
}
// Finally we use our router to post and return login controller
router.post('/login', (req, res) => {
return loginUserController(req, res);
});
app.listen(process.env.PORT || 3000);
There are more advanced configurations, but for simplicity of the example, I provided a simple way to do the correct way (in my opinion). Hope it help.
Packages used in this example
jsonwebtoken
Bcrypt
Your code is not working. Following will not find the user object in your data array.
const findUserByEmail = (email) => {
for (let i = 0; i < data.length; i++) {
return data[i]['email'] === email ? true : false;
}
};
You can find the user like this:
const findUserByEmail = (email) => data.find((datum) => datum.email === email);
Assuming you are sending a POST request with email set correctly. You can use the following code to achieve the result you want:
const findUser = (email, pass) => data.find((datum) => datum.email === email && datum.password === pass);
let foundUser = findUser(req.body.email, req.body.password);
if (foundUser) {
return res.status(200).json({
"status":"success"
"data": foundUser
});
}
res.status(404).json({
"status": "Not Found"
"data": foundUser
});

params looking for id being fired on another route when it shouldn't

I'm new to mongoose so I apologise for incorrect uses of terminology.
I have a routes file as detailed below
const express = require('express');
const router = express.Router();
const passport = require('passport');
const controller = require('./clubController');
const authGuard = passport.authenticate('jwt', { session: false });
const verifyUser = require('./clubController').verifyUser;
const isSiteAdmin = require('./clubController').isSiteAdmin;
router.param('id', controller.params);
router.route('/')
.post(authGuard, controller.newClub)
.get(controller.allPublicClubs);
router.route('/:id')
.put(authGuard, verifyUser(), controller.editClub)
.get(controller.getClub);
router.route('/private')
.get(controller.allPrivateClubs);
module.exports = router;
controller.params
exports.params = function(req, res, next, id) {
Club.findById(id)
.populate('creator teams', '-password -email -role')
.exec()
.then(function(club) {
if (!club) {
return res.status(404).send({ msg: 'No Club exists with that ID' });
} else {
req.club = club;
next();
}
}, function(err) {
// error handling
next(err);
});
};
controller.params is being fired when I make a get request to /private. To my understanding, the params middleware I have setup should only be fired when a called route is using an id parameter.
The value for the id argument in controller.params is set as private which is the route.
The error I receive is detailed below
CastError: Cast to ObjectId failed for value "private" at path "_id" for model "club"
This was working fine yesterday, no idea what changed that it now does not work.
I solved the issue by moving
router.route('/:id')
.put(authGuard, verifyUser(), controller.editClub)
.get(controller.getClub);
To the bottom of all the routes. Was strange as I had the same order before and it worked fine

Categories

Resources