How can I write custom validations for express-validator that rely on information from other fields?
Example Problem:
I have a wishlist app where users can buy items for the wishlist owner. They can leave a note with that item. The note must have less characters than the price of the item.
Post Request: "/purchase"
Body: {
"itemId": "1",
"note": "I'm buying this to say thank you for your help with my Chinese practice. You helped me understand tones better."
}
In the database, the item with ID 1 is $100. This note is 111 characters. 111 is greater than 100 so it should not validate.
I can't figure out how to access the other fields in a custom validator using express-validator so I can't figure out how to write this validation.
I can successfully limit the note length to a constant length, example 100 characters. But how do I make that character length value equal to the price of the item: data.items.find((i) => i.itemId === +req.body.itemId).price;? Is there a way to access req.body within the body() middleware?
body('note', `Note must be 100 or less characters`).custom((note) => note.length <= 100),
Full code:
const { body, validationResult } = require('express-validator');
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const data = {
items: [
{ itemId: 1, price: 100 },
{ itemId: 2, price: 50 },
],
};
app.use(bodyParser.json());
app.post(
'/purchase',
body('note', `Note must be 100 or less characters`).custom((note) => note.length <= 100),
(req, res, next) => {
const errors = validationResult(req).array();
if (errors.length) {
return res.status(400).send(errors);
}
return next();
},
(req, res, next) => {
const itemPrice = data.items.find((i) => i.itemId === +req.body.itemId).price;
res.status(200).send({
message: `Item ${req.body.itemId} purchased for $${itemPrice}`,
});
}
);
app.listen(3500);
I found out you can pass the request object to the custom validator.
const { body, validationResult } = require('express-validator');
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const data = {
items: [
{ itemId: 1, price: 100 },
{ itemId: 2, price: 50 },
],
};
app.use(bodyParser.json());
app.post(
'/purchase',
body('note', `Note too long.`).custom(
(note, { req, location, path }) =>
note.length <= data.items.find((i) => i.itemId === +req.body.itemId).price
),
(req, res, next) => {
const errors = validationResult(req).array();
if (errors.length) {
const noteError = errors.find((err) => err.msg === 'Note too long.');
if (noteError)
noteError.msg = `Note must be less than ${
data.items.find((i) => i.itemId === +req.body.itemId).price
} characters`;
return res.status(400).send(errors);
}
return next();
},
(req, res, next) => {
const itemPrice = data.items.find((i) => i.itemId === +req.body.itemId).price;
res.status(200).send({
message: `Item ${req.body.itemId} purchased for $${itemPrice}`,
});
}
);
app.listen(3500);
The reference link to this with examples is at: the official express-validator docs
Related
const express = require('express');
const router = express.Router();
require('../db/conn');
const User = require('../model/userSchema');
router.get('/', (req, res) => {
res.send(`Hello World from the server from router.`);
});
router.post('/register', (req, res) => {
const { name,email,phone,work,password,cpassword } = req.body;
if(!name || !email || !phone || !work || !password || !cpassword){
return res.status(422).json({error:"please fill all the fields data.."});
}
User.findOne({ "$or": [ { email: email }, { phone: phone} ] })
.then((userExist) => {
if(userExist){
return res.status(422).json({error:"Email ID or Mobile No is already exist."});
}
const user = new User({name,email,phone,work,password,cpassword});
user.save().then(() => {
res.status(201).json({message:"User Registered Successfully."});
}).catch((err) => res.status(500).json({error:"Failed to regsiter user."}));
}).catch(err => { console.log(err)});
});
module.exports = router;
await User.findOne({ "$or": [ { email: email }, { phone: phone} ] });
This code worked for me. But if we want to differentiate both phone and email and aadhar number; so if email id is present than it will show it is present, and if phone no is present than it will show it is present, and if aadhar no is present than it will show. To do this how can we write the syntax?
You can always check userExist, which is the returned record from DB.
if(userExist.phone==phone)
err="Phone No exist"
else if (userExist.aadhar==aadhar)
err="Aadhar Exist"
Finally return the error string
I try to create category to eCommerce project then it throws an error
Postman - throws an error
These are source codes
location: ecommerce-backend\index.js
const express = require('express')
const env = require('dotenv')
const app = express()
const mongoose = require('mongoose')
//routes
const authRoutes = require('./routes/auth')
const adminRoutes = require('./routes/admin/auth')
const categoryRoutes = require('./routes/category')
const productRoutes = require('./routes/product')
const cartRoutes = require('./routes/cart')
//environment variable or you can say constants
env.config()
//mongodb connection
mongoose.connect(
`mongodb+srv://${process.env.MONGO_DB_USERS}:${process.env.MONGO_DB_PASSWORD}#cluster0.nglbc.mongodb.net/${process.env.MONGO_DB_DATABASE}?retryWrites=true&w=majority`,
{
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true
}
).then(() => {
console.log('Database connected')
});
app.use(express.json())
app.use('/api', authRoutes)
app.use('/api', adminRoutes)
app.use('/api', categoryRoutes)
app.use('/api', cartRoutes)
app.use('/api', productRoutes)
app.listen(process.env.PORT, () => {
console.log(`Server is running on port ${process.env.PORT}`)
})
location: ecommerce-backend\routes\category.js
const express = require('express')
const { requireSignin, adminMiddleware } = require('../common-middleware')
const { addCategory,getCategories } = require('../controller/category')
const router = express.Router()
const path = require('path')
const shortid = require('shortid')
const multer = require('multer')
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, path.join(path.dirname(__dirname), 'uploads'))
},
filename: function (req, file, cb) {
cb(null, shortid.generate() + '-' + file.originalname)
}
})
const upload = multer({ storage })
router.post('/category/create',requireSignin, adminMiddleware,upload.single('categoryImage'), addCategory)
router.get('/category/getcategory', getCategories)
module.exports = router
location: ecommerce-backend\models\category.js
const mongoose = require('mongoose')
const categorySchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
slug: {
type: String,
required: true,
unique: true
},
categoryImage: {
type: String,
},
parentId: {
type: String
}
}, { timestamps: true})
module.exports = mongoose.model('Category',categorySchema)
location: ecommerce-backend\controller\category.js
const Category = require('../models/category')
const slugify = require('slugify')
function createCategories(categories, parentId = null){
const categoryList = []
let category
if(parentId == null){
category = categories.filter(cat => cat.parentId == undefined)
}else{
category = categories.filter(cat => cat.parentId == parentId)
}
for(let cate of category){
categoryList.push({
_id: cate._id,
name: cate.name,
slug: cate.slug,
children: createCategories(categories,cate._id)
})
}
return categoryList
}
exports.addCategory = (req, res) => {
const categoryObj = {
name: req.body.name,
slug: slugify(req.body.name)
}
if(req.file){
categoryObj.categoryImage = process.env.API + '/public/'+ req.file.filename
}
if(req.body.parentId){
categoryObj.parentId = req.body.parentId
}
const cat = new Category(categoryObj)
cat.save((error,category) => {
if(error) return res.status(400).json({ error})
if(category){
return res.status(201).json({ category})
}
})
}
exports.getCategories = (req,res) => {
Category.find({})
.exec((error, categories) => {
if(error) return res.status(400).json({error})
if(categories){
const categoryList = createCategories(categories)
res.status(200).json({categoryList})
}
})
}
this is my .env file at ecommerce-backend\ .env
PORT = 2000
MONGO_DB_USERS = mrzombit
MONGO_DB_PASSWORD = ********
MONGO_DB_DATABASE = ecommerce
JWT_SECRET = MERNSECRET
API = http://localhost:2000
I face this problem then I can't figure it out what happened to my code
Thank you!
Make sure you have change the 'Content-Type' in postman header section.
Content-Type: multipart/form-data; boundary=<calculated when request is sent>
I just do below steps:
Delete slugify package from package.json
Reinstall slugify package : you will see that
found 2 high severity vulnerabilities
run npm audit fix to fix them, or npm audit for details
Run npm audit fix
Open new window ! in postman and
copy the token from /api/admin/create and paste this token in the new window: /api/category/create in body ,
form-data :
name (doesn't exist in your DB yet)
categoryImage (click file not text)
You can also try with the following code which I hope would work for you.
**slug: slugify(toString(req.body.name))**
Add
slug: { type: String, slug: "title"} to your model.
I tried to debug the problem of slugify: string argument expected & found that in my case this object is comeing as {} so it was throwing slugify: string argument expected.
try to find if all values are properly received in slugify method.
Code snippet
Schema.pre('save', (next)=> {
console.log(`pre hook is triggered ${this.name}`.silly);
// this.set({ updatedAt: new Date() });
this.slug = slugify(this.name,{lower:true})
next()
})
I'm working in the final project from Udemy's Full Stack Web developer course, and I'm getting stuck at connecting my front and back-end. I'm getting a 404 when making a post request for sign in and I'm not sure why. Things I need to mention about the server.js:
There's no actual database, we're calling an array of objects within the same server.js file
I'm only trying to test with the first element of the array for now (in case you're wondering why am I using the database.user[0].
Now here's the server.js code:
const express = require('express');
const bcrypt = require('bcrypt-nodejs');
const cors = require('cors');
const app = express();
app.use(express.json());
app.use(cors());
const database = {
users: [
{
id: '123',
name: 'John',
email: 'john#gmail.com',
password: 'cookies',
entries: 0,
joined: new Date()
},
{
id: '124',
name: 'Sally',
email: 'sally#gmail.com',
password: 'bananas',
entries: 0,
joined: new Date()
}
]
}
app.get('/',(req, res)=>{
res.send(database.users);
});
app.post('/signin', (req, res) =>{
if(req.body.email === database.users[0].email &&
req.body.password === database.users[0].password){
res.json('success');
} else {
res.status(400).json('error logging in');
}
})
app.post('/register', (req, res) =>{
const { email, name, password } = req.body;
bcrypt.hash(password, null, null, function(err, hash) {
console.log(hash);
// Store hash in your password DB.
});
database.users.push({
id: '125',
name: name,
email: email,
password: password,
entries: 0,
joined: new Date()
})
res.json(database.users[database.users.length-1]);
})
app.get('/profile/:id', (req, res) => {
const { id } = req.params;
let found = false;
database.users.forEach(user => {
if(user.id === id) {
found = true;
return res.json(user);
}
})
if(!found){
res.status(400).json('not found');
}
})
app.post('/image', (req, res)=>{
const { id } = req.body;
let found = false;
database.users.forEach(user => {
if(user.id === id) {
found = true;
user.entries++;
return res.json(user.entries);
}
})
if(!found){
res.status(400).json('not found');
}
})
app.listen(3000, () => {
console.log('app is running on port 3000');
});
And here's my Signin.js component. I don't think you need to see the App.js file, since it only has route changes onSubmit. Also I'm not pasting the actual sign in form rendered in this Signin.js file to keep it simpler.
import React from 'react';
class Signin extends React.Component {
constructor(props){
super(props);
this.state={
signInEmail: '',
signInPassword: ''
}
}
onEmailChange = (event) => {
this.setState({signInEmail : event.target.value})
}
onPasswordChange = (event) => {
this.setState({signInPassword: event.target.value})
}
onSubmitSignIn = () => {
fetch('http://localhost:3001/signin', {
method: 'post',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
email: this.state.signInEmail,
passworrd: this.state.signInPassword
})
})
this.props.onRouteChange('home');
}
So GET requests are working fine, the signin POST request throws a not found and I'm not sure why. I thought maybe a JSON parse issue, but I don't think that's the problem. Also, I'm running my front-end on por 3001 and my back-end on port 3000. I tried checking my server with Postman before attempting to connect the app with the server and it was working fine, but know it isn't! If I type the password incorrectly, I'm also getting a 404 instead of my 400 and console.log error. Maybe someone can shed some light here? Thanks!
I think you are missing these two things:-
Have you added body-parser in our app ?? (Contains key-value pairs of data submitted in the request body. By default, it is undefined, and is populated when you use body-parsing middleware such as body-parser)
const app = express();
var bodyParser = require('body-parser');
app.use(bodyParser.json()); // for parsing application/json
app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded
I also notice in your Signin.js component(check the spelling of password)
onSubmitSignIn = () => {
fetch('http://localhost:3001/signin', {
method: 'post',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
email: this.state.signInEmail,
passworrd: this.state.signInPassword // it should be password not passworrd
})
})
this.props.onRouteChange('home');
}
I am building a boat visualizer using AISHub APIs. After inquiring the APIs I am able to obtain a json file with the vessels and filter with only the vessel I am interested in, and inject them into a table on the webpage. The API gives the following fileds:
[NAME, MMSI, LONGITUDE, LATITUDE]. I can correctly connect to MongoDB as I npm start.
The problem: I would like to send also this data to a collection in to MongoDB database every 5 minutes. I tried many different way to do that, but none of them seems to be working.
It was very difficult to think which parts of the code to pass and which not, but below I believe there are enough information to understand what the problem is:
app.js is where I set the MongoDB connection
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var mongoose = require('mongoose');
const bodyParser = require('body-parser');
const vesselController = require('./controllers/VesselController');
require('./config/keys');
var app = express();
app.use(cors());
app.options('*', cors());
// DB Config
const db = require('./config/keys').MongoURI;
const options = {
useNewUrlParser: true,
reconnectTries: Number.MAX_VALUE,
poolSize: 10
};
mongoose
.connect(db, options)
.then(() => console.log('MongoDB Connection established'))
.catch((err) => console.log('Error connecting MongoDB database due to: ', err));
const PORT = process.env.PORT || 3000;
app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' }));
app.use(bodyParser.json({ limit: '50mb' }));
app.use(cors());
app.route('/vessels/all').get(vesselController.getBaseAll);
app.route('vessels/:id/track').get(vesselController.getCurrent);
app.route('/vessels').get(vesselController.getHistory);
app.listen(PORT, console.log(`Server started on port ${PORT}`));
module.exports = app;
index.js is where I am caching the data (and where I think the app should send data to MongoDB)
var express = require('express');
var router = express.Router();
var axios = require('axios');
const NodeCache = require('node-cache');
const myCache = new NodeCache();
let hitCount = 0;
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
const mmsiOfInterest = [
'367029520',
'366909730',
'367128570'
];
const shipNamesOfInterest = [
'MICHIGAN',
'JP BOISSEAU',
'DELAWARE BAY'
];
router.get('/hello', async function(req, res, next) {
const allData = myCache.get('allData');
if (!allData) {
hitCount++;
console.log(`hit ${hitCount} number of times`);
const { data } = await axios.get(
'http://data.aishub.net/ws.php?username=KEY&format=1&output=json&compress=0&latmin=11.42&latmax=58.20&lonmin=-134.09&lonmax=-52.62'
);
const [ metaData, ships ] = data;
console.log(data);
const shipsOfInterest = ships.filter(
(ship) => mmsiOfInterest.includes(ship.MMSI) || shipNamesOfInterest.includes(ship.NAME)
);
myCache.set('allData', shipsOfInterest, 70);
res.send(data);
return;
}
res.send(allData);
});
module.exports = router;
VesselController.js: is where I have the functions for getting different information such as currect vessel, all vessels, history of vessels
module.exports.getBaseAll = (req, res) => {
Promise.all([
Compnanies.find(),
Vessels.find(),
Positions.aggregate([
{
$sort: {
date: -1
}
},
{
$group: {
_id: '$callsign',
details: {
$push: '$$ROOT'
}
}
},
{
$replaceRoot: {
newRoot: {
$arrayElemAt: [ '$details', 0 ]
}
}
}
])
])
.then(([ companies, vessels, positions ]) => {
// apply vessels detail table as join:
positions.forEach((pos) => {
vessels.forEach((ves) => {
if (pos.callsign == ves.callsign) {
p._detail = ves;
}
});
companies.forEach((com) => {
if (p._detail.company == com.number) {
p._detail = com;
}
});
});
res.status(200).json(positions);
})
.catch((err) => {
return res.status(500).send(err);
});
console.log(vesselController.getBaseAll);
};
module.exports.getHistory = (req, res) => {
var id = req.param.id;
Positions.find(
{
callsign: id,
date: {
$gte: new Date(Date.now() - 1000 * 60 * 60 * 24)
}
},
(err, task) => {
if (err) {
return res.status(500).send(err);
}
res.status(200).json(task);
}
);
console.log(vesselController.getHistory);
};
module.exports.getCurrent = (req, res) => {
var currentPos = Positions.find({
date: {
$gte: new Date(Date.now() - 1000 * 60 * 60)
}
});
currentPos.exec((err, task) => {
if (err) {
return res.status(500).send(err);
}
res.status(200).json(task);
});
console.log(vesselController.getCurrent);
};
In LatitudeLongitude.js I set the proper format according to MongoDB documentation
const mongoose = require('mongoose');
const LatitudeLongitudeSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
mmsi: {
type: Number,
required: false
},
longitude: {
type: Number,
required: false
},
latitude: {
type: Number,
required: false
}
});
const LatitudeLongitude = mongoose.model('LatitudeLongitude', LatitudeLongitudeSchema);
module.exports = LatitudeLongitude;
users.js is where I set the router.post
var express = require('express');
var router = express.Router();
const LatitudeLongitude = require('../models/LatitudeLongitude');
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
router.post('/vessles/map', function(req, res) {
const { name, mmsi, longitude, latitude } = req.body;
let errors = [];
// Check required fields
if (!name || !mmsi || !longitude || !latitude) {
errors.push({ msg: 'No data received' });
}
if (
LatitudeLongitude.findOne({ mmsi: mmsi }).then((pos) => {
if (pos) {
// vessel exists
const newVessel = new Vessles({
name,
mmsi,
longitude,
latitude
});
}
})
);
});
module.exports = router;
Below the collection set inside MongoDB, which is clearly empty:
How can I successfully pass information from the API I mentioned above, into my MongoDB?
What I have donbe so far:
I tried many different ways to pass data from the API to MongoDB every 5 minutes. Among the dirfferent approaches, the one I included in this post are, I think, the most effective, however there is something missing I can't catch/understand.
I believe that the file index.js should be the one that is in charge of taking care of doing that, as I pre-set all the check in that file. However I am now confused whether that is the right location or not.
the reasons why you see the routes as app.route('/vessels/all').get(vesselController.getBaseAll); in the app.js file is because I will use that as end point api check with Postman once everything is working. I figured I would leave it there so you see how I designed the process.
Thanks for shedding light on this matter.
In my opinion, you should first test my approach by attaching following code to your server.js file. then refactor it
setInterval( () => {
fetch('http://your/get/route').
then( updateDB() ).
catch(doWhateverYouWant());
},3000
);
Problem is that in my second post request I cannot get req.body. Neither console.log nor req.body show something.
In the first post request, everything seems to work just fine. And it was also working before I start to re-arrange code. I even change the type of the reply to send, but receiving the old message (status ok).
I do not understand what's going on. Any help, please?
Glitch: https://uatyroni-exercise-tracker.glitch.me - live app
code: https://glitch.com/~uatyroni-exercise-tracker
const express = require("express");
const app = express();
const bodyParser = require("body-parser");
const shortid = require('shortid');
const cors = require("cors");
//Setting MongoDB
const mongoose = require("mongoose");
mongoose.connect(process.env.MONGO_URI);
app.use(cors());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(express.static("public"));
app.get("/", (req, res) => {
res.sendFile(__dirname + "/views/index.html");
});
//Defining Schema & Model
let Schema = mongoose.Schema;
let userSchema = new Schema({
id: { type: String, unique: true, default: shortid.generate },
user: String,
exercise: [{
description: String,
duration: Number,
date: {}
}]
});
let userModel = mongoose.model("Users", userSchema);
let exerciseSchema = new Schema({
userId: String,
description: String,
duration: Number,
date: String
});
let exerciseModel = mongoose.model("Exercies", exerciseSchema);
//THE POST PROCESS
app.post("/api/exercise/new-user", (req, res) => {
let userName = req.body.username;
let userNew = new userModel({ user: userName });
userModel
.find()
.exec()
.then(data => {
data = data.filter(obj => obj["user"] === userName);
console.log("I am the data: " + data);
if (data.length === 0) {
userNew
.save()
.then(result => {
res.json(result);
})
.catch(err => {
console.log(err);
res.json({ error: err });
});
} else {
res.json({ Error: "User is already registered in the database" });
}
});
});
app.post("/api/exercise/add", (req, res) => {
console.log('reqbody is: ' + req.body.description)
let newDate = '';
if (req.body.date == '') {
newDate = new Date().getFullYear() + '-' + new Date().getMonth() + '-' + new Date().getDate();
}
let newExercise = {
description: req.body.description,
duration: req.body.duration,
date: newDate
}
userModel.findById(req.body.userid, (err, data) => {
if (data.length == null)
res.json({
error: "User is not registered. Please register a user first."
});
else {
res.json(data)
}
});
});
// Not found middleware
app.use((req, res, next) => {
return next({ status: 404, message: "not found" });
});
// Error Handling middleware
app.use((err, req, res, next) => {
let errCode, errMessage;
if (err.errors) {
// mongoose validation error
errCode = 400; // bad request
const keys = Object.keys(err.errors);
// report the first validation error
errMessage = err.errors[keys[0]].message;
} else {
// generic or custom error
errCode = err.status || 500;
errMessage = err.message || "Internal Server Error";
}
res
.status(errCode)
.type("txt")
.send(errMessage);
});
const listener = app.listen(process.env.PORT || 4000, () => {
console.log("Your app is listening on port " + listener.address().port);
});