I'm playing around with express-validator attempting to apply the .optional method to my request body, but it does not act as I would expect.
I split my sanitization and validation into two files as such:
//common/middlewares/input.sanitization.middleware.js
const { body } = require('express-validator');
exports.patchSanitizationRules = () => {
return [
body('*')
.escape(),
body(['first_name', 'last_name'])
.trim()
.optional(),
body('email')
.trim()
.normalizeEmail()
.optional()
];
};
//common/middlewares/input.validation.middleware.js
const { body, validationResult } = require('express-validator');
exports.validate = (request, response, next) => {
const errors = validationResult(request);
if (!errors.isEmpty()) {
return response.status(422).send({errors: errors});
}
// Checking everything was passed correctly
console.log(request.body);
return next();
};
exports.patchValidationRules = () => {
return [
body()
.notEmpty()
.isLength({min: 4, max: 64}),
body(['first_name', 'last_name'])
.isAlpha()
.optional(),
body('email')
.isEmail()
.optional()
];
};
//users/routes.config.js
const UserController = require('./controllers/users.controller');
const UserSanitizationMiddleware = require('../common/middlewares/input.sanitization.middleware')
const UserValidationMiddleware = require('../common/middlewares/input.validation.middleware');
exports.routesConfig = (app) => {
app.patch('/users/:userId', [
UserSanitizationMiddleware.patchSanitizationRules(),
UserValidationMiddleware.patchValidationRules(),
UserValidationMiddleware.validate,
UserController.patchById
]);
};
So you may already see my issue here. I would like to place .optional into my body() or body('*'). However, doing this will cause whatever field was not in my body to be replaced by "". Now I could add logic in my controller to not allow "" as a valid value, but I would prefer to handle this within my middleware. How can I edit this?
Related
I'm trying to make GET request to external API (Rick and Morty API). The objective is setting a GET request for unique character, for example "Character with id=3". At the moment my endpoint is:
Routes file:
import CharacterController from '../controllers/character_controller'
const routes = app.Router()
routes.get('/:id', new CharacterController().get)
export default routes
Controller file:
async get (req, res) {
try {
const { id } = req.params
const oneChar = await axios.get(`https://rickandmortyapi.com/api/character/${id}`)
const filteredOneChar = oneChar.data.results.map((item) => {
return {
name: item.name,
status: item.status,
species: item.species,
origin: item.origin.name
}
})
console.log(filteredOneChar)
return super.Success(res, { message: 'Successfully GET Char request response', data: filteredOneChar })
} catch (err) {
console.log(err)
}
}
The purpose of map function is to retrieve only specific Character data fields.
But the code above doesn't work. Please let me know any suggestions, thanks!
First of all I don't know why your controller is a class. Revert that and export your function like so:
const axios = require('axios');
// getCharacter is more descriptive than "get" I would suggest naming
// your functions with more descriptive text
exports.getCharacter = async (req, res) => {
Then in your routes file you can easily import it and attach it to your route handler:
const { getCharacter } = require('../controllers/character_controller');
index.get('/:id', getCharacter);
Your routes imports also seem off, why are you creating a new Router from app? You should be calling:
const express = require('express');
const routes = express.Router();
next go back to your controller. Your logic was all off, if you checked the api you would notice that the character/:id endpoint responds with 1 character so .results doesnt exist. The following will give you what you're looking for.
exports.getCharacter = async (req, res) => {
try {
const { id } = req.params;
const oneChar = await axios.get(
`https://rickandmortyapi.com/api/character/${id}`
);
console.log(oneChar.data);
// return name, status, species, and origin keys from oneChar
const { name, status, species, origin } = oneChar.data;
const filteredData = Object.assign({}, { name, status, species, origin });
res.send(filteredData);
} catch (err) {
return res.status(400).json({ message: err.message });
}
};
When I paste the endpoint URL with query directly inside the axios.get(), it responds correctly and I can see the json object returned. (i.e axios.get(http://localhost:3000/api/products/product_search?secretKey=${secret}&id=${blabla})). However, if I call the url with the summonerByNameUrl method, it crashes when I make a request. What is the problem in my code?
Crash report:
...
data: '<!DOCTYPE html>\n' +
'<html lang="en">\n' +
'<head>\n' +
'<meta charset="utf-8">\n' +
'<title>Error</title>\n' +
'</head>\n' +
'<body>\n' +
'<pre>Cannot GET /[object%20Object]</pre>\n' +
'</body>\n' +
'</html>\n'
},
isAxiosError: true,
toJSON: [Function: toJSON]
Code:
config.js
const summonerByNameUrl = (summonerName) => `${URL(hidden)}${summonerName}`;
module.exports = {
summonerByNameUrl
}
summoner.js
const config = require('../config');
const axios = require('axios');
const getSummonerByName = async (summonerName) => {
const res = await axios.get(config.summonerByNameUrl(summonerName));
return res.data;
}
const summonerParser = async (req, res) => {
if(!req.query.secretKey)
return res.status(403).json({error: 'missing secret key.'})
let data = await getSummonerByName(req.query)
return res.status(200).json(data);
}
module.exports = {
getSummonerByName,
summonerParser
}
products.js
var express = require('express');
var axios = require('axios')
var router = express.Router();
const summoner = require('../services/summoner');
router.get('/product_search', summoner.summonerParser)
module.exports = router;
app.js
...
app.use('/api/products', productsRouter);
...
You're calling your function with getSummonerByName(req.query) where it is clear from the lines just before that req.query is an object and not a string. When objects are used in a string-context (like your URL), they become "[object Object]", hence the error.
Taking some guesses here but it seems you want to forward some req.query information to the Axios call as query params. Try this instead...
const PRODUCT_SEARCH_URL = "http://localhost:3000/api/products/product_search"
const getSummonerByName = async ({ secretKey, id }) => {
const { data } = await axios.get(PRODUCT_SEARCH_URL, {
params: { secretKey, id }
})
return data
}
If you've got a helper function that returns the base URL (ie http://localhost:3000/api/products/product_search) then by all means, use that instead of a string literal in the Axios call.
The req.query is a Object, not a string.
You can try map the req.query object to make a string. Something like that:
Object.keys(req.query).map(key => {
return key + '=' + req.query[key]
}).join('&')
This code return a string like that: 'id=1&name=test', so you can pass to the endpoint.
I'm trying to build a route middleware function to validate a form, but I got a little confused about how should I get errors.
How is validationErrors populated and how should I access it inside route function? The examples I found at the docs and other sites did not helped me
route:
use strict';
const express = require('express');
const router = express.Router();
const User = require('../back/api/models/UserModel');
const Helper = require('./handlerInputs.js');
const bcrypt = require('bcrypt');
router.post('/registrar', [Helper.validaRegistro], function (req, res, next) {
const errors = validationResult(req).throw();
if (errors) {
return res.status(422).json({ errors: errors });
}
[... user register code .... ]
});
handler:
'use strict'
const { check, validationResults } = require('express-validator');
exports.validaRegistro = function(req, res, next){
check(req.body.nome)
.not().isEmpty()
.withMessage('Nome é obrigatório')
.isLength({min: 3, max: 20})
.withMessage('Nome deve ter entre 3 e 20 caracteres')
.isAlpha('Nome deve ser literal');
check(req.body.email)
.normalizeEmail()
.isEmail()
.withMessage('Email inválido');
optPwd = {
checkNull: false,
checkFalsy: false
}
check(req.body.password)
.exists(optPwd)
.withMessage('Senha é obrigatória');
check(req.body.password === req.body.passordconf)
.exists()
.withMessage('Confirme a senha')
.custom((value, { req }) => value === req.body.password)
.withMessage('Senhas não são iguais')
.custom((value, { req }) => value.length >= 8)
const result = req.getValidationResults();
const erros = req.ValidationErrors;
if(erros){
console.log(erros);
}
????
}
What you can do is, Just write validation logic inside middleware itself rather than writing the same thing again and again on the different controller.
Another best way to create common logic is to put validation rules on different files and put handling validation logic in a different file.
Please follow this URL, I have implemented the same thing with efficient way.
https://github.com/narayansharma91/node_quick_start_with_knex
if(erros){
const status = 422;
res.status(status).json({
success: false,
status,
errors: errors.array(),
});
}
I'm using the latest version of express-validator for validation.
I'm not getting any response, However Old method i.e checkBody is working fine while new method i.e check('keyName') is not working properly.
Below is my code.
package.json
"express-validator": "^5.0.3",
routes.js
var authValidator = require('./../validation/auth.validation');
var routes = require('express').Router();
routes.post('/login', [
authValidator.validateLogin,
authValidator.checkValidationResult ], function (req, res) {
console.log('3');
//res.send("Some other stuffs");
}
);
module.exports = routes;
auth.validation.js
module.exports.validateLogin = validateLogin;
module.exports.checkValidationResult = checkValidationResult;
const {check, validationResult} = require('express-validator/check');
const {matchedData, sanitize} = require('express-validator/filter');
var response = require('./../general/MyResponse');
var messages = require('./../general/messages');
function validateLogin(req, res, next) {
console.log('1');
return [
check('email').isLength({min: 1}).withMessage(messages.EMAIL_REQUIRED)
.isEmail().withMessage(messages.INVALID_EMAIL),
check('password').isLength({min: 1}).withMessage(messages.PASSWORD_REQUIRED),
]
}
function checkValidationResult(req, res, next) {
console.log('2');
var result = validationResult(req)
if (!result.isEmpty()) {
response.createResponse(
res, 400,
result.array()[0].msg,
{'error': result.array()[0].msg}, {}
)
} else {
next()
}
}
I've noticed that node js not able to go ahead from the function validateLogin in auth.validation.js.
Can anyone tell me what's wrong with above code.
Inside console, Only 1 is displaying.
I'm attaching screenShot for referance.
We need to use simple Array and don't need to create function.
Follow this link
Is it possible to do the validation in a separate file and not inline in the route? - GitHub for more details.
Code should be like this.
auth.validation.js
var response = require('./../general/MyResponse');
var messages = require('./../general/messages');
const {check, validationResult} = require('express-validator/check');
const {matchedData, sanitize} = require('express-validator/filter');
module.exports.validateLogin = [
check('email').isLength({min: 1}).withMessage(messages.EMAIL_REQUIRED).isEmail().withMessage(messages.INVALID_EMAIL),
check('password').isLength({ min: 1 }).withMessage(messages.PASSWORD_REQUIRED),
];
module.exports.checkValidationResult = checkValidationResult;
function checkValidationResult(req, res, next) {
console.log('2');
var result = validationResult(req)
if (!result.isEmpty()) {
response.createResponse(res, 400,
result.array()[0].msg,
{'error': result.array()[0].msg}, {}
)
} else {
next()
}
}
`
validateLogin and checkValidationResult are being applied as middlewares to your route. In middlewares you use next()method to call next middleware in the queue. Just like in your checkValidationResult.
In case of validateLogin, its not passing control to next middleware. But check method from express-validator v5 is itself a middleware method. Thus I guess it won't work correctly.
Please have a look at: https://github.com/ctavan/express-validator/issues/449
Try using following Code:
routes.js
var authValidator = require('./../validation/auth.validation');
var routes = require('express').Router();
var authValidations = authValidator.getAuthValidations();
routes.post('/login',
authValidations,
authValidator.checkValidationResult, function (req, res) {
console.log('3');
//res.send("Some other stuffs");
}
);
module.exports = routes;
auth.validations.js
module.exports.getAuthValidations = getAuthValidations;
module.exports.checkValidationResult = checkValidationResult;
const {check, validationResult} = require('express-validator/check');
const {matchedData, sanitize} = require('express-validator/filter');
var response = require('./../general/MyResponse');
var messages = require('./../general/messages');
function getAuthValidations(req, res, next) {
return [
check('email').isLength({min: 1}).withMessage(messages.EMAIL_REQUIRED)
.isEmail().withMessage(messages.INVALID_EMAIL),
check('password').isLength({min: 1}).withMessage(messages.PASSWORD_REQUIRED),
]
}
function checkValidationResult(req, res, next) {
console.log('2');
var result = validationResult(req)
if (!result.isEmpty()) {
response.createResponse(
res, 400,
result.array()[0].msg,
{'error': result.array()[0].msg}, {}
)
} else {
next()
}
}
use koa2 ejs koa-router, ejs template how to use another middleware's ctx.state
localhost:3000/admin/usermsg
admin.get('/usermsg', async(ctx) => {
ctx.state.userMsg = {
page: Number(ctx.query.page),
limit: 4,
pages: 0,
count: count
}
var userMsg = ctx.state.userMsg;
ctx.state.users = await new Promise(function(resolve, reject){
userMsg.pages = Math.ceil(userMsg.count / userMsg.limit);
userMsg.page = userMsg.page > userMsg.pages ? userMsg.pages : userMsg.page;
userMsg.page = userMsg.page < 1 ? 1 : userMsg.page;
var skip = (userMsg.page - 1) * userMsg.limit;
User.find().limit(userMsg.limit).skip(skip).exec(function(err, doc){
if(doc){
resolve(doc);
}
if(err){
reject(err);
}
})
})
await ctx.render('admin/usermsg');
})
localhost:3000/damin/category
admin.get('/category', async(ctx) => {
await ctx.render('admin/category');
})
in the category template,can not get ctx.state.userMsg.
how should i get ctx.state.userMsg in category template?
Well, assuming userMsg is something you use a lot in your views, you could make a dedicated middleware just to obtain that value.
Middleware work in 'stacks': by calling next(), you can pass control to the next one in the stack (with access to the modified ctx.state). A trivial example:
const setUserMsg = async (ctx, next) => {
ctx.state.userMsg = await myFuncThatReturnsAPromise()
await next()
}
router.get('/someroute',
setUserMsg,
ctx => { ctx.body = ctx.state.userMsg })
router.get('/someotherroute',
setUserMsg,
ctx => { ctx.body = ctx.state.userMsg })
Here, setUserMsg's sole purpose is to extract a value (presumably from the database) and add it to the context.